一、课程类型树实现–树形数据无限极获取
1.方案一:递归
每个节点都要发送一条sql
public List<CourseType> recursive(long pid) {
List<CourseType> list = courseTypeMapper.selectList(new EntityWrapper<CourseType>().eq("pid", pid));
if (list == null || list.size()<1) {
return null;
}
for (CourseType courseType : list) {
List<CourseType> course = recursive(courseType.getId());
courseType.setChildren(course);
}
return list;
}
2.方案二:循环
一次性把所以的节点都查询出来,再来组织他们关系
①查询所有节点
②如果是第一级放入返回的list
③如果是非第一级,建立父子关系
1 获取服务 map(id-node)
2 把自己作为父亲一个儿子放进去就ok children=new Array<>();
//循环
private List<CourseType> cycle(long pid) {
//1.准备一个空list
List<CourseType> result=new ArrayList<>();
//2.查询所有类型
List<CourseType> courseTypes = courseTypeMapper.selectList(null);
//3.建立一个map
Map<Long,CourseType> allTypesDto = new HashMap<>();
for (CourseType allType : courseTypes) {
allTypesDto.put(allType.getId(),allType);
}
//4..遍历是否是第一级
for (CourseType courseType : courseTypes) {
Long pidTemp = courseType.getPid();
//4.1.如果是第一级,直接返回列表
if(pidTemp==pid){
result.add(courseType);
}else{
//4.2.如果不是第一级,根据pidTemp找到对应的第一级
CourseType parent = allTypesDto.get(pidTemp);
//4.3.把非第一级的数据添加到第一级里面。
parent.getChildren().add(courseType);
}
}
return result;
}
二、课程类型后台缓存优化
1.常见的缓存实现方案
jpa/mybatis二级缓存------不支持集群环境使用
中央缓存:redis/memcached --支持集群环境
2.数据转换-----fastJson
导包
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson 调用者需要转换-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
入门代码
@Test
public void test() throws Exception{
Person zs = new Person(1L, "zs");
//单个对象--json对象---java对象转json对象
String jsonObj = JSONObject.toJSONString(zs);
System.out.println(jsonObj);
//集合---json数组--java集合转json数组
String jsonArray = JSONArray.
toJSONString(Arrays.asList(zs, zs,
new Person(2L, "ls")));
System.out.println(jsonArray);
//json对象--单个对象--json对象转java对象
Person person = JSONObject.parseObject(jsonObj, Person.class);
System.out.println(person);
//json数组--集合---json数组转java集合
List<Person> persons = JSONArray.parseArray(jsonArray, Person.class);
System.out.println(persons);
}
3.后台缓存优化的实现
3.1.搭建缓存服务端
第一步,导包
<!--redis客户端-jedis-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
第二步,配置redis.properties
redis.host=127.0.0.1
redis.port=6379
redis.password=lihao
redis.timeout=3000
第三步,实现服务
/**
* 获取连接池对象
*/
public enum RedisUtils {
INSTANCE;
static JedisPool jedisPool = null;
static {
//1 创建连接池配置对象
JedisPoolConfig config = new JedisPoolConfig();
//2 进行配置-四个配置
config.setMaxIdle(1);//最小连接数
config.setMaxTotal(11);//最大连接数
config.setMaxWaitMillis(10 * 1000L);//最长等待时间
config.setTestOnBorrow(true);//测试连接时是否畅通
//3 通过配置对象创建连接池对象
Properties properties = null;
try {
properties = new Properties();
properties.load(RedisUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
} catch (IOException e) {
e.printStackTrace();
}
String host = properties.getProperty("redis.host");
String port = properties.getProperty("redis.port");
String password = properties.getProperty("redis.password");
String timeout = properties.getProperty("redis.timeout");
jedisPool = new JedisPool(config, host, Integer.valueOf(port),Integer.valueOf(timeout), password);
}
//获取连接
public Jedis getSource() {
return jedisPool.getResource();
}
//关闭资源
public void closeSource(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
/**
* 设置字符值
*
* @param key
* @param value
*/
public void set(String key, String value) {
Jedis jedis = getSource();
jedis.set(key, value);
closeSource(jedis);
}
/**
* 设置
* @param key
* @param value
*/
public void set(byte[] key, byte[] value) {
Jedis jedis = getSource();
jedis.set(key, value);
closeSource(jedis);
}
/**
*
* @param key
* @return
*/
public byte[] get(byte[] key) {
Jedis jedis = getSource();
try {
return jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSource(jedis);
}
return null;
}
/**
* 设置字符值
*
* @param key
*/
public String get(String key) {
Jedis jedis = getSource();
try {
return jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSource(jedis);
}
return null;
}
/**
* 删除
*
* @param key
*/
public void del(String key) {
Jedis jedis = getSource();
try {
jedis.del(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSource(jedis);
}
}
}
@RestController
@RequestMapping("/redis")
public class RedisCache {
//ctrl+Alt:快速try catch
@PostMapping
public AjaxResult addOrUpdate(@RequestParam("key") String key,@RequestParam("value")String value){
try {
RedisUtils.INSTANCE.set(key, value);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("添加或修改失败");
}
}
@GetMapping
public String get(@RequestParam("key")String key){
try {
return RedisUtils.INSTANCE.get(key);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@DeleteMapping
public AjaxResult del(@RequestParam("key")String key){
try {
RedisUtils.INSTANCE.del(key);
return AjaxResult.me().setMessage("删除成功");
} catch (Exception e) {
e.printStackTrace();
}
return AjaxResult.me().setSuccess(false).setMessage("删除失败");
}
}
3.2.搭建缓存客户端,把服务暴露出去
它的父模块已经导入springcloud相关的包,这里不用导入,直接继承
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>hrm_common_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 给调用的模块来转换json-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson 调用者需要转换-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!--客户端feign支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
client:
//value的值是需要使用该服务的服务的名字
@FeignClient(value = "HRM-COMMON", fallbackFactory = RedisClientFallbackFactory.class )
@RequestMapping("/redis")
public interface RedisClient {
//ctrl+Alt:快速try catch
@PostMapping
AjaxResult addOrUpdate(@RequestParam("key") String key,@RequestParam("value")String value);
@GetMapping
String get(@RequestParam("key")String key);
@DeleteMapping
AjaxResult del(@RequestParam("key")String key);
}
RedisClientFallbackFactory
@Component
public class RedisClientFallbackFactory implements FallbackFactory<RedisClient> {
@Override
public RedisClient create(Throwable throwable) {
return new RedisClient() {
@Override
public AjaxResult addOrUpdate(String key, String value) {
return AjaxResult.me().setSuccess(false).setMessage("redis调用失败");
}
@Override
public String get(String key) {
return "redis调用失败";
}
@Override
public AjaxResult del(String key) {
return AjaxResult.me().setSuccess(false).setMessage("redis调用失败");
}
};
}
}
3.3.调用缓存客户端暴露的服务
引用:
//此包里面已经有feign的包了,所以此处直接依赖即可
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>hrm_redis_client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
开启扫描:
缓存逻辑实现:
@Component
public class CourseTypeCacheImpl implements ICourseTypeCache {
@Autowired
private RedisClient redisClient;
private final static String COURSETYPE_KEY_IN_CACHE="coursetype_key_in_cache";
public List<CourseType> getTreeData(){
String s = redisClient.get(COURSETYPE_KEY_IN_CACHE);
return JSONArray.parseArray(s,CourseType.class);
}
public void setTreeData(List<CourseType> courseTypes){
if(courseTypes==null || courseTypes.size()<1){
redisClient.addOrUpdate(COURSETYPE_KEY_IN_CACHE,"[]");
}else{
String jsonStr = JSONArray.toJSONString(courseTypes);
redisClient.addOrUpdate(COURSETYPE_KEY_IN_CACHE,jsonStr);
}
}
}
service层实现:
public List<CourseType> treeData(long pid) {
//从缓存获取数据
List<CourseType> treeData = courseTypeCache.getTreeData();
//如果缓存里面没有,就从数据库里面获取,并将其放入缓存,再返回
if(treeData!=null && treeData.size()>0){
System.out.println("cache......");
return treeData;
}else{
System.out.println("database......");
//否则先查询数据库
List<CourseType> courseTypes = cycle(pid);
//再存入缓存
courseTypeCache.setTreeData(courseTypes);
//最后返回
return courseTypes;
}
}
4.关于缓存的面试题—面试必备
4.1.你项目中有没有使用到缓存?
用过!在哪个项目的那个模块用的缓存,为什么要用它? 经常查询很少修改
4.2.用的是哪个缓存
redis,它可以集群
4.3.使用缓存的好处
减轻数据库压力,提高访问速度,增强用户体验
4.4.我们缓存数据很多?-redis集群
主从复制-单点故障
哨兵模式-每个节点数据都是一样
redis-cluster: 单点故障,高并发,大量数据
4.5.缓存穿透、缓存击穿和缓存雪崩
缓存穿透:高并发访问缓存和数据库中都不存在的数据,每次都要击穿缓存查询数据库.
解决方法:
方法一:有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层数据库的查询压力。
方法二:另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿:一个key过期,又来高并发访问. 直接高并发访问数据库.
解决办法:
方法一:让热点数据永远不过期.
方法二:加互斥锁,互斥锁参考代码如下 我在访问数据库是你就不要访问,但是这样做的效率低下。
缓存雪崩:一堆key同时过期,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决办法:
方法一:缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
方法二: 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
方法三: 设置热点数据永远不过期。
三、服务间的相互调用
服务间调用–feign-ribbon,hystrix(负载均衡,服务高可用(熔断))
外部调用服务-都是通过zuul-ribbon,hystrix(负载均衡,服务高可用(熔断))
无论是服务间调用或者是外部调用都是进行负载均衡和熔断+降级。
redis服务是给服务间调用,前端不能直接操作redis
fastdfs直接给前端调用,前端可以直接删除图片和删除图片