使用singleflight防止缓存击穿(Java)
缓存击穿
在使用缓存时,我们往往是先根据key从缓存中取数据,如果拿不到就去数据源加载数据,写入缓存。但是在某些高并发的情况下,可能会出现缓存击穿的问题,比如一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
一般解决方案
首先我们想到的解决方案就是加锁,一种办法是:拿到锁的请求,去加载数据,没有拿到锁的请求,就先等待。这种方法虽然避免了并发加载数据,但实际上是将并发的操作串行化,会增加系统延时。
singleflight
singleflight是groupcache这个项目的一部分,groupcache是memcache作者使用golang编写的分布式缓存。singleflight能够使多个并发请求的回源操作中,只有第一个请求会进行回源操作,其他的请求会阻塞等待第一个请求完成操作,直接取其结果,这样可以保证同一时刻只有一个请求在进行回源操作,从而达到防止缓存击穿的效果。下面是参考groupcache源码,使用Java实现的singleflight代码:
//代表正在进行中,或已经结束的请求
public class Call {
private byte[] val;
private CountDownLatch cld;
public byte[] getVal() {
return val;
}
public void setVal(byte[] val) {
this.val = val;
}
public void await() {
try {
this.cld.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void lock() {
this.cld = new CountDownLatch(1);
}
public void done() {
this.cld.countDown();
}
}
//singleflight 的主类,管理不同 key 的请求(call)
public class CallManage {
private final Lock lock = new ReentrantLock();
private Map<String, Call> callMap;
public byte[] run(String key, Supplier<byte[]> func) {
this.lock.lock();
if (this.callMap == null) {
this.callMap = new HashMap<>();
}
Call call = this.callMap.get(key);
if (call != null) {
this.lock.unlock();
call.await();
return call.getVal();
}
call = new Call();
call.lock();
this.callMap.put(key, call);
this.lock.unlock();
call.setVal(func.get());
call.done();
this.lock.lock();
this.callMap.remove(key);
this.lock.unlock();
return call.getVal();
}
}
我们使用CountDownLatch来实现多个线程等待一个线程完成操作,CountDownLatch包含一个计数器,初始化时赋值,countDown()可使计数器减一,当count为0时唤醒所有等待的线程,await()可使线程阻塞。我们同样用CountDownLatch来模拟一个10次并发,测试代码如下:
public static void main(String[] args) {
CallManage callManage = new CallManage();
int count = 10;
CountDownLatch cld = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(() -> {
try {
cld.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
byte[] value = callManage.run("key", () -> {
System.out.println("func");
return ByteArrayUtil.oToB("bar");
});
System.out.println(ByteArrayUtil.bToO(value).toString());
}).start();
cld.countDown();
}
}
测试结果如下:
func
bar
bar
bar
bar
bar
bar
bar
bar
bar
bar
可以看到回源操作只被执行了一次,其他9次直接取到了第一次操作的结果。
总结
可以看到singleflight可以有效解决高并发情况下的缓存击穿问题,singleflight这种控制机制不仅可以用在缓存击穿的问题上,理论上可以解决各种分层结构的高并发性能问题。
使用singleflight防止缓存击穿(Java)的更多相关文章
- 使用Golang的singleflight防止缓存击穿
背景 在使用缓存时,容易发生缓存击穿. 缓存击穿:一个存在的key,在缓存过期的瞬间,同时有大量的请求过来,造成所有请求都去读dB,这些请求都会击穿到DB,造成瞬时DB请求量大.压力骤增. singl ...
- Java Redis缓存穿透/缓存雪崩/缓存击穿,Redis分布式锁实现秒杀,限购等
package com.example.redisdistlock.controller; import com.example.redisdistlock.util.RedisUtil; impor ...
- 【Java面试】怎么防止缓存击穿的问题?
"怎么防止缓存击穿?" 这是很多一二线大厂面试的时候考察频率较高的问题. 在并发量较高的系统中,缓存可以提升数据查询的性能,还能缓解后端存储系统的并发压力.可谓是屡试不爽的利器. ...
- 一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)
这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.简述MySQL中索引类型对数据库的性能的影响 2.RDB和AOF机制 3.Redis的过期键的删除策略 4.Redis ...
- redis 缓存击穿 看一篇成高手系列3
什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...
- 【Redis】- 缓存击穿
什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...
- 使用BloomFilter布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重
Bloom Filter是一个占用空间很小.效率很高的随机数据结构,它由一个bit数组和一组Hash算法构成.可用于判断一个元素是否在一个集合中,查询效率很高(1-N,最优能逼近于1). 在很多场景下 ...
- 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理
[原创]分布式之缓存击穿 什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查 ...
- redis缓存穿透,缓存击穿,缓存雪崩原因+解决方案
一.前言 在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是 ...
随机推荐
- Jmeter 常用函数(2)- 详解 __RandomDate
如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 产生一个随机日期 语法格式 ${__R ...
- Jmeter系列(56)- 详解 Weighted Switch Controller 权重控制器
如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html 简单介绍 它能分配其子项目(Child ...
- ftp 搭建 centos 源
1. yum -y install vsftpd* 2. /etc/init.d/vsftpd restart 3. mount CentOS-7-aarch64-Everything-1810 ...
- Spring Security认证流程分析--练气后期
写在前面 在前一篇文章中,我们介绍了如何配置spring security的自定义认证页面,以及前后端分离场景下如何获取spring security的CSRF Token.在这一篇文章中我们将来分析 ...
- Qt QString字符串分割、截取
在做项目中不可避免的会使用到一串字符串中的一段字符,因此常常需要截取字符串. 有两种方式可以解决这个问题: 方法一:QString分割字符串: QString date=dateEdit.toStri ...
- 3个必备cookie实用方法
今天跟大家介绍一下三种cookie的使用方法,selenium提供了我们add_cookie()方法来跳过验证码直接登录的方法.我们现在以博客园登录为例,都知道现在博客园登录要拼图验证. 先在网页打开 ...
- Java 获取一段时间内的每一天
有时候我们会遇到一些业务场景,需要去获取一段时间内的每一天日期 public static List<Date> findDates(Date dBegin, Date dEnd) { L ...
- app转iap
ios打包ipa的四种实用方法(.app转.ipa) http://blog.csdn.net/oiken/article/details/49535369 手动压缩改后缀方式 这种方式与4.1的方法 ...
- echarts 画折线的一些需要去改动的地方
1.客户想要去要制定特定线条的样式(比如:颜色) 2.要去自定义改变后端传 的数值不合理的地方,在tooltiop中去展示出来 后续持更.....
- mac android 真机调试
1.已经安装好Androidstudio或者eclipse 2.下载配置好Android Sdk等 3.将android手机通过USB数据线连接Mac,打开终端输入system_profiler SP ...