通过双 key 来解决缓存并发问题
我们在使用缓存的时候,不管Redis或者是Memcached,基本上都会遇到以下3个问题:缓存穿透、缓存并发、缓存集中失效。这篇文章主要针对【缓存并发】问题展开讨论,并给出具体的解决方案。
1.什么是缓存并发?
在高并发的访问下,当某个缓存处于过期失效的时间点时,极有可能出现多个进程同时查询该缓存(该缓存是业务场景中非常 "热点" 的数据,比如首页的缓存数据)。因为查询DB并重新缓存需要一定的时间,而瞬时并发非常高,如果此时缓存失效了,这些并发请求都会直接访问DB,从而导致DB服务器的CPU或者内存负载过高,服务能力下降甚至宕机,此问题即缓存并发问题。
缓存并发问题在微服务架构下凸显更加严重,比如某个基础服务A因为上述问题出现不可用,进而导致依赖A服务的B、C服务也不可用,而B服务的不可用又导致服务E、F不可用,不可用的服务就像滚雪球一样越滚越大,最终导致系统出现严重故障,此现象我们称之为雪崩效应。
注意缓存并发和缓存集中失效的区别在于:缓存并发指的是某一个热点key的失效,而缓存集中失效则是一批key同时失效,两者都可能导致雪崩问题。
2.如何解决?
针对该问题,存在以下三种解决方案:
加锁:在缓存失效后,通过加锁的方式只允许一个线程查询数据和写缓存,其他线程如果发现有锁就等待,等解锁后再返回数据。该方案会造成部分请求等待。
二级缓存:A1为原始缓存,A2为拷贝缓存。A1失效时,可以访问A2,其中A1的缓存失效时间设置为短期(比如5min),A2的缓存失效时间设置为长期(比如1天)。如果缓存value很大,此方案的缓存空间利用率低。
双key:思路和方案2类似,不同的是双key分别缓存过期时间(key-time)和缓存数据(key-data),其中(key-time)的缓存失效时间设置为短期(比如5min),(key-data)的缓存失效时间设置为长期(比如1天)。当第一个线程发现 key-time 过期不存在时,则先更新key-time,然后去查询数据库并更新key-data 的值;当其他线程来获取数据时,虽然第一个线程还没有从数据库查询完毕并更新缓存,但发现key-time存在,会直接读取缓存的旧数据返回。和二级缓存的方案对比,该方案的缓存空间利用率高。
3.双key方案的示例代码
1. 写缓存的示例代码
1public static boolean set(String key, String value, int seconds) {
2 Jedis jedis = null;
3 try {
4 jedis = jedisPool.getResource();
5 if (seconds > 0){
6 // 添加数据缓存,缓存有效时间 = 真实时间 + 1 天
7 jedis.set(key, seconds + 60 * 60 * 24, value);
8
9 // 添加过期时间缓存,缓存有效时间 = 真实时间
10 jedis.set("lock_" + key, seconds, System.currentTimeMillis() + "");
11 } else {
12 jedis.set(key, value);
13 jedis.set("lock_" + key, System.currentTimeMillis() + "");
14 }
15
16 return true;
17 } catch (JedisException e) {
18 if (jedis != null) {
19 returnBrokenResource(jedis);
20 jedis = null;
21 }
22 throw e;
23 } finally {
24 if (jedis != null) {
25 returnResource(jedis);
26 }
27 }
28}
2. 读缓存的示例代码
1public static String get(String key) {
2 Jedis jedis = null;
3 try {
4 jedis = jedisPool.getResource();
5
6 // 缓存过期 && 获取锁成功,setnx:原子操作
7 if (jedis.setnx("lock_" + key, System.currentTimeMillis() + "") == 1) {
8 /**
9 * 将锁的失效时间设为60s,在60s内若查询数据库成功,则更新锁的失效时间=缓存时间
10 * 如果60s内出现异常,则60s后第一个请求又会去访问数据库
11 * 返回null表示没有查询到数据库,外层代码会通过数据库获取数据并设置缓存
12 */
13 jedis.expire("lock_" + key, 60);
14 return null;
15 } else{
16 // 缓存未过期或者缓存过期但获取锁失败, 则返回旧数据
17 return jedis.get(key);
18 }
19 } catch (JedisException e) {
20 if (jedis != null) {
21 returnBrokenResource(jedis);
22 jedis = null;
23 }
24 throw e;
25 } finally {
26 if (jedis != null) {
27 returnResource(jedis);
28 }
29 }
30}
通过双 key 来解决缓存并发问题的更多相关文章
- 如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
缓存雪崩 数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 比如一个雪崩的简单过程: 1.redis集群大面积故障 2.缓存失 ...
- redis的缓存穿透 缓存并发 缓存失效
我们在用缓存的时候,不管是Redis或者Memcached,基本上会通用遇到以下三个问题: 缓存穿透 缓存并发 缓存失效 一.缓存穿透 Paste_Image.png Paste_Image.png ...
- 每一个程序员都应该知道的高并发处理技巧、创业公司如何解决高并发问题、互联网高并发问题解决思路、caoz大神多年经验总结分享
本文来源于caoz梦呓公众号高并发专辑,以图形化.松耦合的方式,对互联网高并发问题做了详细解读与分析,"技术在短期内被高估,而在长期中又被低估",而不同的场景和人员成本又导致了巨头 ...
- <转>浅谈缓存击穿、缓存并发和缓存失效
原文地址:缓存穿透.缓存并发.缓存失效之思路变迁 我们在用缓存的时候,不管是Redis或者Memcached,基本上会通用遇到以下三个问题: 缓存穿透 缓存并发 缓存失效 一.缓存穿透 注 ...
- asp.net怎样解决高并发问题
队列+多线程+couchbase缓存 ,解决高并发问题. using System; using System.Collections.Generic; using System.Linq; usin ...
- 大型网站技术架构(四)--核心架构要素 开启mac上印象笔记的代码块 大型网站技术架构(三)--架构模式 JDK8 stream toMap() java.lang.IllegalStateException: Duplicate key异常解决(key重复)
大型网站技术架构(四)--核心架构要素 作者:13GitHub:https://github.com/ZHENFENG13版权声明:本文为原创文章,未经允许不得转载.此篇已收录至<大型网站技 ...
- Java模拟并解决缓存穿透
什么叫做缓存穿透 缓存穿透只会发生在高并发的时候,就是当有10000个并发进行查询数据的时候,我们一般都会先去redis里面查询进行数据,但是如果redis里面没有这个数据的时候,那么这10000个并 ...
- Redis: 缓存过期、缓存雪崩、缓存穿透、缓存击穿(热点)、缓存并发(热点)、多级缓存、布隆过滤器
Redis: 缓存过期.缓存雪崩.缓存穿透.缓存击穿(热点).缓存并发(热点).多级缓存.布隆过滤器 2019年08月18日 16:34:24 hanchao5272 阅读数 1026更多 分类专栏: ...
- java解决高并发问题
对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研 ...
- asp.net解决高并发的方案.
asp.net解决高并发的方案. Posted on 2012-11-27 22:31 75077027 阅读(3964) 评论(1) 编辑 收藏 最近几天一直在读代震军的博客,他是 Discuz!N ...
随机推荐
- VS2022 17.12.0 Preview2版本对Copilot的功能增强
前提条件,使用最新版的17.12.0 Preview2,并且有有效的Copilot AI订阅,那么可以体验这个新鲜好用的功能 增强了Copilot AI对IEnumerable Visualizer的 ...
- angular cli, vs code liveserver, vs 2019 iis express 10, vs code kestrel 使用 https + ip
更新: 2022-03-20 修订版: Vs Code, Visual Studio 2022, Angular and Live Server Running Through Https and I ...
- JAVAEE——JDK安装
1.JDK下载 (1)jdk官网 1.输入下载地址:Oracle Software Downloads | Oracle 2.向下滑动,找到Developer Downloads后点击java 3.点 ...
- QT框架中的缓存:为什么有QHash和QMap,还设计了QCache和QContiguousCache?
简介 本文介绍了QT框架中可用于缓存的几个数据类型各自的特点:通过本文读者可以了解到为什么有QHash和QMap,还设计了QCache和QContiguousCache? 目录 QHash和QMap有 ...
- 字节跳动的多平台绽放秘诀 | Flutter 开发者故事
字节跳动旗下运营着一系列成功的用户产品.企业应用以及服务,覆盖信息.教育.娱乐等不同领域.随着产品阵容的不断发展,传统的原生双平台开发已经难以满足团队更高效.更灵活.更精美,以及更多样的产品研发需求. ...
- 反DDD模式之“复用”
本文书接上回<反DDD模式之关系型数据库>,关注公众号(老肖想当外语大佬)获取信息: 最新文章更新: DDD框架源码(.NET.Java双平台): 加群畅聊,建模分析.技术实现交流: 视频 ...
- USB TCPM
USB TCPM(Type-C Port Manager)的主要作用是管理 USB Type-C 端口的连接和电源传输协议(USB Power Delivery, PD),确保设备正确识别.协商和切换 ...
- kotlin类与对象——>扩展
1,扩展概念(OC中早期就有此功能) Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式.这通过叫做 扩展 的特殊声明完成.例如,你可以为一个你不能修改的.来自第三方库中 ...
- 报名开启|QKE 容器引擎托管版暨容器生态发布会!
当下,"云原生"技术红利正吞噬旧秩序,重塑新世界. 但您的企业是否依然困惑:缺少运维人员或运维团队,想要专注于业务的开发,又不得不兼顾集群的日常运维:在生产环境中,为了保证业务的高 ...
- Java新特性-stream流
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据. Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达 ...