一、温故而知新~
1、Redis的数据结构
Redis有5种基础的数据结构,分别是什么?
答:String、Hash、List、Set、SortedSet。除此之外,还有 HyperLogLog、Geo、Pub/Sub,算是高级数据结构了。
2、小小插话:
在最近的复习中,感到String结构使用非常广,比如说把用户的登陆信息转成 JSON 字符串后缓存起来,等需要取出的时候再反序列化一次。
又比如!大家应该知道,java的String是不可变的,无法修改。 关于Redis的String结构,它是动态的并且可以修改的。讲到这里,再详细讲一下redis的Stirng结构吧~
Redis的String在内部结构实现上类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。如上图所示,当前字符串实际分配空间为capacity,一般就要高于实际字符串长度len。
那么,其实当字符串长度小于 1M 时,扩容是对现有空间的成倍增长(举个例子,如果当前capacity
为10,而你需要存储一个长度为11的字符串,Redis会将capacity
增加到20。);
如果长度超过 1M 时,扩容一次只会多增加 1M 的空间。最大长度为 512M(也就是固定增量扩容,当字符串长度超过1M时,扩容的策略会有所不同。Redis一次只会多增加1M的空间,而不再是成倍增长。)
二、redis的缓存雪崩、穿透、击穿
接下来,进入本章的正题!与大家一起学习 ~ redis的缓存雪崩、穿透、击穿。
首先说明一下,项目中最佳解决方案一定需要结合实际业务调整,不同的业务处理不完全相同的。
1、缓存雪崩
即缓存同一时间大面积的失效,这个时候来了一大波请求,都怼到数据库上,最后数据库处理不过来崩了。
1.1 业务场景举例
某宝的首页大量热点数据,在某大型活动期间,针对不同时间段需要展示不同的首页数据。比如,在00:00时,需要替换的首页数据,新首页数据刚开始加载。而 0 点正在有个小活动开始,大批请求涌入。但因为新数据刚开始加载,请求多数没有命中缓存,请求到了数据库,最后就把数据库打挂了。
1.2 解决方案
方法一:给过期时间加个随机时间。① 随机时间可以长达几分钟。因为Redis是单线程处理数据的,那么几秒的缓冲不一定能够保证新数据都被加载完成。② 而且过期时间范围加大,key 会更加分散,这样也是一定程度缩短 Redis 在过期 key 时候的阻塞时间。因为当Redis的键过期时,它通常会执行一个过期键的清理操作,如果有太多的键在同一时刻过期,这可能导致Redis在清理键时出现阻塞,从而影响其他操作的性能。
ps:(随机时间解释:这是指在Redis缓存中设置首页数据的过期时间,并在过期时间上增加一些随机性,以避免在同一时刻大量请求涌入,同时导致数据库压力激增。通过添加随机时间,可以使请求在不同时间点命中缓存,而不是在同一时刻。)
方法二:加互斥锁,但这个方案会导致吞吐量明显下降。但还是要看实际业务,像上述例子就不合适用!!!
ps:(解释一波吧:加互斥锁可以确保在过期键清理操作期间不会有并发访问,但这也会导致吞吐量下降,因为锁会限制并发性能。详细点说,在处理缓存数据时引入互斥锁可以确保同一时间只有一个请求可以对缓存进行写入或修改,以避免并发冲突。使用互斥锁的代价是性能下降,因为在任何时候只允许一个请求来访问或修改缓存数据,其他请求必须等待。这就限制了系统的吞吐量。)懂了吧? 小白的我们!
方法三:热点数据不设置过期。不过期的话,正常业务请求自然就不会打到数据库了。
那新的问题又来了,不过期有脏数据,怎么办?---很简单!活动整体结束后再删除嘛!
2、缓存击穿
缓存击穿是指一个热点 key 过期或被删除后,导致线上原本能命中该热点 key 的请求,瞬间大量地打到数据库上,最终导致数据库被击垮。
2.1 业务场景举例
出现情况一般是误操作,比如设置错了过期时间、误删除导致的。
2.2 解决方案(长记性就行)
方法一:① 热点数据到底要不要过期,什么时候过期要明确。② 既然是热点数据,大概率是核心流程。那么该保证的核心功能还是需要保证的,减少犯错机会。
方法二:线上权限问题,审核清楚权限管理。
3、缓存穿透
缓存穿透是指:客户端请求缓存和数据库中不存在的数据,导致所有的请求都打到数据库上。如果请求很多,数据库依旧会被挂得明明白白。
3.1 业务场景举例
① 数据库主键 id 都是正数,然后客户端发起了 id = -1
的查询;
② 一个查询接口,有一个状态字段 status,其实 0 表示开始、1 表示结束。结果有请求一直发 status=3
的请求过来。
3.2 解决方案
方法一
做好参数校验!!!,对于不合理的参数要及时return结束!!!(简单来说,就是不要信任来自前端、客户端和上游服务的请求数据,该做的校验还是要做。)
方法二
对于查不到数据的 key,也将其短暂缓存起来。(但是后面肯定要去看为什么会有这样的数据,从根本上解决问题,该方法只是缓解问题而已。)
方法三
提供一个能迅速判断请求是否有效的拦截机制,比如布隆过滤器,Redis 本身就具有这个功能。让它维护所有合法的 key,如果请求参数不合法,则直接返回。
4、业务可靠性处理
① 提高 Redis 可用性:Redis 要么用集群架构,要么用主从 + 哨兵。保证 Redis 的可用性。
(主从复制:主节点用于写入数据,而从节点通过复制主节点的数据来提供读取服务。主节点负责写入操作,从节点则复制主节点的数据,以确保数据的备份和容错性。)
(哨兵:哨兵是一种监控 Redis 服务器状态的工具,它可以自动检测主节点的故障并进行故障转移。当主节点发生故障时,哨兵可以自动选举一个从节点作为新的主节点,以保持系统的可用性。)
② 减少对缓存的依赖
对于热点数据,是不是可以考虑加上本地缓存,比如:Guava、Ehcache,更简单点,hashMap、List 什么也可以。ps:Guava(Google Guava)和 Ehcache 都是 Java 中用于缓存的流行库。
③ 业务降级
从保护下游(与你的系统相互交互的外部服务、接口或数据库)的角度考虑,针对大流量场景是不是可以做限流。这样即使缓存崩了,也不至于把下游全部拖垮。
今天就学到这吧!每天一天小进步~,记得也把前面的复习一下!!!!!!!!!哈哈哈!拜拜,明天见~