【Redis】热Key/大Key问题、缓存击穿、缓存穿透、缓存雪崩、缓存与数据库一致性问题
【Redis】热Key/大Key问题、缓存击穿、缓存穿透、缓存雪崩、缓存与数据库一致性问题
- 1、什么是热Key问题,如何解决热key问题?
- 1.1 热key的定义?
- 1.2 如何识别热Key?
- 1.3 如何解决热Key?
- 2、什么是大Key问题,如何解决?
- 2.1 大Key的定义
- 2.2 如何处理大Key
- 3、什么是缓存击穿、缓存穿透、缓存雪崩?
- 4、如何解决Redis和数据库的一致性问题?
- 4.1 为什么删缓存而不是更新?
- 4.2 先写数据库还是先删缓存?
- 4.2.1 先删缓存再写数据库
- 4.2.2 先写数据库再删缓存
- 4.2.3 缓存双删
1、什么是热Key问题,如何解决热key问题?
当使用Redis作为存储时,如果发生一些特殊情况,比如明星官宣的突发事件,世界杯等重大活动,双十一的活动秒杀等等,就会出现特别大的流量,并且会导致某些热词、商品等被频繁的查询和访问。
如果在同一个时间点上,Redis中的同一个key被大量访问,就会导致流量过于集中,使得很多物理资源无法支撑,如网络带宽、物理存储空间、数据库连接等。
这也是为什么某某明星官宣之后,微博上面就会出现宕机的情况。有时候这种宕机发生后,其他功能都是可以使用的,只是和这个热点有关的内容会无法访问,这其实就和热点数据有关系了。
对于热key的处理,主要在于事前预测和事中解决。
对于事前预测就是根据一些根据经验,提前的识别出可能成为热key的Key,比如大促秒杀活动等。
在事中解决方面,主要可以考虑,热点key拆分、多级缓存、热key备份、限流等方案来解决。
1.1 热key的定义?
热key的定义,通常以其接收到的Key被请求频率来判定,例如:
● QPS集中在特定的Key:Redis实例的总QPS为10,000,而其中一个Key的每秒访问量达到了7,000。那么这个key就算热key了。
● 带宽使用率集中在特定的Key:对一个拥有1000个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
● CPU使用时间占比集中在特定的Key:对一个拥有10000个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。
1.2 如何识别热Key?
1、根据经验,提前预测
这种方法在大多数情况下还是比较奏效的。比较常见的就是电商系统中,会在做秒杀、抢购等业务开始前就能预测出热key。
但是,这种方式的局限性也很大,就是有些热key是完全没办法预测的,比如明星什么时候要官宣这种事情就无法预测。
2、实时收集
还有一种热点数据的发现机制,那就是实时的做收集,比如在客户端、服务端或者在代理层,都可以对实时数据进行采集,然后进行统计汇总。
达到一定的数量之后,就会被识别为热key。
具体的收集方式也有很多种,可以在客户端进行收集、也可以在统一代理层进行收集、还可以通过redis的自带命令进行收集。redis 4.0.3中提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可。
1.3 如何解决热Key?
1、多级缓存
解决热key问题最主要的方式就是加缓存。通过缓存的方式尽量减少系统交互,使得用户请求可以提前返回。
这样即能提升用户体验,也能减少系统压力。
缓存的方式有很多,有些数据可以缓存在客户的客户端浏览器中,有些数据可以缓存在距离用户就近的CDN中,有些数据可以通过Redis等这类缓存框架进行缓存,还有些数据可以通过服务器本地缓存进行。
这种使用多个缓存的情况,就组成了二级缓存、三级缓存等多级缓存了。总之,通过缓存的方式尽量减少用户的访问链路的长度。
2、热key拆分
将一个热key拆分成多个key,在每一个Key后面加一个后缀名,然后把这些key分散到多个实例中。
这样在客户端请求的时候,可以根据一定的规则计算得出一个固定的Key,这样多次请求就会被分散到不同的节点上了。
比如 <淄博烧烤> 是个热点key,
把他拆分成淄博烧烤_0001、淄博烧烤_0002、淄博烧烤_0003、淄博烧烤_0004,然后把它们分别存储在cluster中的不同节点上,这样用户在查询<淄博烧烤>的时候,先根据用户ID算出一个下标,然后就访问其中一个节点就行了
有人问了,这不是意味着一个用户只能拿到部分数据了吗?确实是,但是有时候我们并不一定就需要全部的数据。
比如说,同样的两个用户在刷抖音,都想看<淄博烧烤>这个热点相关的视频,但是我们并不一定要给所有用户都推送同样的内容,完全可以把这个词条下面的无数个视频分散存储在不同的节点上,然后给不同的用户推送在不同的节点上的数据就行了。
然后在这个热点key没那么热了之后,再把数据做一下汇总,挑选出一下好的视频在重新推送给没推送到的用户就行了
2、什么是大Key问题,如何解决?
Big Key是Redis中存储了大量数据的Key,不要误以为big key只是表示Key的值很大,他还包括这个Key对应的value占用空间很多的情况,通常在String、list、hash、set、zset等类型中出现的问题比较多。其中String类型就是字符串的值比较大,而其他几个类型就是其中元素过多的情况。
Redis的Big Key可能存在以下几个危害:
1、影响性能:由于big key的values占用的内存会很大,所以读取它们的速度会很慢,会影响系统的性能。
2、占用内存: 大量的big key也会占满Redis的内存,让Redis无法继续存储新的数据,而且也会导致Redis卡住
3、内存空间不均匀:比如在 Redis 集群中,可能会因为某个节点上存储了Big Key,导致多个节点之间内存使用不均匀。
4、影响Redis备份和恢复:如果从RDB文件中恢复全量数据时,可能需要大量的时间,甚至无法正常恢复。
5、搜索困难:由于大key可能非常大,因此搜索key内容时非常困难,并且可能需要花费较长的时间完成搜索任务。
6、迁移困难:大对象的迁移和复制压力较大,极易破坏缓存的一致性
7、过期执行耗时:如果 Bigkey 设置了过期时间,当过期后,这个 key 会被删除,而大key的删除过程也比较耗时
2.1 大Key的定义
Redis中多大的key算作大key并没有一个固定的标准,因为这主要取决于具体的场景和应用需求。一般来说,如果一个key的value比较大,占用的内存比较多,或者某个key包含的元素数量比较多,这些都可以被认为是大key。
通常情况下,建议不要超过以下设定,超过这些数量就可能会影响Redis的性能。
● 对于 String 类型的 Value 值,值超过 5MB(腾讯云定义是10M,阿里云定义是5M)。
● 对于 Set 类型的 Value 值,含有的成员数量为 10000 个(成员数量多)。
● 对于 List 类型的 Value 值,含有的成员数量为 10000 个(成员数量多)。
● 对于 Hash 格式的 Value 值,含有的成员数量 1000 个,但所有成员变量的总 Value 值大小为 100MB(成员总的体积过大)。
但是,这些并不是绝对的限制,而是一个经验值,具体的情况还需要根据应用场景和实际情况进行调整。
2.2 如何处理大Key
1、拆分大Key
在业务代码中,将一个big key有意的进行拆分,比如根据日期或者用户尾号之类的进行拆分。使用小键替代大键可以有效减小存储空间。
2、通过合理的设置缓存TTL,避免过期缓存不及时删除而增大key大小。
3、将大键转移存放在数据库中。
3、什么是缓存击穿、缓存穿透、缓存雪崩?
1、缓存击穿
是指当某一key的缓存过期时,大并发量的请求同时访问此key,瞬间击穿缓存服务器直接访问数据库,让数据库处于负载的情况。
解决方式有2个:一个是访问数据库之前加互斥锁,保证获取锁的线程才能访问数据库,否则需要阻塞。第二个是异步任务定时更新缓存,在缓存失效前进行更新。
2、缓存穿透
是指缓存服务器中没有缓存数据,数据库中也没有符合条件的数据,导致业务系统每次都绕过缓存服务器查询下游的数据库,缓存服务器完全失去了其应有的作用。
解决方式有2个:一个是缓存空值,缓存中查到的是空值而不是null,说明之前已经查过了,就不会再查数据库了。第二个是使用布隆过滤器(Bloom Filter),可以判断出哪些值是一定不存在的。
3、缓存雪崩
是指当大量缓存同时过期或缓存服务宕机,所有请求的都直接访问数据库,造成数据库高负载,影响性能,甚至数据库宕机。
解决方式可以设置不同的过期时间,可以把不同的key过期时间设置成不同的, 并且通过定时刷新的方式更新过期时间。
4、如何解决Redis和数据库的一致性问题?
为了保证Redis和数据库的数据一致性,肯定是要缓存和数据库双写了。
更新数据的常见方案有3个:
1、先更新数据库, 再删除缓存。
2、延迟双删:先删除缓存,再更新数据库,再删除一次缓存
3、cache-aside:更新数据库,基于 binlog 监听进行缓存删除
读取数据的常见方案:
1、查询缓存,如果缓存中有值,则直接返回
2、查询数据库
3、把数据库的查询结果更新到缓存中
4.1 为什么删缓存而不是更新?
为了保证数据库和缓存里面的数据是一致的,很多人会在做数据更新的时候,会同时更新缓存里面的内容。但是应该优先选择删除缓存而不是更新缓存。
1、更新缓存操作更复杂
我们放到缓存中的数据,很多时候可能不只是简单的一个字符串类型的值,他还可能是一个大的JSON串,一个map类型等等。
举个例子,我们需要通过缓存进行扣减库存的时候,你可能需要从缓存中查出整个订单模型数据,把他进行反序列化之后,再解析出其中的库存字段,把他修改掉,然后再序列化,最后再更新到缓存中。
可以看到,更新缓存的动作,相比于直接删除缓存,操作过程比较的复杂,而且也容易出错。
2、并发场景下更新操作会出现不一致问题
在"写写并发"的场景中,如果同时更新缓存和数据库,那么很容易会出现因为并发的问题导致数据不一致的情况。如:
先写数据库,再更新缓存:
先更新缓存,后写数据库:
但是,如果是做缓存的删除的话,在写写并发的情况下,缓存中的数据都是要被清除的,所以就不会出现数据不一致的问题。
4.2 先写数据库还是先删缓存?
4.2.1 先删缓存再写数据库
先删缓存再写数据库,会导致读写并发时数据不一致的问题。
也就是说,假如一个读线程,在读缓存的时候没查到值,他就会去数据库中查询,但是如果自查询到结果之后,更新缓存之前,数据库被更新了,但是这个读线程是完全不知道的,那么就导致最终缓存会被重新用一个"旧值"覆盖掉。
这也就导致了缓存和数据库的不一致的现象。
4.2.2 先写数据库再删缓存
如果我们先更新数据库,再删除缓存,有一个好处,那就是缓存删除失败的概率还是比较低的,除非是网络问题或者缓存服务器宕机的问题,否则大部分情况都是可以成功的。
并且这个方案还有一个好处,那就是数据库是作为持久层存储的,先更新数据库就能确保数据先写入持久层可以保证数据的可靠性和一致性,即使在删除缓存失败的情况下,数据库中已有最新数据。
但是这个方案也存在一个问题,那就是先写数据库,后删除缓存,如果第二步失败了,会导致数据库中的数据已经更新,但是缓存还是旧数据,导致数据不一致。
4.2.3 缓存双删
为了解决4.2.2中,写数据库成功,删除缓存失败,导致的数据不一致的问题。可以使用缓存双删:
1、先删除缓存
2、更新数据库
3、再删除缓存
面试题:
一、什么是热Key问题,如何解决热key问题?
同一个时间点上,Redis中的同一个key被大量访问,这样的Key可以成为热Key。
解决热Key的方式:
1、根据经验,提前预测。对可能出现的热Key做方案。
2、多级缓存。增加本地缓存,对热点数据提前返回。
3、热Key拆分,不同节点保存一部分数据,最后汇总或者降级只返回一部分数据。
二、什么是大Key问题,如何解决?
一个Key的值很大,或者Key对应的value占用空间很多,则称为大Key。
大Key的定义:对于 String 类型的 Value 值,值超过 5MB;对于 Set 类型或者List类型的 Value 值,含有的成员数量为 10000 个。
如何解决:
1、拆分大Key:在业务代码中,将一个big key有意的进行拆分,比如根据日期或者用户尾号之类的进行拆分。使用小键替代大键可以有效减小存储空间。
2、通过合理的设置缓存TTL,避免过期缓存不及时删除而增大key大小。
3、将大键转移存放在数据库中。
三、什么是缓存击穿、缓存穿透、缓存雪崩?
1、缓存击穿
是指当某一key的缓存过期时,大并发量的请求同时访问此key,瞬间击穿缓存服务器直接访问数据库,让数据库处于负载的情况。
解决方式有2个:一个是访问数据库之前加互斥锁,保证获取锁的线程才能访问数据库,否则需要阻塞。第二个是异步任务定时更新缓存,在缓存失效前进行更新。
2、缓存穿透
是指缓存服务器中没有缓存数据,数据库中也没有符合条件的数据,导致业务系统每次都绕过缓存服务器查询下游的数据库,缓存服务器完全失去了其应有的作用。
解决方式有2个:一个是缓存空值,缓存中查到的是空值而不是null,说明之前已经查过了,就不会再查数据库了。第二个是使用布隆过滤器(Bloom Filter),可以判断出哪些值是一定不存在的。
3、缓存雪崩
是指当大量缓存同时过期或缓存服务宕机,所有请求的都直接访问数据库,造成数据库高负载,影响性能,甚至数据库宕机。
解决方式可以设置不同的过期时间,可以把不同的key过期时间设置成不同的, 并且通过定时刷新的方式更新过期时间。
参考链接:
1、https://www.yuque.com/hollis666/wk6won/lysd3t
2、https://www.yuque.com/hollis666/wk6won/abfis3
3、https://www.yuque.com/hollis666/wk6won/tmcgo0
4、https://www.yuque.com/hollis666/wk6won/uswtlzlot2lcvy10