缓存击穿

在使用缓存时,我们往往是先根据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)的更多相关文章

  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. Jmeter 常用函数(24)- 详解 __digest

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.htm 作用 在特定的哈希算法中返回加密的值,并带有可 ...

  2. JavaScript学习系列博客_28_JavaScript 数组去重

    数组去重 var arr = [1,2,3,2,2,1,3,4,2,5]; //去除数组中重复的数字 //获取数组中的每一个元素 for(var i=0 ; i<arr.length ; i++ ...

  3. mysql高级内容学习总结

    创建索引 create [unique] index indexname on tablename(columnname(length)) alter tablename add [unique] i ...

  4. ZK的watch机制

    1.watcher原理框架 由图看出,zk的watcher由客户端,客户端WatchManager,zk服务器组成.整个过程涉及了消息通信及数据存储. zk客户端向zk服务器注册watcher的同时, ...

  5. Java后台服务慢优化杂谈

    Java后台服务慢优化杂谈 前言 你是否遇到过这样的场景,当我们点击页面某个按钮后,页面一直loading,要等待好几分钟才出结果的画面,有时直接502或504,作为一个后台开发,看到自己开发的系统是 ...

  6. python sqlite3简单操作

    python sqlite3简单操作(原创)import sqlite3class CsqliteTable: def __init__(self): pass def linkSqlite3(sel ...

  7. windows环境安装vue-cli及webpack并创建vueJs项目

    1. 安装node.js 2. 如果安装的是旧版本的 npm,可以通过 npm 命令来进行版本升级,命令如下: npm install npm -g npm网站服务器位于国外,所以经常下载缓慢或出现异 ...

  8. Typed Lua

    https://the-ravi-programming-language.readthedocs.io/en/latest/ravi-overview.html https://github.com ...

  9. Mac上如何降级Java版本

    升级到了Java9,有些工具就不工作了.因此要降级到Java8.方法: /Library/Java/JavaVirtualMachines/下的高版本SDK即可

  10. 文件操作 -- 生成java文件

    import hashlibimport os def genJavaFile(packageName, soFile):    className, suffix = soFile.split('. ...