乐趣区

关于redis:redis缓存常见问题场景总结

在应用 redis 缓存时,咱们大略都听过缓存击穿、缓存雪崩之类的场景和计划,这也是个别常见面试题的内容。但在这些年的理论开发中,确实亲身经历了这类场景之后,对这类场景和解决方案就更加粗浅,这类再对立总结一下。

当然计划不是惟一的,后续如果我用到更好的计划,仍然会基于本文档补充。

1. 缓存击穿

1.1. 定义

缓存击穿,也就是说当 redis 缓存中有一个 key 是大量申请同时拜访的热点数据,如果忽然这个 key 工夫到了,那么大量的申请在缓存中获取不到该 key,穿过缓存间接来到数据库导致数据库解体,这样因为单个 key 生效而穿过缓存到数据库称为缓存击穿。

1.2. 计划

1.2.1. 计划一:被动更新 key

这里问题在于 key 是被动刷新的,即 key 过期后主动删除,当流量申请进来了才去读 db 更新缓存。如果场景容许,咱们能够被动控制性的刷新 key。key 仍然能够设置过期工夫,但咱们能够通过后盾 job 等形式,从 db 拿最新的值更新 key 对应的 value,更新的同时天然对 key 进行续期。

这样以达到能定时更新 redis 值,又保障 redis key 永远不生效,突发大流量也走不到 db。

1.2.2. 计划二:更新缓存加锁

缓存击穿的场景在压测中常常可见,当没有缓存预热时,redis 缓存 key 一开始不存在。假如 jmeter 100 个线程并发拜访时,都发现缓存 key 不存在,100 个申请都会去查问 db,而后更新 redis 值。

其实咱们在发现 key 不存在之后,能够对于“查 db 更新 redis 缓存”这个过程加上分布式锁,保障只有有一个申请去读 db、更新缓存。其余的申请在期待锁开释后,再去查下 redis 缓存。此时缓存有值就间接拜访缓存。

2. 缓存雪崩

2.1. 定义

当大量申请在拜访都会先从缓存查问,如果此时大部分缓存同时过期生效,那么这些申请都查问不到缓存,此时他们会全副将申请到数据库,当申请数量足够大时此时将会把数据库压垮,这就是缓存雪崩。比方:在凌晨十二点搞促销,大概有 10000 个用户发动申请,此时缓存过期,则这 10000 个申请间接打到数据库上,把数据库压垮,即便重启数据库申请仍然会打到数据库上。

这里和“缓存击穿”不同的中央在于:

  • 缓存击穿:单个 key 过期,单个 key 的大量申请进来。
  • 缓存雪崩:多个 key 同时过期,多个 key 的大量申请进来。

2.2. 计划

2.2.1. 计划一:过期工夫扩散

问题在于 key 同时过期。首先,如果业务上 key 的生成工夫不是同时的,那么过期工夫也就不是同时的,这类问题能够防止。

如果 key 的生成工夫是同时的,例如:缓存是通过 job 或其余触发条件批量一起生成的,那么在定义每个 key 的过期工夫时,能够基于原定的工夫再加上一个随机时间段,以保障最终的过期工夫不统一。

2.2.2. 计划二:集群架构

redis 的集群架构中,可依据写入命令依照不同 slot,调配在不同 master 上。因为这些 key 是不同的,针对缓存雪崩场景,写入的申请就能够调配在不同 master 节点上,能缓解一部分压力。

当然,成果无限,但至多比单体架构抗压强。

2.2.3. 计划三:降级限流

可评估数据库能承受的最大申请量,做限流。那么被限住的申请就要做服务降级,可在队列中期待异步更新 redis 值。

3. 缓存穿透

3.1. 定义

指当申请查问缓存和数据库都不存在的数据时,先查问缓存为空,再查询数据库仍然为空,向申请返回空,如果大量申请同时拜访这些不存在 key 那么这些申请仍然会造成压垮数据库的景象,这种通常是歹意查问和被攻打几率较大。

缓存穿透 缓存击穿 名字听起来很像,但不是一回事。 缓存穿透 是针对缓存中不存在的 key。而 缓存击穿 是本来存在某个缓存 key,等生效后忽然大批量拜访这个 key。

3.2. 计划

3.2.1. 缓存 null 值

当拜访一些不存在的 key 时,因为在 db 中查到的值为 null,就不会缓存下来,所以下次访问仍然会走 db。

那么当在 db 中查到的值为 null 时,咱们罗唆就创立一个缓存 key,存的值就为 null 等。当下次访问的时候,就会走缓存中取,而不必走 db。

然而这个计划有局限性,得看具体场景。假如第一次拜访的时候不存在,咱们缓存了一个 null 的 key,很快 db 中对应的 key 就有值了,可咱们拜访时仍然是从缓存中获取了 null。

有两种改良策略:

  1. 能够针对 null 的值,咱们的过期工夫能够设短一点。(上策)
  2. 当 db 中值新建、更新时,可能被动革除对应的缓存 key。(上策)

3.2.2. 布隆过滤器

详见《布隆过滤器 与 Redis BitMap》

4. 热点 key(缓存击穿)

4.1. 定义

这里 (缓存击穿),是因为和缓存击穿场景有点像。后面说的缓存击穿,单指 key 过期,大流量击穿 db。而这里不必等到 key 过期,间接更大的流量,击穿 redis,再击穿 db。

热点 key,就是霎时有大量的申请去拜访 redis 上某个固定的 key。例如一些热搜词条:IG 夺冠、梅西夺冠,一瞬间会有大量的用户申请都拜访固定的词条,如果这些词条内容存在 redis 中,那么拜访的就是某个固定的 key。

咱们晓得,就算是 redis 的集群机构,针对某个固定的 key,也是被调配某个固定的哈希槽上,对应 redis 某单个节点。而 redis 单个节点的性能无限,此时就容易被击溃,带来的危害有:

1. 流量集中,达到物理网卡下限

当某一热点 Key 的申请在某一节点所在的主机上超过该主机网卡流量下限时,因为流量的适度集中,会导致该节点的服务器中其它服务无奈进行

2. 申请过多,缓存分片服务被打垮

Redis 单点查问性能是无限的,单节点 QPS 差不多也就几万,当热点 key 的查问超过 Redis 节点的性能阈值时,申请会占用大量的 CPU 资源,影响其余申请并导致整体性能升高;重大时会导致缓存分片服务被打垮,表现形式之一就是 Redis 节点自重启,此时该节点存储的所有 key 的查问都是不可用状态,会把影响辐射到其余业务上。

3. 集群架构下,产生拜访歪斜

即某个数据分片被大量拜访,而其余数据分片处于闲暇状态,可能引起该数据分片的连接数被耗尽,新的连贯建设申请被回绝等问题。

4. DB 击穿,引起业务雪崩

热 Key 的申请压力数量超出 Redis 的承受能力易造成缓存击穿,当缓存挂掉时,此时再有申请产生,可能间接把大量申请间接打到 DB 层上,因为 DB 层绝对缓存层查问性能更弱,在面临大申请时很容易产生 DB 雪崩景象,重大影响业务。

4.2. 辨认热点 key

4.2.1. 业务教训评估

比方热搜关键词、秒杀商品的词条等等,可能提前辨认到可能是热点 key 的,就提前做筹备。

4.2.2. 业务侧监控

在操作 redis 之前,退出一行代码进行数据统计,异步上报行为;如相似日志采集,将单次 redis 命令的操作 / 后果 / 耗时等统计,异步音讯发送给采集音讯队列,毛病就是对代码造成入侵,个别能够交给中间件加在本人包的 redis 二方包中;如果有做的好一点的 Daas 平台,能够在 proxy 层做监控,业务无需感知,对立在 Daas 平台查看 redis 监控。

4.2.3. redis 服务器端监控

redis 自带一些监控命令,可由运维侧基于 redis 的命令,提供一些监控平台。

1. monitor 命令

该命令能够实时抓取出 redis 服务器接管到的命令,而后写代码统计出热 key 是啥;当然,也有现成的剖析工具能够给你应用,比方 redis-faina;然而该命令在高并发的条件下,有内存增暴增的隐患,还会升高 redis 的性能。

2. hotkeys 参数

redis 4.0.3 提供了 redis-cli 的热点 key 发现性能,执行 redis-cli 时加上–hotkeys 选项即可;然而该参数在执行的时候,如果 key 比拟多,执行起来比较慢。

4.3. 计划

4.3.1. 二级缓存

针对热点 key 做二级缓存,将流量摊派到 java 各个节点的内存中,也缩小了网络带宽。

当然,二级缓存带来的毛病就是难保护缓存的一致性。不过这也算是针对热点 key 场景下的一种降级策略。

4.3.2. key 正本拆分

能够将某个 key 的内容复制成多个 key,如:xxkey_01、xxkey_02、xxkey_03…,这样这些 key 就会扩散在不同的哈希槽中,不同的 redis 节点上。

在 java 代码每次申请 redis key 时,能够通过轮询等形式,拜访不同 key 中的数据。就将流量平摊到不同的 redis 节点中。

后面是设置编号,拜访时随机拜访其中一个 key。还能够思考依照用户群体、拜访场景等维度拆分,拆分生成不同的 key,同样的思路,也是能够将流量拆离开。

4.3.3. 热点 key redis 集群隔离

如果财力容许,为了避免热点 key 引发问题时,外围业务不受影响,该当提前做好外围 / 非核心业务的 Redis 的隔离,至多热点 key 存在的 redis 集群该当与外围业务隔离开来。

4.4. 有赞 TMC 框架履行

有赞专门设计了一套框架解决热点 key 的问题,具体内容请看原文《有赞通明多级缓存解决方案(TMC)设计思路》。上面做简略阐明。

TMC 本地缓存整体构造分为如下模块:

  • Jedis-Client:Java 利用与缓存服务端交互的间接入口,接口定义与原生 Jedis-Client 无异;
  • Hermes-SDK:自研“热点发现 + 本地缓存”性能的 SDK 封装,Jedis-Client 通过与它交互来集成相应能力;
  • Hermes 服务端集群:接管 Hermes-SDK 上报的缓存拜访数据,进行热点探测,将热点 key 推送给 Hermes-SDK 做本地缓存;
  • 缓存集群:由代理层和存储层组成,为利用客户端提供对立的分布式缓存服务入口;
  • 根底组件:etcd 集群、Apollo 配置核心,为 TMC 提供“集群推送”和“对立配置”能力;

1. 监控

有赞改写了 jedis 原生的 jar 包,退出了 Hermes-SDK 包,目标就是做热点发现和本地缓存;
从监控的角度看,该包对于 Jedis-Client 的每次 key 值拜访申请,Hermes-SDK 都会通过其通信模块将 key 拜访事件异步上报给 Hermes 服务端集群,以便其依据上报数据进行“热点探测”。

2. 热 key 解决计划

在解决热 key 计划上,有赞用的是二级缓存。

有赞在监控到热 key 后,Hermes 服务端集群会通过各种伎俩告诉各业务零碎里的 Hermes-SDK,通知他们这个 key 是热 key,要做本地缓存。于是 Hermes-SDK 就会将该 key 缓存在本地,对于前面的申请;Hermes-SDK 发现这个是一个热 key,间接从本地中拿,而不会去拜访集群;

3. 二级缓存一致性计划

Hermes-SDK 的热点模块仅缓存热点 key 数据,绝大多数非热点 key 数据由缓存集群存储;

热点 key 变更导致 value 生效时,Hermes-SDK 通过 etcd 集群播送事件,异步生效业务利用集群中其余节点的本地缓存,保障集群最终统一。

退出移动版