redis缓存在高并发和安全压力下的一些问题解决方案---缓存穿透、缓存击穿、缓存雪崩;redis分布式锁:set px nx、Redisson实现java代码层控制分布式锁

1、缓存击穿

  • 是某一个热点key在高并发访问的情况下,突然失效,导致大量的并发打进mysql数据库的情况
  • 解决:在正常的访问情况下,如果缓存失效,需保护mysql,在重启缓存的过程,
    使用redis数据库的分布式锁,解决mysql的访问压力问题。

2、缓存穿透

  • 是利用redis和mysql的机制(redis缓存一旦不存在,就访问mysql),直接绕过缓存访问mysql,而制造的db请求压力
    一般在代码中防止该现象的发生
  • 解决: 为了防止缓存穿透将,null或者空字符串值设置给redis

3、缓存雪崩

  • 缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,导致的db崩溃
  • 解决:设置不同的缓存失效时间

4、解决缓存击穿的分布式锁有两种

1. redis自带一个分布式锁,set px nx

  • 这种分布式锁作用在redis上
  • redis上的命令:set key:lock 1 px time(ms) nx
    在这里插入图片描述

2. redisson框架,一个redis的带有juc的lock功能的客户端的实现(既有jedis的功能,又有juc的锁功能)

  • 这种分布式锁作用在多个Jedis上
    在这里插入图片描述

3.使用redis分布式锁的实现

3.1、使用本方法处理高并发下的人会遇到的问题
1、 高并发下,若是拿锁的线程因某些原因,导致自己的锁未经过自己释放而过期,而某一时刻该线程醒来时,会来释放锁,而此时redis上的锁已经是别的线程上的锁了,怎么办?

解决此问题有两种方案:

    1. 不主动释放锁,让其自动释放。(但要求高响应的系统都不会这样实现)
    1. 仍然主动释放锁,但是设置锁的key对于的唯一Value ,需解锁时,先判断是否是自己的锁,再释放锁,是则释放,否则直接跳过。
2、若是刚好在比对锁的key对于的唯一Value的时候,该线程的锁过期,由别的线程进入并拿到锁,此时释放锁,而此时redis上的锁已经是别的线程上的锁了,该怎么办?
  • 可以用lua脚本,在查询到key的同时删除该key,防止高并发下的该意外的发生。
    (lua:别让我看到你,看到我就把你干掉)
    在这里插入图片描述
3.2、redis分布式锁代码体现,以下代码对上述问题的解决方案都有体现。

本代码是一个业务逻辑中的服务层的一个方法。

public PmsSkuInfo getSkuBySkuId(String skuId,String ip) {
        System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"进入的商品详情的请求");
        //获取jedis对象
        Jedis jedis = redisUtil.getJedis();
        //获取skuinfo
        PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();

        //构造key
        String skukey = "sku:"+skuId+":Info";
        //查缓存
        String skuJson = jedis.get(skukey);
        //判断缓存是否存在
        if (StringUtils.isNotBlank(skuJson)){
            //缓存存在 提取出数据封装在对象中
            pmsSkuInfo = JSON.parseObject(skuJson,PmsSkuInfo.class);
        }else{
            //缓存中不存在改数据 去mysql中查找

            //设置分布式锁 防止击穿
           
            //设置redis上分布式锁的唯一值
            String lockValue = UUID.randomUUID().toString();
            //上锁成功放回 OK
            String Ok = jedis.set("sku:"+skuId+":lock",lockValue,"nx","px",10*1000);
            if (StringUtils.isNotBlank(Ok) && Ok.equals("OK")){
                //设置锁成功 有权在10s内操作mysql
                pmsSkuInfo = getSkuBySkuIdFromDb(skuId);
                if (pmsSkuInfo != null){

                    //先睡眠5s再让其操作mysql  再解锁
                    // 睡眠时为了看到其他线程自旋效果 生产环境不会睡眠
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //db存在数据 将db数据存入redis
                    jedis.set("sku:"+skuId+":Info", JSON.toJSONString(pmsSkuInfo));
                }else{
                    //db不存在数据 将null存入redis 防止穿透
                    jedis.set("sku:"+skuId+":Info",JSON.toJSONString(""));
                }

                //操作完mysql,将redis的分布式锁释放
                System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"使用完毕,将锁归还:"+"sku:" + skuId + ":lock");
                String currentLockValue = jedis.get("sku:"+skuId+":lock");
                if (StringUtils.isNotBlank(currentLockValue) && currentLockValue.equals(lockValue)){
                    //jedis.eval("lua");可与用lua脚本,在查询到key的同时删除该key,防止高并发下的意外的发生
                    jedis.del("sku:" + skuId + ":lock");// 用token确认删除的是自己的sku的锁
                }

            }else {
                //设置失败 说明已经有别的用户线程设置了锁
                System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"没有拿到锁,开始自旋");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //自旋 让其睡眠几秒后重写调用本方法  睡眠是为了看效果 具体生产环境不会设置睡眠
                //注意 不能不带return ,若是不带return 直接调用方法,会另外启动一个线程
                //被新启动的线程为孤儿线程,别的线程无法访问
                return getSkuBySkuId(skuId,ip);
            }
        }
        //关闭jedis
        jedis.close();
        return pmsSkuInfo;
    }
3.1.1、模拟并发,两个页面同时访问,看后台输出,第一个先进入的线程会拿到锁,锁是唯一,别的线程拿不到锁会不断自旋,直到拿锁线程释放锁为止。
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-2进入的商品详情的请求
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-2拿到分布式锁
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-3进入的商品详情的请求
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-3没有拿到锁,开始自旋
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-3进入的商品详情的请求
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-3没有拿到锁,开始自旋
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-2使用完毕,将锁归还:sku:115:lock
ip为127.0.0.1的同学:DubboServerHandler-192.168.154.1:53630-thread-3进入的商品详情的请求

4、使用redission的简单实现

redisson封装许多juc的并发工具类,可以调用内部工具类实现并发控制。

public String testRedisson(){
        Jedis jedis = redisUtil.getJedis();
        RLock lock = redissonClient.getLock("lock");// 声明锁
        lock.lock();//上锁
        try {
            String v = jedis.get("k");
            if (StringUtils.isBlank(v)) {
                v = "1";
            }
            System.out.println("->" + v);
            jedis.set("k", (Integer.parseInt(v) + 1) + "");
        }finally {
            jedis.close();
            lock.unlock();// 解锁
        }
        return "success";
    }

有兴趣的小伙伴可以 一起交流哦!!!

原创文章 59 获赞 21 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_44185736/article/details/105572245