课程类型类型树查询及后台缓存优化

一、课程类型树实现–树形数据无限极获取

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直接给前端调用,前端可以直接删除图片和删除图片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值