Redis解决并发的思路

java的redis处理并发 redis解决并发_分布式

Redis中的数据存储策略

企业中的数据存储策略 其核心就是设计Key

这里我们的Key的设计是

数据对象名:数据对象id:对象属性

Key ---- Sku:108:info

Redis解决并发的简单代码实现

@Override
    public PmsSkuInfo getSkuById1(String skuId) {
        PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
        //链接缓存
        Jedis jedis = redisUtil.getJedis();
        //从缓冲中获得数据
        String skuJson = jedis.get("sku"+skuId+"info");
        if(StringUtils.isNotBlank(skuJson)){
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }else {
            //缓存没有 从db中取数据
            pmsSkuInfo=getSkuByIdFromDb(skuId);
            //将db数据写入redis缓存
            if(pmsSkuInfo!=null){
                jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
            }
        }
        return pmsSkuInfo;
    }

Redis缓存问题 — 缓存穿透

  • 缓存穿透的概念
    缓存穿透是指查询一个一定不存在的数据 由于缓存不命中 将去查询db 这导致这个不存在的数据每次都要去db查询 在流量大时 db就可能挂掉 要是有人利用不存在的key频繁攻击我们的应用 这就是漏洞
  • 解决方案
    对空结果进行缓存 并且设置一个过期时间
if(pmsSkuInfo!=null){
                jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
            }else{
                //数据库中没有这个sku
                //为了防止缓存穿透  设置一个空字符串给redis
                jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString(""));
            }

Redis缓存问题 — 缓存击穿

  • 缓存击穿的概念
    对于一些设置了过期时间的key 这些key可能会在某段时间内被高并发的访问 是一种非常热点的数据
    这个时候 如果这个key在大量请求同时进来前刚好失效 那么所有对于这个key的数据查询全部都落在了
    db上 我们称为缓存击穿
    缓存击穿指某一个热点key在高并发的情况下突然失效 导致大量的并发打到db上
  • 解决方案
    利用Redis数据库的分布式锁 解决Mysql的访问压力问题

java的redis处理并发 redis解决并发_java的redis处理并发_02

java的redis处理并发 redis解决并发_缓存_03

  • 设置分布式锁的代码简单实现
//缓存没有 从db中取数据
            //设置分布式锁
            String OK = jedis.set("sku" + skuId + "lock", "1", "nx", "px", 10);
            if (StringUtils.isNotBlank(OK)&&OK.equals("OK")){
                //设置分布式锁成功  有权利在规定的过期时间里访问Mysql数据库
                pmsSkuInfo=getSkuByIdFromDb(skuId);

                //将db数据写入redis缓存
                if(pmsSkuInfo!=null){
                    jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
                }else{
                    //数据库中没有这个sku
                    //为了防止缓存穿透  设置一个空字符串给redis
                    jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString(""));
                }
                
                //在访问Mysql后 将分布式锁释放掉
                jedis.del("sku"+skuId+"lock");

            }else {
                //设置分布式锁失败 自旋尝试获取锁
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                return getSkuById1(skuId);
            }
  • 设置分布式锁的测试结果

java的redis处理并发 redis解决并发_缓存_04

结果与我们预想的一致 速度比较快的11先设置分布式锁成功后 进行Mysql的数据库的访问

此时 12进入商品详情页 设置分布式锁失败 这样一来 我们就利用了Redis的分布式锁解决了缓存击穿的情况下对于Mysql数据库的高并发的访问问题

  • 如果Redis中的锁已经过期了 然后锁过期的请求又执行完毕 回来删锁 删除了其他线程的锁 怎么办?
    我们画图解释一下上面这种场景

java的redis处理并发 redis解决并发_java的redis处理并发_05

解决方式 在第一次设置分布式锁的时候 将value值设置为一个随机生成的token值

在删除锁的时候再去做一次判断 验证两次获取的token是否一致

这部分的完整代码

//根据sku_id查出对应的某一个sku
    @Override
    public PmsSkuInfo getSkuById(String skuId) {
        System.out.println(Thread.currentThread().getName() + "进入商品详情");
        PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
        //连接缓存
        Jedis jedis = redisUtil.getJedis();
        //查询缓存
        String skuKey = "sku:" + skuId + ":info";
        String skuJson = jedis.get(skuKey);
        if (StringUtils.isNotBlank(skuJson)) {
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
            System.out.println(Thread.currentThread().getName() + "从缓存中获取数据");
        } else {
            //缓存没有 查mysql
            //查询mysql之前 设置分布式锁
            String token = UUID.randomUUID().toString();
            System.out.println(Thread.currentThread().getName()+"发现缓存中没有  申请缓存的分布式锁");
            String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000);
            if (StringUtils.isNotBlank(ok) && ok.equals("OK")) {
                System.out.println(Thread.currentThread().getName() + "设置分布式锁成功 可以访问mysql数据库");
                //设置成功 有权利在10秒过期时间访问数据库
                pmsSkuInfo = getSkuByIdFromDb(skuId);

                /*
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/

                //mysql查询结果放入redis
                if (pmsSkuInfo != null) {
                    jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo));
                } else {
                    //数据库没有这个sku
                    //为了防止缓存穿透 设置一个null或者空字符串给redis
                    jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString(""));
                }
                System.out.println(Thread.currentThread().getName() + "释放缓存的分布式锁");
                String token2 = jedis.get("sku:" + skuId + ":lock");

                if (StringUtils.isNotBlank(token2) && token2.equals(token)) {
                    //用token确认删除的是自己的锁
                    //释放分布式锁
                    jedis.del("sku:" + skuId + ":lock");
                }

            } else {
                System.out.println(Thread.currentThread().getName() + "设置分布式锁失败 自旋尝试获取锁");
                //分布式锁设置失败
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return getSkuById(skuId);
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }

Redis缓存问题 — 缓存雪崩

  • 缓存雪崩的概念
    缓存雪崩是指在设置缓存时采用了相同的过期时间 导致缓存在某一时刻同时失效 请求全部打到db
    缓存雪崩是很多key集体失效
  • 解决方法
    设置不同的缓存失效时间