本文章主要介绍了使用同步代码块+双重判断解决redis缓存穿透问题,具有不错的的参考价值,希望对您有所帮助,如解说有误或未考虑完全的地方,请您留言指出,谢谢!

      我写的程序里面有控制层、业务层、持久层,主要测试方法就是通过Student对象的id属性值,去redis中查询对象(student对象在redis是存储的key是studentKey),如果redis中没有这个对象,那就去数据库中查询相应的student对象,然后把这个对象放入redis中,下次查询的时候就直接从redis中获取就可以了,如果说一个线程来访问是没有问题的,但是如果考虑多线程高并发的情况,那就会出现redis缓存穿透的问题了,所以我创建了一个线程池,使用for循环设置一万个线程去测试,以下是我在控制层中编写的多线程高并发代码:

// 设置线程池的大小:建议是CPU的内核数或者是CPU内核数的两倍 
ExecutorService executorService = Executors.newFixedThreadPool(8); 
 
// 设置一万个线程 
for (int i = 0; i < 10000; i++){ 
    executorService.submit(new Runnable() { 
        @Override 
        public void run() { 
            studentService.getStudentById(1); 
        } 
    }); 
}

其中studentService.getStudentById(1);就是通过student的id去获取student对象的代码,然后去业务层执行真正的业务,没有解决redis缓存穿透的代码是这样写的:

Student student = (Student) redisTemplate.opsForValue().get("studentKey"); 
if (null == student){ 
    System.out.println("查询数据库。。。。。。。。。。。。。"); 
    student = studentMapper.selectByPrimaryKey(id); 
    redisTemplate.opsForValue().set("studentKey",student); 
 
}else{ 
    System.out.println("查询redis。。。。。。。。。。。。。"); 
}

上面那两条输出语句是看看这10000次请求都是从哪里获得数据的,执行之后就产生了下面的结果:

由于篇幅问题,就截取这么多吧,可以看出前面很多个线程都查询了数据库,但是这只用了1万个线程,如果是100万个线程呢,那数据库不就崩溃了,但是为什么会出现这样的问题呢,相信有很多朋友可能不是特别清晰,我就简单说下我的理解吧,假设有10个线程几乎同时进入service中的查询方法,那么将会同时有10个线程查询的student是null,那么他们也会同时去向数据库查询数据,这样就大大加大了服务器数据库的压力,所以我使用同步代码块+双重循环来解决上述问题,对于上面的第二段代码,我将其改编成下面这段代码:

Student student = (Student) redisTemplate.opsForValue().get("studentKey"); 
 
if (null == student){ 
    // 使用双重查询+同步代码块解决redis缓存穿透问题 
    synchronized (this){ 
        student = (Student) redisTemplate.opsForValue().get("studentKey"); 
        if (null == student){ 
            System.out.println("查询数据库。。。。。。。。。。。。。"); 
            student = studentMapper.selectByPrimaryKey(id); 
            redisTemplate.opsForValue().set("studentKey",student); 
        }else{ 
            System.out.println("查询redis。。。。。。。。。。。。。"); 
        } 
    } 
}else{ 
    System.out.println("查询redis。。。。。。。。。。。。。"); 
}

我先说明一下这样改为什么能解决redis缓存穿透的问题,假设还是10个线程同时进入if循环,他们此时的student都是null,之后他们遇到了同步代码块,那他们只能一个一个执行了,当第一个线程进入同步代码块的时候发现redis中没有student对象,然后进入第二个if判断,先输出“查询数据库。。。。。”的语句,之后就从数据库中获取到了student对象,并将其存放在了redis缓存中,当下一个线程进入同步代码块的时候先从redis中查询student,由于前面那个线程已经把student对象放入redis缓存中了,所以student不为null,进行else里面打印“查询redis。。。。。”字符串到屏幕上,后面一起进来的线程都可以查询到student,所以不会在打印“查询数据库。。。。。”了,这是第一批的10个线程,如果是后面的那些线程会直接从redis中查到student,不会进入第一个if判断,所以屏幕上只打印了一行“查询数据库。。。。。”,接下来看结果:

这就是我想要的结果,已经成功解决了redis缓存穿透的问题,如果有感觉我代码说的不清晰的小伙伴,可以看下面我的控制层和业务层代码,持久层是我使用MyBatis逆向工程生成的,使用生成的查询方法,所以就不贴出来了,不过里面有一些Springboot的内容,不过不影响的;

控制层代码:

package com.bjpowernode.springboot.controller; 
 
import ....(太多了就用...代替吧!) 
@Controller 
public class StudentController { 
 
    @Autowired 
    private StudentService studentService; 
 
    @RequestMapping(value = "/boot/student") 
    public @ResponseBody Object student(){ 
        // 设置线程池的大小:建议是CPU的内核数或者是CPU内核数的两倍 
        ExecutorService executorService = Executors.newFixedThreadPool(8); 
 
        // 设置一万个线程 
        for (int i = 0; i < 10000; i++){ 
 
            executorService.submit(new Runnable() { 
                @Override 
                public void run() { 
                    studentService.getStudentById(1); 
                } 
            }); 
        } 
        return studentService.getStudentById(1); 
    } 
}

业务层代码:

package com.bjpowernode.springboot.service; 
 
import ...(还是省略哈!) 
 
@Service 
public class StudentService { 
 
    @Autowired 
    private StudentMapper studentMapper; 
 
    // 这个是springboot自动初始化的,可以直接注入使用,里面如果想写的话只能是<String,String>或者<Object,Object>, 
    // 不过也可以不写,可能自动会给你进行匹配,其实<Object,Object>就可以完全匹配了 
    @Autowired 
    private RedisTemplate redisTemplate; 
 
    public Object getStudentById(int id) { 
 
        // 设置key的序列化方式,让redis中的key能显示字符串,而不是二进制数字 
        redisTemplate.setKeySerializer(new StringRedisSerializer()); 
 
        Student student = (Student) redisTemplate.opsForValue().get("studentKey"); 
 
        if (null == student){ 
            // 使用双重查询+同步代码块解决redis缓存穿透问题 
            synchronized (this){ 
                student = (Student) redisTemplate.opsForValue().get("studentKey"); 
                if (null == student){ 
                    System.out.println("查询数据库。。。。。。。。。。。。。"); 
                    student = studentMapper.selectByPrimaryKey(id); 
                    redisTemplate.opsForValue().set("studentKey",student); 
                }else{ 
                    System.out.println("查询redis。。。。。。。。。。。。。"); 
                } 
            } 
        }else{ 
            System.out.println("查询redis。。。。。。。。。。。。。"); 
        } 
        return student; 
    } 
}

好了,这就是真正的结束了,程序员之路漫漫兮,加油哦!!!


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

如何查看自己电脑CPU的内核数目知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。