Redis 缓存穿透,击穿,互斥锁解决方案,模拟高并发请求,附代码

话不多说直接入正题,首先了解Redis 缓存穿透,击穿是什么概念;

缓存击穿:指单个热点key大量请求,而redis缓存由于某种原因刚好失效,直接请求DB(该数据存在数据库),导致数据库承受不住压力宕机,服务因此被干懵了;

缓存穿透:大量请求数据库一定不存在的数据,由于缓存不起作用,数据库被大量请求,又被干懵了

一般这种问题的解决方式,我采用的是加锁,用互斥锁让并发线程排队,实现对数据库压力的减少!但是直接锁每一个线程会造成资源浪费,假设30个并发线程,其实只需要第一个线程拿到锁,请求数据库,再set到redis里去,其余29个线程直接拿redis 的数据就可以了!

对于缓存穿透是博主采用的直接暴力给空对象,设置过期时间,让其尽量少走数据库!但是这种情况只是对请求的key是一定的,但实际生产中,对于大量的随机key是无效的,后续优化采用业内常用的布隆过滤器方法~

请求直接上代码

@Override
    public User queryById(int id) throws InterruptedException {

       User user= (User) redisTemplate.opsForValue().get(id+"");

        if (null==user)
        {
            //排队拿到锁,请求数据库
            if (reentrantLock.tryLock())
            {
                try {
                    System.out.println(Thread.currentThread().getName()+"拿到锁请求数据库--》");
                    user=deptDao.queryById(id);
                    if (user==null)
                    {
                        //防止缓存穿透 设置空对象
                        redisTemplate.opsForValue().set(id+"",new User(),30, TimeUnit.MINUTES);
                    }else {
                        redisTemplate.opsForValue().set(id+"",user);
                    }

                }
                finally {
                    reentrantLock.unlock();
                }

            }else{
                 user =(User)redisTemplate.opsForValue().get(id+"");
                 if (null==user)
                 {
                     System.out.println(Thread.currentThread().getName()+"等待--》");
                     Thread.sleep(100);

                     return queryById(id);
                 }
            }
        }

这个方法的好处就是不需要让每个线程都请求数据,高并发时仅有少数几条数据请求数据库,博主自己测试500并发请求,也只有偶尔两条线程请求数据库,按照这个比例数据库完成能hold住,上述代码是service 层;

废话不多说直接看结果!

首先controller 模拟500并发请求service

    @GetMapping("/dept/get/{id}")
    public List<User> queryById (@PathVariable int id) throws InterruptedException {
        List<User> list=new CopyOnWriteArrayList<>();
        for (int i = 0; i <500 ; i++) {

            new Thread(()->{


                try {
                 User user= deptService.queryById(id);
                    list.add(user);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            },"Thead"+i).start();

        }
        Thread.sleep(5000);
        System.out.println("当前主线程"+list.size());
        return list;
    }

因为代码测试是不是所有的并发请求都拿到正确的值,所以采用list集合装所有访问结果,但是由于并发是同时抢cpu资源,肯定会造成主线程结束,其余并发请求未结束,造成list的size 一直为0 !为此主线程需等待5s,手动设置主线程最后结束,才能拿到所有的请求结果!

模拟测试:缓存击穿

redis里只有ID为2,3,4的数据,所以设置请求ID为1(数据库存在),

浏览器输入请求地址http://localhost:8001/dept/get/1

运行结果:

可以看到500并发线程,仅有不到5条请求直接落到了DB上,其余的都是在等待0.1秒后,直接请求redis了!效果还是可以的!

为验证请求的数据是否正确,前台也返回出请求的结果的结果集合!

注:本人是刚毕业的小白,若有错误请指正,谢谢!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值