缓存击穿

在使用缓存时,我们往往是先根据key从缓存中取数据,如果拿不到就去数据源加载数据,写入缓存。但是在某些高并发的情况下,可能会出现缓存击穿的问题,比如一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

一般解决方案

首先我们想到的解决方案就是加锁,一种办法是:拿到锁的请求,去加载数据,没有拿到锁的请求,就先等待。这种方法虽然避免了并发加载数据,但实际上是将并发的操作串行化,会增加系统延时。

singleflight

singleflight是groupcache这个项目的一部分,groupcache是memcache作者使用golang编写的分布式缓存。singleflight能够使多个并发请求的回源操作中,只有第一个请求会进行回源操作,其他的请求会阻塞等待第一个请求完成操作,直接取其结果,这样可以保证同一时刻只有一个请求在进行回源操作,从而达到防止缓存击穿的效果。下面是参考groupcache源码,使用Java实现的singleflight代码:

  1. //代表正在进行中,或已经结束的请求
  2. public class Call {
  3. private byte[] val;
  4. private CountDownLatch cld;
  5. public byte[] getVal() {
  6. return val;
  7. }
  8. public void setVal(byte[] val) {
  9. this.val = val;
  10. }
  11. public void await() {
  12. try {
  13. this.cld.await();
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. public void lock() {
  19. this.cld = new CountDownLatch(1);
  20. }
  21. public void done() {
  22. this.cld.countDown();
  23. }
  24. }
  1. //singleflight 的主类,管理不同 key 的请求(call)
  2. public class CallManage {
  3. private final Lock lock = new ReentrantLock();
  4. private Map<String, Call> callMap;
  5. public byte[] run(String key, Supplier<byte[]> func) {
  6. this.lock.lock();
  7. if (this.callMap == null) {
  8. this.callMap = new HashMap<>();
  9. }
  10. Call call = this.callMap.get(key);
  11. if (call != null) {
  12. this.lock.unlock();
  13. call.await();
  14. return call.getVal();
  15. }
  16. call = new Call();
  17. call.lock();
  18. this.callMap.put(key, call);
  19. this.lock.unlock();
  20. call.setVal(func.get());
  21. call.done();
  22. this.lock.lock();
  23. this.callMap.remove(key);
  24. this.lock.unlock();
  25. return call.getVal();
  26. }
  27. }

我们使用CountDownLatch来实现多个线程等待一个线程完成操作,CountDownLatch包含一个计数器,初始化时赋值,countDown()可使计数器减一,当count为0时唤醒所有等待的线程,await()可使线程阻塞。我们同样用CountDownLatch来模拟一个10次并发,测试代码如下:

  1. public static void main(String[] args) {
  2. CallManage callManage = new CallManage();
  3. int count = 10;
  4. CountDownLatch cld = new CountDownLatch(count);
  5. for (int i = 0; i < count; i++) {
  6. new Thread(() -> {
  7. try {
  8. cld.await();
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. byte[] value = callManage.run("key", () -> {
  13. System.out.println("func");
  14. return ByteArrayUtil.oToB("bar");
  15. });
  16. System.out.println(ByteArrayUtil.bToO(value).toString());
  17. }).start();
  18. cld.countDown();
  19. }
  20. }

测试结果如下:

  1. func
  2. bar
  3. bar
  4. bar
  5. bar
  6. bar
  7. bar
  8. bar
  9. bar
  10. bar
  11. bar

可以看到回源操作只被执行了一次,其他9次直接取到了第一次操作的结果。

总结

可以看到singleflight可以有效解决高并发情况下的缓存击穿问题,singleflight这种控制机制不仅可以用在缓存击穿的问题上,理论上可以解决各种分层结构的高并发性能问题。

使用singleflight防止缓存击穿(Java)的更多相关文章

  1. 使用Golang的singleflight防止缓存击穿

    背景 在使用缓存时,容易发生缓存击穿. 缓存击穿:一个存在的key,在缓存过期的瞬间,同时有大量的请求过来,造成所有请求都去读dB,这些请求都会击穿到DB,造成瞬时DB请求量大.压力骤增. singl ...

  2. Java Redis缓存穿透/缓存雪崩/缓存击穿,Redis分布式锁实现秒杀,限购等

    package com.example.redisdistlock.controller; import com.example.redisdistlock.util.RedisUtil; impor ...

  3. 【Java面试】怎么防止缓存击穿的问题?

    "怎么防止缓存击穿?" 这是很多一二线大厂面试的时候考察频率较高的问题. 在并发量较高的系统中,缓存可以提升数据查询的性能,还能缓解后端存储系统的并发压力.可谓是屡试不爽的利器. ...

  4. 一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.简述MySQL中索引类型对数据库的性能的影响 2.RDB和AOF机制 3.Redis的过期键的删除策略 4.Redis ...

  5. redis 缓存击穿 看一篇成高手系列3

    什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...

  6. 【Redis】- 缓存击穿

    什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...

  7. 使用BloomFilter布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重

    Bloom Filter是一个占用空间很小.效率很高的随机数据结构,它由一个bit数组和一组Hash算法构成.可用于判断一个元素是否在一个集合中,查询效率很高(1-N,最优能逼近于1). 在很多场景下 ...

  8. 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理

    [原创]分布式之缓存击穿   什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查 ...

  9. redis缓存穿透,缓存击穿,缓存雪崩原因+解决方案

    一.前言 在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是 ...

随机推荐

  1. 免费验证码接收网站&不停开小号方法

    手机号注册:免费验证码接收网站 0.http://getfreesmsnumber.com/ 9个外国网站,访问后要快点点击链接,否则2秒会检测出adblocker 1.http://smsrecei ...

  2. JavaScript学习系列博客_6_JavaScript中的算数运算符

    运算符(操作符) 在JS中 +.-.*./.%这些都是算数运算符,typeof也是一个运算符,它的操作结果就是得到一个描述变量数据类型的字符串. + 运算符 1.两个值在都没有string类型的值的情 ...

  3. 如何寻找决策最优解?熵权TOPSIS助你科学决策

    熵权topsis是一种融合了熵值法与TOPSIS法的综合评价方法.熵值法是一种客观赋值法,可以减少主观赋值带来的偏差:而topsis法是一种常见的多目标决策分析方法,适用于多方案.多对象的对比研究,从 ...

  4. Java数据结构——循环队列

    普通顺序队列存在的问题在普通顺序队列中,入队的操作就是先将尾指针rear右移一个单位,然后将元素值赋值给rear单位.出队时,则是头指针front后移一个单位.像这样进行了一定数量的入队和出队操作后, ...

  5. [PyTorch 学习笔记] 3.1 模型创建步骤与 nn.Module

    本章代码:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson3/module_containers.py 这篇文章来看下 ...

  6. SICP之应用序和正则序

    以一个题目来说明 (define (square x) (* x x)) (define (sum-squares x y) (+ (square x) (square y))) (define (f ...

  7. 力扣Leetcode 55. 跳跃游戏

    跳跃游戏 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 示例 1: 输入: [2,3,1,1,4] 输出: ...

  8. 一张图对比React、Angular、Vue.js

    文章原文地址:https://baijiahao.baidu.com/s?id=1609374985643812253&wfr=spider&for=pc

  9. OpenCV(Open Source Computer Vision Library)计算机视觉库

    OpenCV(最基本的滤波到高级的物体检测皆有涵盖) 简介: OpenCV 是跨平台的,可以在  Windows.Linux.Mac OS.Android.iOS 等操作系统上运行. OpenCV 的 ...

  10. kotlin 作用域函数 : let、run、with、apply、 also、takeIf、takeUnless

    1.官方文档 英文: https://kotlinlang.org/docs/reference/scope-functions.html 中文: https://www.kotlincn.net/d ...