谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁

文章目录

  • 缓存使用
  • SpringBoot使用redis异常
  • Jedis
    • 使用jedis

缓存使用

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

哪些数据适合放入缓存?

  • 即时性、数据一致性要求不高的
  • 访问量大且更新频率不高的数据(读多,写少)
    举例:电商类应用,商品分类,商品列表等适合缓存并加一个失效时间(根据数据更新频率来定),后台如果发布一个商品,买家需要5分钟才能看到新的商品一般还是可以接受的。
    谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第1张图片
    本地缓存可以使用map来做缓存,分布式下则不行,则需要加入缓存中间件来统一缓存
    谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第2张图片
    这里我们使用redis,Java使用redis有很多方法。

SpringBoot使用redis异常

springBoot可以直接使用redis,但是可能会有异常

    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if(StringUtils.isBlank("catalogJSON") || catalogJSON == null){
            Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            String s = JSON.toJSONString(catalogJsonFromDb);
            stringRedisTemplate.opsForValue().set("catalogJSON",s,1,TimeUnit.SECONDS);
            return catalogJsonFromDb;
        }
        Map<String, List<Catalog2Vo>> map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return map;
    }

堆外内存溢出:
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第3张图片
原因
springboot在2.0后默认使用lettuce操作redis客户端,它使用netty进行网络通信,lettuce的bug导致堆外内存溢出,底层实现是只要有操作,就会统计内存使用量,操作完会decrement减内存,可能是lettuce客户端在减内存的过程出错
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第4张图片
在这里插入图片描述
解决:
对这个问题调节-Xmx大小不起作用,而且如果netty(netty如没有指定,默认是-Xmx300m)没指定堆外内存,默认使用-Xmx,所以一旦出现问题,就算调大-Xmx也总会到堆外内存溢出的地步。所以我们不能用io.netty.maxDirectMemory只修改堆外内存大小。
还有2种方案:

1、升级lettuce客户端
2、排除lettuce依赖,使用jedis

Jedis

jedis是什么
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;

使用jedis

Jedis实例不是线程安全的,所以不可以多个线程共用一个Jedis实例,但是创建太多的实现也不好因为这意味着会建立很多sokcet连接。
JedisPool是一个线程安全的网络连接池。可以用JedisPool创建一些可靠Jedis实例,可以从池中获取Jedis实例,使用完后再把Jedis实例还回JedisPool。这种方式可以避免创建大量socket连接并且会实现高效的性能.

依赖

        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
            <version>2.9.3version>
        dependency>

RedisUtil

public class RedisUtil {

    private  JedisPool jedisPool;

    public void initPool(String host,int port ,int database){
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(200);
        poolConfig.setMaxIdle(30);
        poolConfig.setBlockWhenExhausted(true);
        poolConfig.setMaxWaitMillis(10*1000);
        poolConfig.setTestOnBorrow(true);
        jedisPool=new JedisPool(poolConfig,host,port,20*1000);
    }

    public Jedis getJedis(){
        Jedis jedis = jedisPool.getResource();
        return jedis;
    }

}

RedisConfig

@Configuration
public class RedisConfig {

    //读取配置文件中的redis的ip地址
    @Value("${spring.redis.host:disabled}")
    private String host;

    @Value("${spring.redis.port:0}")
    private int port;

    @Value("${spring.redis.database:0}")
    private int database;

    @Bean
    public RedisUtil getRedisUtil(){
        if(host.equals("disabled")){
            return null;
        }
        RedisUtil redisUtil=new RedisUtil();
        redisUtil.initPool(host,port,database);
        return redisUtil;
    }

}

application.properties

spring.redis.host=redis服务地址
spring.redis.port=6379
spring.redis.database=0

使用测试

    @Autowired
    RedisUtil redisUtil;

    @Test
    public void testRedis() {

        Jedis jedis = redisUtil.getJedis();
        //jedis.set("test","test");
        String s = jedis.get("test");
        System.out.println(s);
    }

业务使用

 /**
     * 查出所有分类 返回首页json
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        //加入缓存逻辑
        Jedis jedis = redisUtil.getJedis();
        String catalogJson = jedis.get("catalogJson");

        Map<String, List<Catalog2Vo>> json = null;

        //缓存存在 转换返回
        if (!StringUtils.isEmpty(catalogJson)) {
            json = JSONObject.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
            return json;
        }
        
        //缓存没有从数据查询
        json = getCatalogJsonFromDB();
        //转成str 加入缓存
        String jsonString = JSON.toJSONString(json);
        jedis.set("catalogJson", jsonString);

        return json;
    }
 /**
     * 从数据库获取数据并封装返回
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {
        //查询出所有分类
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        //先查出所有一级分类
        List<CategoryEntity> level1Categorys = getCategorys(selectList, 0L);

        //封装数据 map k,v 结构
        Map<String, List<Catalog2Vo>> map = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //每一个的一级分类,查到这个一级分类的二级分类
            List<CategoryEntity> category2Entities = getCategorys(selectList, v.getCatId());
            List<Catalog2Vo> catelog2Vos = null;

            if (category2Entities != null) {
                catelog2Vos = category2Entities.stream().map(level2 -> {
                    //封装catalog2Vo
                    Catalog2Vo catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, level2.getCatId().toString(), level2.getName());
                    //每一个二级分类,查到三级分类
                    List<CategoryEntity> category3Entities = getCategorys(selectList, level2.getCatId());
                    if (category3Entities != null) {
                        List<Object> catalog3List = category3Entities.stream().map(level3 -> {
                            //封装catalog3Vo
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        //封装catalog3Vo到catalog2Vo
                        catalog2Vo.setCatalog3List(catalog3List);
                    }
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }
            //返回v=catalog2Vo
            return catelog2Vos;
        }));


        return map;
    }

测试ok
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第5张图片
但是这样在高并发下缓存是会存在很多问题的,详情参见分布式-Redis-缓存问题
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第6张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第7张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第8张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第9张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第10张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第11张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第12张图片
谷粒商城微服务分布式高级篇八——分布式缓存-Redis-Jedis-分布式锁_第13张图片
lua脚本:

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

就是加锁设置过期时间保证加锁操作是原子性的,解锁也是同样保持原子性。(原子性简单理解就是1和2两个操作都是要同时ok这整个任务才能算ok)

加锁获取数据

 public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithRedisLock() {
        Jedis jedis = redisUtil.getJedis();
        //加锁
        String token = UUID.randomUUID().toString();
        String lock = jedis.set("lock", token, "NX", "EX", 20);
        System.out.println(lock);

        Map<String, List<Catalog2Vo>> map = null;
        //加锁成功
        if ("ok".equals(lock)) {
            map = getCatalogJsonFromDB();
            //删除锁 lua脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            jedis.eval(script, Collections.singletonList("lock"), Collections.singletonList(token));
            return map;
        } else {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatalogJsonFromDBWithRedisLock();
        }

    }

Redis总结

你可能感兴趣的:(谷粒商城,redis,java,缓存,数据库,多线程)