栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

20.缓存-缓存使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

20.缓存-缓存使用

1 缓存使用

为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访间。而db承担数据落盘工作。

哪些数据适合放入锾存?

  • 即时性、数据—致性要求不高的
  • 访问量大且更新频率不高的数据(读多,写少)

举例:电商类应用,商品分类,商品列表等适合缓存并加一个失效时间(根据数据更新频率来定),后台如果发布一个商品,买家需要5分钟才能看到新的商品一般还是可以接受的。

2 本地缓存与分布式缓存 2.1 本地缓存

2.2 分布式缓存-本地模式在分布式下的问题

其中一个服务的缓存修改了,但是其他服务里的缓存没有办法更新,无法保证数据的一致性

2.3分布式缓存

3 整合Redis 3.1 引入依赖

product模块


    org.springframework.boot
    spring-boot-starter-data-redis

3.2 配置redis
redis:
  host: 192.168.56.10
  port: 6379
3.3 单元测试
@Test
void test03() {
    ValueOperations ops = stringRedisTemplate.opsForValue();

    ops.set("hello", "world_" + UUID.randomUUID());

    String hello = ops.get("hello");
    System.out.println("保存的数据是:" + hello);
}
4 改造三级分类业务

将原来的getCatalogJson方法改名为getCatalogJsonFromDB,并创建一个新的getCatalogJson方法

@Autowired
StringRedisTemplate stringRedisTemplate;

@Override
public Map> getCatalogJson() {
    // 给缓存中放JSON字符串,拿出的JSON字符串,还需要逆转为能用的对象类型

    String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
    if (StringUtils.isEmpty(catalogJSON)) {
        // 缓存中没有,查询数据库
        Map> catalogJsonFromDB = getCatalogJsonFromDB();
        // 将查到的数据放入缓存
        String s = JSON.toJSONString(catalogJsonFromDB);
        stringRedisTemplate.opsForValue().set("catalogJSON", s);

        // 返回查询数据库查到的数据
        return catalogJsonFromDB;
    }

    // 将缓存中查询出的json转换为指定的对象
    Map> result = JSON.parseObject(catalogJSON, new TypeReference>>() {
    });
    return result;
}


// 从数据库查询并封装分类数据
public Map> getCatalogJsonFromDB() {

    // 查询出表pms_category所有的记录实体
    List categoryEntityList = baseMapper.selectList(null);

    // 查出所有的一次分类
    List level_1_categorys = getParent_cid(categoryEntityList, 0L);

    // 封装数据,构造一个以1级id为键,2级分类列表为值的map
    Map> collect = level_1_categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {
        // 根据一级分类id查找二级分类
        List level_2_categorys = getParent_cid(categoryEntityList, l1.getCatId());

        // 封装结果为Catelog2Vo的集合
        List catelog2Vos = null;

        if (level_2_categorys != null) {

            // 把 level_2_categorys 封装为 catelog2Vos
            catelog2Vos = level_2_categorys.stream().map(l2 -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo(l1.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

                // 根据二级分类id查找三级分类
                List level_3_categorys = getParent_cid(categoryEntityList, l2.getCatId());

                // 将 level_3_categorys 封装为 catelog3Vos
                if (level_3_categorys != null) {
                    List catelog3Vos = level_3_categorys.stream().map(l3 -> {
                        Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                        return catelog3Vo;
                    }).collect(Collectors.toList());

                    catelog2Vo.setCatalog3List(catelog3Vos);
                }

                return catelog2Vo;

            }).collect(Collectors.toList());

        }

        return catelog2Vos;
    }));

    return collect;
}

重启服务,访问http://localhost:10000/index/catalog.json测试

5 压力测试内存溢出

云服务器的吞吐量低是网络带宽原因。

问题原因:

  1. SpringBoot2.0以后默认使用Lettuce作为操作redis的客户端。它使用netty进行通信
  2. Lettuce的bug导致netty堆外内存溢出
  3. netty如果没有指定堆外内存,会默认使用服务配置里的 -Xmx 作为堆外内存的大小
  4. 可以通过-Dio.netty.maxDirectMemory进行设置

解决方法:

  1. 升级 Lettuce客户端
  2. 切换使用 jedis客户端
5.1 切换为jedis

1、排除Lettuce依赖包

2、导入jedis依赖


    org.springframework.boot
    spring-boot-starter-data-redis
    
        
            io.lettuce
            lettuce-core
        
    



    redis.clients
    jedis

3、重启服务测试

坑:切换为jedis客户端后压力测试时报如下异常nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out] with root cause

问题原因:

我的redis在阿里云服务器上,压力测试时可能由于网速太慢的问题,redis连接可能超时了

解决方法:

配置redis的超时时间

redis:
  timeout: 100000
5.2 redisTemplate和Lettuce的关系

redisTemplate:spring对Lettuce、jedis的再次封装

Lettuce、jedis:操作redis的底层客户端

6 缓存失效问题 6.1 缓存穿透

6.2 缓存雪崩

6.3 缓存击穿

7 解决缓存击穿 7.1 分布式锁

7.2 锁时序图

7.3 本地锁查询数据库

给getCatalogJsonFromDB方法加锁,

将getCatalogJson方法中添加缓存的代码转移到getCatalogJsonFromDB里

改进后的getCatalogJson方法

@Override
public Map> getCatalogJson() {
    // 给缓存中放JSON字符串,拿出的JSON字符串,还需要逆转为能用的对象类型

    String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");

    if (!StringUtils.hasText(catalogJSON)) {
        // 缓存中没有,查询数据库
        return getCatalogJsonFromDB();
    }

    // 将缓存中查询出的json转换为指定的对象
    Map> result = JSON.parseObject(catalogJSON, new TypeReference>>() {
    });
    return result;
}

改进后的getCatalogJsonFromDB方法

// 从数据库查询并封装分类数据
public Map> getCatalogJsonFromDB() {

    synchronized (this) {
        // 得到锁以后,在去缓存中确定一次
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if(!StringUtils.isEmpty(catalogJSON)) {
            Map> result = JSON.parseObject(catalogJSON, new TypeReference>>() {
            });
            return result;
        }

        // 查询出表pms_category所有的记录实体
        List categoryEntityList = baseMapper.selectList(null);

        // 查出所有的一次分类
        List level_1_categorys = getParent_cid(categoryEntityList, 0L);

        // 封装数据,构造一个以1级id为键,2级分类列表为值的map
        Map> collect = level_1_categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {
            // 根据一级分类id查找二级分类
            List level_2_categorys = getParent_cid(categoryEntityList, l1.getCatId());

            // 封装结果为Catelog2Vo的集合
            List catelog2Vos = null;

            if (level_2_categorys != null) {

                // 把 level_2_categorys 封装为 catelog2Vos
                catelog2Vos = level_2_categorys.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(l1.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

                    // 根据二级分类id查找三级分类
                    List level_3_categorys = getParent_cid(categoryEntityList, l2.getCatId());

                    // 将 level_3_categorys 封装为 catelog3Vos
                    if (level_3_categorys != null) {
                        List catelog3Vos = level_3_categorys.stream().map(l3 -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());

                        catelog2Vo.setCatalog3List(catelog3Vos);
                    }

                    return catelog2Vo;

                }).collect(Collectors.toList());

            }

            return catelog2Vos;
        }));

        // 将查到的数据放入缓存
        String s = JSON.toJSONString(collect);
        stringRedisTemplate.opsForValue().set("catalogJSON", s);
        return collect;
    }

}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/886274.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号