-
撤销(Ctrl+Z)
-
重做(Ctrl+Y)
-
清空
-
H
标题(Ctrl+1~6)
- 一级标题
- 二级标题
- 三级标题
- 四级标题
- 五级标题
- 六级标题
-
粗体(Ctrl+B)
-
斜体(Ctrl+I)
-
删除线
-
插入引用(Ctrl+Q)
-
无序列表(Ctrl+U)
-
有序列表(Ctrl+O)
-
表格
-
插入分割线
-
插入链接(Ctrl+L)
-
插入图片
- 添加图片链接
-
插入代码块
-
保存(Ctrl+S)
-
开启预览
-
开启目录导航
-
关闭同步滚动
-
全屏(按ESC还原)
# 知识全景图 ![图片alt](/media/article/image/2020-12-14/1607932818322.png ''图片title'') * **高性能主线**:线程模型、数据结构、持久化、网络框架; * **高可靠主线**:主从复制、哨兵极致; * **高可扩展主线**:数据分片、负载均衡。 # 问题画像 ![图片alt](/media/article/image/2020-12-14/1607933106053.png ''图片title'') # Redis演变 ![图片alt](/media/article/image/2021-01-05/1609828867535.png ''图片title'') ![图片alt](/media/article/image/2021-01-05/1609841595780.png ''图片title'') * Redis通过网络框架进行访问,不再是动态库了,使得Redis成为一个基础的网络服务,扩大应用范围。 * Redis的value类型丰富。 * Redis的持久化支持日志AOF和快照RDB * simpleKV是单机键值数据库,而Redis支持高可靠集群和高扩展集群。 # bigkey * value很大的,才叫bigkey。 * 字符串类型,比如超过10kb,非字符串类型元素个数很多。 # value的类型及数据结构 * value的类型有五种: String(字符串)、List(列表)、 Hash(哈希)、Set(集合)和 Sorted Set(有序集合) ![图片alt](/media/article/image/2021-01-05/1609829286318.png ''图片title'') ![图片alt](/media/article/image/2021-01-05/1609830968780.png ''图片title'') ## String字符串 * string需要额外的空间记录数据长度,空间使用等信息。使用简单动态字符串结构体(Simple Dynamic String,SDS)保存。 * 内存存储,有SDS开销、RedisObject开销、dictEntry结构开销。可以使用压缩列表。 * 内存分配库jemalloc,分配内存时会找一个比申请的字节数大一些,但是接近2的幂次数的字节,比如申请6字节会分配8字节,申请24字节分配32字节。 * 最大可以存储512Mb ![图片alt](/media/article/image/2021-01-06/1609901728450.png ''图片title'') ## hash哈希 * Redis的所有键值采用hash表来保存。 * 一个哈希表就是一个数组,数组的每个元素称为一个哈希桶。所以一个哈希表由多个哈希桶组成,每个哈希桶中保存键值对数据。哈希桶中保存的并不是值,而是值的指针。 * 哈希表的好处是在,查询时通过计算键的哈希值找到对应哈希桶的位置,时间复杂度是O(1)。如果发现查询变慢了,那就是hash表发生冲突或者rehash可能带来的操作阻塞。 * Redis解决哈希冲突是链式哈希,同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。这样效率低,Redis会采用渐进式rehash操作。 * 不支持范围查询 ## set集合 * 元素不允许重复 ## sorted有序集合 * 有两个值:member值和score值,member复制存储数据,不允许重复,score负责存储顺序 ## 压缩列表 * 压缩列表类似于数组,在表头多了,zlbytes列表长度、zltail列表尾部偏移量、zllen列表中entry个数。表尾还有个zlend表示列表结束。 * 好处是查找第一个和最后一个元素是O(1),其他元素是O(n) * 使用场景:队列 * 整数数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。 ## 跳表 * 在链表的基础上,增加了多级索引,通过索引位置的几个跳转,快速定位。 ## GEO数据类型存储经纬度 # 速度快的原因 ## Redis的线程 * Redis的网络IO和键值对读写是一个线程完成的,持久化,异步删除,集群数据同步是额外的线程执行的。 ## 多线程的开销 * 多线程确实可以增加系统吞吐率,增加系统扩展性。 * 但是,多线程面临共享资源的并发访问控制问题。大部分线程都在等待获取共享资源的互斥锁,并行变成串行,增加线程也没用。 ## 速度快的原因 * Redis是内存数据库,大部分操作在内存中执行,再加上采用了高效的数据结构,例如:哈希和跳表 * 采用多路复用机制,可以在网络IO操作中并发处理大量数据请求,实现高吞吐率。 ## 处理请求过程 * 需要先监听客户端请求listen/bind,和客户端建立连接accept,从socket中读取请求recv,解析客户端的请求parse,根据请求类型读取键值对数据get,最后返回给客户端,即向socket中写回数据send。 * 潜在问题是accept()和send()是可能发生网络IO阻塞的,可以使用socket网络模型本身的非阻塞模式。 ![图片alt](/media/article/image/2021-01-05/1609832161818.png ''图片title'') ## 基于多路复用的高性能I/O模型 * Linux的多路复用指:一个线程处理多个IO流,就是select/epoll机制。在Redis只运行单进程时,该机制允许内核中,同时存在多个监听套接字和已连接套接字。 # Redis高可靠性 * 数据尽量少丢失:使用AOF和RDB * 服务尽量少中断:主从库模式 # AOF日志 * Redis使用后写日志,先执行,执行成功再写日志。这样不会记录错误数据,不会阻塞当前的写操作。 ## 三种写回策略 * Always:同步写回,每个写操作执行完,立马把日志写回到磁盘。 * Everysec:每秒写回,每个写操作执行完,先把日志写到内存缓存区,每隔一秒再把日志写回到磁盘上。 * No:操作系统控制写回,每个写操作执行完,先把日志写到内存缓存区,由操作系统决定什么时候写回磁盘。 ![图片alt](/media/article/image/2021-01-05/1609834074717.png ''图片title'') ## 日志文件太大怎么办 * AOF重写机制,同一个key只记录最新的value值。 ## AOF重写阻塞问题 * AOF重写是由额外的进程bgrewriteaof完成的,避免阻塞主进程。 * 一处拷贝:每次执行重写时,主进程会fork一个bgrewriteaof子进程,fork会把主进程的内存拷贝给它,这样子进程就可以单独完成aof重写。 * 两处日志:此时主进程未阻塞,当有新的写操作时,第一处日志就是当前使用的AOF日志;第二处日志指新的AOF重写日志。 # RDB快照 ## 命令 * save:在主进程执行,会阻塞主进程, * bgsave:创建一个子进程,在子进程中执行,不会阻塞主进程, # AOF和RDB选择问题 * 数据不能丢失:AOF和RDB混合使用 * 允许分钟级别丢失:使用RDB * 允许秒级丢失:使用AOF的everysec配置 ## 实际问题示例 * 我曾碰到过这么一个场景:我们使用一个 2 核 CPU、4GB 内存、500GB 磁盘的云主机运行 Redis,Redis 数据库的数据量大小差不多是 2GB,我们使用了 RDB 做持久化保证。当时 Redis 的运行负载以修改操作为主,写读比例差不多在 8:2 左右,也就是说,如果有 100 个请求,80 个请求执行的是修改操作。你觉得,在这个场景下,用 RDB 做持久化有什么风险吗?你能帮着一起分析分析吗? * 2核CPU、4GB内存、500G磁盘,Redis实例占用2GB,写读比例为8:2,此时做RDB持久化,产生的风险主要在于 CPU资源 和 内存资源 这2方面: * a、内存资源风险:Redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写实复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和,如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上,当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准(可以理解为武功被废)。如果机器没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。 * b、CPU资源风险:虽然子进程在做RDB持久化,但生成RDB快照过程会消耗大量的CPU资源,虽然Redis处理处理请求是单线程的,但Redis Server还有其他线程在后台工作,例如AOF每秒刷盘、异步关闭文件描述符这些操作。由于机器只有2核CPU,这也就意味着父进程占用了超过一半的CPU资源,此时子进程做RDB持久化,可能会产生CPU竞争,导致的结果就是父进程处理请求延迟增大,子进程生成RDB快照的时间也会变长,整个Redis Server性能下降。 * c、另外,可以再延伸一下,老师的问题没有提到Redis进程是否绑定了CPU,如果绑定了CPU,那么子进程会继承父进程的CPU亲和性属性,子进程必然会与父进程争夺同一个CPU资源,整个Redis Server的性能必然会受到影响!所以如果Redis需要开启定时RDB和AOF重写,进程一定不要绑定CPU。 # 主从库模式 * 读操作:主库和从库 * 写操作:主库负责写,主库将写操作同步给从库 ![图片alt](/media/article/image/2021-01-05/1609836948833.png ''图片title'') * 主从库通过replicaof(Redis5.0之前使用的是slaveof)命令完成同步。 ``` replicaof 主库ip 端口号 ``` ![图片alt](/media/article/image/2021-01-05/1609837402845.png ''图片title'') ## 主从库间网络中断怎么办 * Redis在2.8之前只能重新使用全量复制的方式同步,2.8之后可以进行增量复制的方式同步。 * 主从库断联时,会把命令写入到缓冲区repl_backlog_buffer。这是环形缓冲区,主库记录自己写入的位置,从库记录自己读到的位置。 ![图片alt](/media/article/image/2021-01-05/1609838087490.png ''图片title'') ![图片alt](/media/article/image/2021-01-05/1609838031053.png ''图片title'')![图片alt](/media/article/image/2021-01-05/1609838050015.png ''图片title'') ## Redis实例不要太大,几个G就行 # (哨兵模式)主库挂了怎么办 ## 哨兵模式基本流程 * 哨兵是运行在特殊模式下的Redis进程,主要负责: 1. 监控 哨兵进程会周期性的发送ping命令给主从库,进行监控主从库是否下线,若主库下线则启动自动切换主库流程 2. 选择主库 按照一定规则,从从库中选择一个作为主库。 3. 通知 把新的主库连接,通知给其他从库,通知给客户端,让它们把请求发送到新主库上。 ![图片alt](/media/article/image/2021-01-05/1609838933373.png ''图片title'') ## 防止哨兵误判主库下线 * 采用哨兵集群,多个哨兵一起判断 ![图片alt](/media/article/image/2021-01-05/1609839051998.png ''图片title'') ## 如何选定新主库 * 先按照一定条件进行筛选,再按照规则对剩下的从库进行打分,选择分数最高的从库作为主库 * 第一轮优先级高的从库得分高 * 第二轮与旧主库同步程度最近的从库得分高 * 第三轮ID号小的从库得分高 ![图片alt](/media/article/image/2021-01-05/1609839172614.png ''图片title'') # 哨兵集群 * 一个哨兵挂了,其他哨兵还可以工作 * 哨兵集群中,彼此是不需要配置其他哨兵的IP的 * 哨兵之间可以相互发现,通过Redis的pub/sub 发布/订阅机制实现 ![图片alt](/media/article/image/2021-01-05/1609839808104.png ''图片title'') * 基于INFO命令的从库列表,可以帮助哨兵和从库建立连接 ![图片alt](/media/article/image/2021-01-05/1609839833199.png ''图片title'') * 基于pub/sub机制,实现客户端事件的通知 ![图片alt](/media/article/image/2021-01-05/1609839948896.png ''图片title'') * 哨兵配置命令 ``` sentinel monitor <master-name> <ip> <redis-port> <quorum> ``` ## 要保证哨兵集群中所有哨兵的配置一致 * 尤其是主观下线的判断值down-after-milliseconds # 切/分片集群 ![图片alt](/media/article/image/2021-01-05/1609840366901.png ''图片title'') ## 如何保持更多的数据 ### 纵向扩展 scale up * 升级单个Redis实例配置,加内存、CPU、磁盘。 ### 横向扩展 scale out * 使用多个Redis实例 ![图片alt](/media/article/image/2021-01-05/1609840640765.png ''图片title'') ## Redis Cluster方案 * 采用哈希槽Hash Slot,处理数据和实例之间的关系,手动分配哈希槽时,需要把16384个槽都分配完,Redis集群才能正常工作。 * 对可以使用CRC16算法,计算一个16bit值,产生16384个模,每个模对应一个哈希槽。Redis会自动把这些槽平均分配给N个实例,也可以通过cluster meet 手动分配。 ![图片alt](/media/article/image/2021-01-05/1609840953639.png ''图片title'') * 集群中,增删实例会重新分配哈希槽;为了负载均衡,也会重新分配哈希槽。 # 数据统计 * 新增用户数或留存用户数:聚合统计 * 最新评论列表:排序统计 * 用户签到数:二值状态统计 * 网页独立访客量:基数统计 ![图片alt](/media/article/image/2021-01-06/1609903637186.png ''图片title'') # Redis保存时间序列数据 ### 时间序列数据特点 * 快速写入 * 查询有点查询、范围查询、聚合计算三种。 ### 方案一: * 同时保存在hash和sorted set #### 优点 * 使用hash进行单键查询,使用sorted set进行范围查询 #### 缺点 * 聚合计算时,需要把数据传输到客户端再聚合,数据传输开销大 * 所有数据都要存两次,内存开销大 #### 适用场景 * Redis实例网络带宽高,内存容量大 ### 方案二 * 使用RedisTimeSeries模块。 #### 优点 * 在Redis中直接进行聚合计算,避免大量传输到客户端 #### 缺点 * 范围查询的时间复杂度高O(n) * 点查询只能返回最新的数据,不能查任意时间点数据 #### 适用场景 * Redis实例网络带宽,内存容量有限,数据量大,聚合计算频繁。 # 消息队列 * 在分布式系统中,两个组件要基于消息队列进行通信时,一个组件把要处理的数据以消息的形式传递到消息队列中,然后这个组件可以继续做其他工作,另一个组件从消息队列中把消息读出来,再在本地处理。 ![图片alt](/media/article/image/2021-01-06/1609913151221.png ''图片title'') ![图片alt](/media/article/image/2021-01-06/1609913734330.png ''图片title'') ## 需求1:消息保序 * 消费者是异步处理消息的,但是消费者仍然需要按照生产者产生消息的顺序进行执行。 ## 需求2:重复消息处理 * 由于网络阻塞会出现消息重复传输,消息队列要能够处理重复消息,消费者要能够避免重复处理 ## 需求3:消息可靠性保证 * 消费者在处理消息过程中,发生故障或宕机,消息队列要能够提供可靠性,保证消费者可以重新读取消息继续处理。 ## 基于list消息队列方案 ![图片alt](/media/article/image/2021-01-06/1609914155265.png ''图片title'') ## 基于stream消息队列方案 ![图片alt](/media/article/image/2021-01-06/1609914232870.png ''图片title'') ## 关于Redis是否适合做消息队列 * Redis是轻量级消息队列,kafka和RabbitMQ是重量级的。 # Redis性能的影响因素 * Redis内部阻塞式操作 * CPU核和NUMA架构影响 * Redis关键系统配置 * Redis内存碎片 * Redis缓冲区 ## Redis阻塞操作 * 客户端:网络IO,键值对增删改查,数据库操作 * 磁盘:生成RDB快照,记录AOF日志,AOF日志重写 * 主从节点:主库生成,传输RDB文件,从库接受RDB文件、清空数据库、加载RDB文件。 * 切片:向其他实例发送哈希槽,数据迁移 * ![图片alt](/media/article/image/2021-01-06/1609914689762.png ''图片title'') ## Redis变慢的原因 * 响应延迟 * 基线性能 1. 使用复杂度过高的命令或一次查询全量数据; 2. 操作 bigkey; 3. 大量 key 集中过期; 4. 内存达到 maxmemory; 5. 客户端使用短连接和 Redis 相连; 6. 当 Redis 实例的数据量大时,无论是生成 RDB,还是 AOF 重写,都会导致 fork 耗时 严重; 7. AOF 的写回策略为 always,导致每个操作都要同步刷回磁盘; 8. Redis 实例运行机器的内存不足,导致 swap 发生,Redis 需要到 swap 分区读取数 据; 9. 进程绑定 CPU 不合理; 10. Redis 实例运行机器上开启了透明内存大页机制; 11. 网卡压力过大。 ### 解决方案 * 获取当前Redis实例的当前环境下的基线性能 ``` # 打印120秒内的最大延迟,实例运行延时大于这个的两倍就说明变慢了 redis-cli --intrinsic-latency 120 ``` * 是否用了慢命令,若有可改成其他命令,例:把聚合命令放到客户端 ``` showlog get 1 # 查看最近一条慢查询的日志 # 使用latency monitor命令监控慢查询 config set latency-monitor-threshold 1000 # 设置慢查询的时间阈值为1000微秒 latency monitor # 开启监控 ``` * 是否对过期key设置了相同的过期时间,若有则在每个key的过期时间加上一个随机数,避免同时删除 * 是否存在bigkey,bigkey的删除可以用异步删除,bigkey的集合查询和聚合操作,可以用SCAN命令在客户端完成 ``` ./redis-cli --bigkeys ``` * Redis的AOF是什么级别,若允许少量数据丢失,可以采用everysec模式;将配置项no-appendfsync-on-rewrite改为yes,避免aof重写与fsync竞争磁盘IO资源。 * Redis内存使用是否过大?发生swap了吗,若是则需要增加内存或者使用集群 * Redis运行环境是否启用了透明大页机制,若有则需要关闭。 * 是否运行了Redis主从集群,主库控制在2-4G,避免加载过大的RDB而阻塞 * 是否使用多核CPU?若有,则可以给Redis实例绑定物理核 * CPU是否是NUMA架构,若是,则把Redis实例和网络中断处理程序运行在一个CPU socket上 # 内存碎片 ## 形成原因 * 内因:操作系统内存分配机制 * 外因:Redis负载特征,键值对大小不一样和删改操作 ## 查看内存碎片 ``` INFO memory ``` * 其中mem_fragmentation_ratio = (used_memory_rss/used_memory)就是内存碎片率 * 其中mem_fragmentation_ratio 在1-1.5之间是正常的,如果大于1.5说明内存占用率超过了50%,需要采取措施降低。 * 如果其中mem_fragmentation_ratio小于1,可能是发生swap了。 ## 清理内存碎片 * 重启Redis * 设置Redis自动清理`config set activedefrag yes` ### 内存碎片自动清理参数 * active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理; * active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的 总空间比例达到 10% 时,开始清理。 * active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展; * active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致 响应延迟升高。 # 缓冲区 ## 客户端和服务器的缓冲区 * 输入缓存区,缓存客户端输入的命令 * 输出缓存区,缓存服务端输出的结果 ![图片alt](/media/article/image/2021-01-06/1609923579803.png ''图片title'') ## 如何应对输入缓冲区溢出 ### 原因 * 写入了bigkey,写入多个百万级别的集合类型数据 * 服务端处理请求过慢 ### 查看缓冲区命令 ``` CLIENT LIST ``` ### 解决 * 把缓冲区调大 * 数据命令的发送和处理速度调优 ## 如何应对输出缓冲区溢出 ### 原因 * 服务端返回bigkey的大量结果 * 执行了MONITOR命令(这个是监测Redis行为的,不要在生成环境使用) * 缓冲区大小设置不合适 ### 解决 * 设置缓冲区大小,normal代表普通客户端,第一个0是缓冲区大小,第二个0是缓冲区持续写入量限制,第三个0是缓冲区持续写入时间限制。 ``` client-output-buffer-limit normal 0 0 0 例如:client-output-buffer-limit pubsub 8mb 2mb 60 ``` ## 主从集群的缓冲区 * 全量复制 * 增量复制 ### 复制缓冲区的溢出问题 * 在全量复制时,主节点在发送RDB文件给从节点时,会把接受的新的写命令保存到复制缓冲区中,等RDB传输完成时,再把缓冲区的命令发送给从节点。如果在主节点在发送RDB文件给从节点时,接收到大量写命令时,可能会发送缓冲区溢出问题。 ![图片alt](/media/article/image/2021-01-06/1609925226910.png ''图片title'') # 缓存 ## 缓存的特征 * 一个系统不同层的之间的访问速度不同,所以才要缓存。 * 一个快速的子系统要连接一个慢速的子系统,为了避免每次都要去连接慢速的子系统,自身会存储部分数据。 快速的子系统缓存系统的容量总是小于慢速子系统的。 ![图片alt](/media/article/image/2021-01-07/1609987314696.png ''图片title'') ## 处理缓存的两种情况 * 命中缓存 * 缓存缺失,缓存中没有数据,需要重新读取数据,还有额外存储到缓存中。 ![图片alt](/media/article/image/2021-01-07/1609987919052.png ''图片title'') ## 只读缓存 * 应用在进行读请求,会调用Redis的GET接口,查询Redis中的数据。 * 应用在进行写请求,会直接访问数据库,在数据库进行增删改操作。对于删改的数据,如果Redis中有这些数据的话,则会直接删除,下次应用再读取这些数据时,会发生缓存缺失,应用会先读取数据库的数据然后,再写入到Redis。 ![图片alt](/media/article/image/2021-01-07/1609989035194.png ''图片title'') ### 特点 * 应用的写操作会直接到数据库,数据没有丢失的风险。 ### 应用场景 * 读多写少 ## 读写缓存 * 和只读缓存不同的是,应用在进行写操作时,会先写入Redis。 * 写回有两种策略:同步写回(Redis和数据库一起写都完成才返回),异步写回() ### 特点 * 性能高,但是Redis出现故障或宕机时会丢失数据。 ### 应用场景 * 写入比较多 ## 缓存容量 * 一般建议缓存容量设置为总数据的15%—30%,兼顾访问性能和内存空间开销。 `set memory 4gb` ## 缓存淘汰机制 * 缓存被写满是无法避免的,此时面临两个问题,淘汰哪些数据?如何淘汰? * 7种淘汰机制 ![图片alt](/media/article/image/2021-01-07/1609992184595.png ''图片title'') ## 缓存淘汰方案 ### 方案一 * allkeys-lru策略,重复利用LRU算法优势,把最近最常访问的数据留在缓存中,提升性能 ### 适用场景 * 业务数据有明显冷热数据区分。 ### 方案二 * allkeys-random策略,随机选择淘汰数据进行删除。 ### 使用场景 * 业务数据访问频率相差不大 ### 方案三 * volatile-lru策略,可以给置顶的数据不设置过期时间 ### 适用场景 * 适合有置顶需求的业务 ## Redis缓存注意 * 在Redis中,对应淘汰的数据,无论是否是脏数据,都会被删除。因此当数据被设置为脏数据时也需要在数据库中把数据修改回来。 # 缓存异常 ## 缓存数据跟数据库数据不一致问题 * 缓存中有数据,缓存中的数据跟数据库数据不一致。 * 缓存中没有数据,数据库的值不是最新的。 ![图片alt](/media/article/image/2021-01-07/1610005118439.png ''图片title'') ## 通常使用只读缓存 * 建议是先更新数据库在删除缓存。 #### 原因 * 先删除缓存值在更新数据库,会导致请求因为缓存缺失而访问数据库,造成数据库压力。 * 而且业务应用读取数据库数据,跟删除缓存值的时间不好估算,延迟双删中等待时间不好设置。 ## 缓存雪崩 ### 定义 * 大量应用请求无法在Redis缓存中进行处理,导致大量请求发送到数据库层,导致数据库层压力激增。 ### 原因一 * 缓存中有大量数据同时过期,导致大量请求无法处理。 ### 解决办法一 * 数据设置过期时间时,加上一个小的随机数。(expire命令,随机增加1-3命令) ### 解决办法二 * 服务降级,针对不同的数据采取不同的策略 * 业务应用访问非核心数据时(如商品属性):暂停从缓存中读取,而是直接返回预定义信息、空值、错误信息。 * 业务应用访问核心数据时(如商品库存):仍然从缓存中读取,如果缓存缺失,则读取数据库。 ### 原因二 * Redis宕机 ### 解决办法一(事后处理) * 在业务系统中实现服务熔断或者请求限流机制。 * 服务熔断,暂停业务应用访问Redis实例和数据库。 * 请求限流,限制数据库层的每秒访问次数。 ### 解决办法二(事前预防) * 通过主从方式构建Redis高可靠集群。 ## 缓存击穿 ### 定义 * 访问非常频繁的热点数据过期 ### 解决 * 热点数据不设置过期时间 ## 缓存穿透 ### 定义 * 访问的数据Redis缓存中没有,数据库层也没有,此时应用无法从数据库层读取数据并写入到Redis中,如果有持续大量请求,就会给缓存和数据库带来压力。 ### 原因一 * 业务层误操作,缓存和数据库中的数据被删了。 ### 解决办法一 * 缓存空值和缺省值 ### 解决办法二 * 使用布隆过滤器,快速判断数据是否存在,避免从数据库中查询数据是否存在,减小数据库压力。 ### 原因二 * 恶意攻击,专门访问数据库中没有的数据 ### 解决办法 * 在请求入口前端实现恶意代码检测 ![图片alt](/media/article/image/2021-01-07/1610008628309.png ''图片title'') ## 缓存污染 ### 定义 * 留存在缓存中的数据,实际上不会再访问了,但又占据缓存空间。 ### 解决 * LRU策略:关注数据的实效性。 * LFU策略:关注数据的访问频次(优先使用)。
知识全景图
![图片alt](/media/article/image/2020-12-14/1607932818322.png ‘‘图片title’’)
- 高性能主线:线程模型、数据结构、持久化、网络框架;
- 高可靠主线:主从复制、哨兵极致;
- 高可扩展主线:数据分片、负载均衡。
问题画像
![图片alt](/media/article/image/2020-12-14/1607933106053.png ‘‘图片title’’)
Redis演变
![图片alt](/media/article/image/2021-01-05/1609828867535.png ‘‘图片title’’)
![图片alt](/media/article/image/2021-01-05/1609841595780.png ‘‘图片title’’)
- Redis通过网络框架进行访问,不再是动态库了,使得Redis成为一个基础的网络服务,扩大应用范围。
- Redis的value类型丰富。
- Redis的持久化支持日志AOF和快照RDB
- simpleKV是单机键值数据库,而Redis支持高可靠集群和高扩展集群。
bigkey
- value很大的,才叫bigkey。
- 字符串类型,比如超过10kb,非字符串类型元素个数很多。
value的类型及数据结构
- value的类型有五种:
String(字符串)、List(列表)、 Hash(哈希)、Set(集合)和 Sorted Set(有序集合)
![图片alt](/media/article/image/2021-01-05/1609829286318.png ‘‘图片title’’)
![图片alt](/media/article/image/2021-01-05/1609830968780.png ‘‘图片title’’)
String字符串
- string需要额外的空间记录数据长度,空间使用等信息。使用简单动态字符串结构体(Simple Dynamic String,SDS)保存。
- 内存存储,有SDS开销、RedisObject开销、dictEntry结构开销。可以使用压缩列表。
- 内存分配库jemalloc,分配内存时会找一个比申请的字节数大一些,但是接近2的幂次数的字节,比如申请6字节会分配8字节,申请24字节分配32字节。
- 最大可以存储512Mb
![图片alt](/media/article/image/2021-01-06/1609901728450.png ‘‘图片title’’)
hash哈希
- Redis的所有键值采用hash表来保存。
- 一个哈希表就是一个数组,数组的每个元素称为一个哈希桶。所以一个哈希表由多个哈希桶组成,每个哈希桶中保存键值对数据。哈希桶中保存的并不是值,而是值的指针。
- 哈希表的好处是在,查询时通过计算键的哈希值找到对应哈希桶的位置,时间复杂度是O(1)。如果发现查询变慢了,那就是hash表发生冲突或者rehash可能带来的操作阻塞。
- Redis解决哈希冲突是链式哈希,同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。这样效率低,Redis会采用渐进式rehash操作。
- 不支持范围查询
set集合
- 元素不允许重复
sorted有序集合
- 有两个值:member值和score值,member复制存储数据,不允许重复,score负责存储顺序
压缩列表
-
压缩列表类似于数组,在表头多了,zlbytes列表长度、zltail列表尾部偏移量、zllen列表中entry个数。表尾还有个zlend表示列表结束。
-
好处是查找第一个和最后一个元素是O(1),其他元素是O(n)
-
使用场景:队列
-
整数数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。
跳表
- 在链表的基础上,增加了多级索引,通过索引位置的几个跳转,快速定位。
GEO数据类型存储经纬度
速度快的原因
Redis的线程
- Redis的网络IO和键值对读写是一个线程完成的,持久化,异步删除,集群数据同步是额外的线程执行的。
多线程的开销
- 多线程确实可以增加系统吞吐率,增加系统扩展性。
- 但是,多线程面临共享资源的并发访问控制问题。大部分线程都在等待获取共享资源的互斥锁,并行变成串行,增加线程也没用。
速度快的原因
- Redis是内存数据库,大部分操作在内存中执行,再加上采用了高效的数据结构,例如:哈希和跳表
- 采用多路复用机制,可以在网络IO操作中并发处理大量数据请求,实现高吞吐率。
处理请求过程
- 需要先监听客户端请求listen/bind,和客户端建立连接accept,从socket中读取请求recv,解析客户端的请求parse,根据请求类型读取键值对数据get,最后返回给客户端,即向socket中写回数据send。
- 潜在问题是accept()和send()是可能发生网络IO阻塞的,可以使用socket网络模型本身的非阻塞模式。
![图片alt](/media/article/image/2021-01-05/1609832161818.png ‘‘图片title’’)
基于多路复用的高性能I/O模型
- Linux的多路复用指:一个线程处理多个IO流,就是select/epoll机制。在Redis只运行单进程时,该机制允许内核中,同时存在多个监听套接字和已连接套接字。
Redis高可靠性
- 数据尽量少丢失:使用AOF和RDB
- 服务尽量少中断:主从库模式
AOF日志
- Redis使用后写日志,先执行,执行成功再写日志。这样不会记录错误数据,不会阻塞当前的写操作。
三种写回策略
- Always:同步写回,每个写操作执行完,立马把日志写回到磁盘。
- Everysec:每秒写回,每个写操作执行完,先把日志写到内存缓存区,每隔一秒再把日志写回到磁盘上。
- No:操作系统控制写回,每个写操作执行完,先把日志写到内存缓存区,由操作系统决定什么时候写回磁盘。
![图片alt](/media/article/image/2021-01-05/1609834074717.png ‘‘图片title’’)
日志文件太大怎么办
- AOF重写机制,同一个key只记录最新的value值。
AOF重写阻塞问题
- AOF重写是由额外的进程bgrewriteaof完成的,避免阻塞主进程。
- 一处拷贝:每次执行重写时,主进程会fork一个bgrewriteaof子进程,fork会把主进程的内存拷贝给它,这样子进程就可以单独完成aof重写。
- 两处日志:此时主进程未阻塞,当有新的写操作时,第一处日志就是当前使用的AOF日志;第二处日志指新的AOF重写日志。
RDB快照
命令
- save:在主进程执行,会阻塞主进程,
- bgsave:创建一个子进程,在子进程中执行,不会阻塞主进程,
AOF和RDB选择问题
- 数据不能丢失:AOF和RDB混合使用
- 允许分钟级别丢失:使用RDB
- 允许秒级丢失:使用AOF的everysec配置
实际问题示例
-
我曾碰到过这么一个场景:我们使用一个 2 核 CPU、4GB 内存、500GB 磁盘的云主机运行 Redis,Redis 数据库的数据量大小差不多是 2GB,我们使用了 RDB 做持久化保证。当时 Redis 的运行负载以修改操作为主,写读比例差不多在 8:2 左右,也就是说,如果有 100 个请求,80 个请求执行的是修改操作。你觉得,在这个场景下,用 RDB 做持久化有什么风险吗?你能帮着一起分析分析吗?
-
2核CPU、4GB内存、500G磁盘,Redis实例占用2GB,写读比例为8:2,此时做RDB持久化,产生的风险主要在于 CPU资源 和 内存资源 这2方面:
-
a、内存资源风险:Redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写实复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和,如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上,当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准(可以理解为武功被废)。如果机器没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。
-
b、CPU资源风险:虽然子进程在做RDB持久化,但生成RDB快照过程会消耗大量的CPU资源,虽然Redis处理处理请求是单线程的,但Redis Server还有其他线程在后台工作,例如AOF每秒刷盘、异步关闭文件描述符这些操作。由于机器只有2核CPU,这也就意味着父进程占用了超过一半的CPU资源,此时子进程做RDB持久化,可能会产生CPU竞争,导致的结果就是父进程处理请求延迟增大,子进程生成RDB快照的时间也会变长,整个Redis Server性能下降。
-
c、另外,可以再延伸一下,老师的问题没有提到Redis进程是否绑定了CPU,如果绑定了CPU,那么子进程会继承父进程的CPU亲和性属性,子进程必然会与父进程争夺同一个CPU资源,整个Redis Server的性能必然会受到影响!所以如果Redis需要开启定时RDB和AOF重写,进程一定不要绑定CPU。
主从库模式
- 读操作:主库和从库
- 写操作:主库负责写,主库将写操作同步给从库
![图片alt](/media/article/image/2021-01-05/1609836948833.png ‘‘图片title’’)
- 主从库通过replicaof(Redis5.0之前使用的是slaveof)命令完成同步。
replicaof 主库ip 端口号
![图片alt](/media/article/image/2021-01-05/1609837402845.png ‘‘图片title’’)
主从库间网络中断怎么办
- Redis在2.8之前只能重新使用全量复制的方式同步,2.8之后可以进行增量复制的方式同步。
- 主从库断联时,会把命令写入到缓冲区repl_backlog_buffer。这是环形缓冲区,主库记录自己写入的位置,从库记录自己读到的位置。
![图片alt](/media/article/image/2021-01-05/1609838087490.png ‘‘图片title’’)
![图片alt](/media/article/image/2021-01-05/1609838031053.png ‘‘图片title’’)![图片alt](/media/article/image/2021-01-05/1609838050015.png ‘‘图片title’’)
Redis实例不要太大,几个G就行
(哨兵模式)主库挂了怎么办
哨兵模式基本流程
- 哨兵是运行在特殊模式下的Redis进程,主要负责:
-
监控
哨兵进程会周期性的发送ping命令给主从库,进行监控主从库是否下线,若主库下线则启动自动切换主库流程 -
选择主库
按照一定规则,从从库中选择一个作为主库。 -
通知
把新的主库连接,通知给其他从库,通知给客户端,让它们把请求发送到新主库上。
![图片alt](/media/article/image/2021-01-05/1609838933373.png ‘‘图片title’’)
防止哨兵误判主库下线
- 采用哨兵集群,多个哨兵一起判断
![图片alt](/media/article/image/2021-01-05/1609839051998.png ‘‘图片title’’)
如何选定新主库
- 先按照一定条件进行筛选,再按照规则对剩下的从库进行打分,选择分数最高的从库作为主库
- 第一轮优先级高的从库得分高
- 第二轮与旧主库同步程度最近的从库得分高
- 第三轮ID号小的从库得分高
![图片alt](/media/article/image/2021-01-05/1609839172614.png ‘‘图片title’’)
哨兵集群
- 一个哨兵挂了,其他哨兵还可以工作
- 哨兵集群中,彼此是不需要配置其他哨兵的IP的
- 哨兵之间可以相互发现,通过Redis的pub/sub 发布/订阅机制实现
![图片alt](/media/article/image/2021-01-05/1609839808104.png ‘‘图片title’’)
-
基于INFO命令的从库列表,可以帮助哨兵和从库建立连接
![图片alt](/media/article/image/2021-01-05/1609839833199.png ‘‘图片title’’) -
基于pub/sub机制,实现客户端事件的通知
![图片alt](/media/article/image/2021-01-05/1609839948896.png ‘‘图片title’’)
- 哨兵配置命令
sentinel monitor <master-name> <ip> <redis-port> <quorum>
要保证哨兵集群中所有哨兵的配置一致
- 尤其是主观下线的判断值down-after-milliseconds
切/分片集群
![图片alt](/media/article/image/2021-01-05/1609840366901.png ‘‘图片title’’)
如何保持更多的数据
纵向扩展 scale up
- 升级单个Redis实例配置,加内存、CPU、磁盘。
横向扩展 scale out
- 使用多个Redis实例
![图片alt](/media/article/image/2021-01-05/1609840640765.png ‘‘图片title’’)
Redis Cluster方案
- 采用哈希槽Hash Slot,处理数据和实例之间的关系,手动分配哈希槽时,需要把16384个槽都分配完,Redis集群才能正常工作。
- 对可以使用CRC16算法,计算一个16bit值,产生16384个模,每个模对应一个哈希槽。Redis会自动把这些槽平均分配给N个实例,也可以通过cluster meet 手动分配。
![图片alt](/media/article/image/2021-01-05/1609840953639.png ‘‘图片title’’)
- 集群中,增删实例会重新分配哈希槽;为了负载均衡,也会重新分配哈希槽。
数据统计
- 新增用户数或留存用户数:聚合统计
- 最新评论列表:排序统计
- 用户签到数:二值状态统计
- 网页独立访客量:基数统计
![图片alt](/media/article/image/2021-01-06/1609903637186.png ‘‘图片title’’)
Redis保存时间序列数据
时间序列数据特点
- 快速写入
- 查询有点查询、范围查询、聚合计算三种。
方案一:
- 同时保存在hash和sorted set
优点
- 使用hash进行单键查询,使用sorted set进行范围查询
缺点
- 聚合计算时,需要把数据传输到客户端再聚合,数据传输开销大
- 所有数据都要存两次,内存开销大
适用场景
- Redis实例网络带宽高,内存容量大
方案二
- 使用RedisTimeSeries模块。
优点
- 在Redis中直接进行聚合计算,避免大量传输到客户端
缺点
- 范围查询的时间复杂度高O(n)
- 点查询只能返回最新的数据,不能查任意时间点数据
适用场景
- Redis实例网络带宽,内存容量有限,数据量大,聚合计算频繁。
消息队列
- 在分布式系统中,两个组件要基于消息队列进行通信时,一个组件把要处理的数据以消息的形式传递到消息队列中,然后这个组件可以继续做其他工作,另一个组件从消息队列中把消息读出来,再在本地处理。
![图片alt](/media/article/image/2021-01-06/1609913151221.png ‘‘图片title’’)
![图片alt](/media/article/image/2021-01-06/1609913734330.png ‘‘图片title’’)
需求1:消息保序
- 消费者是异步处理消息的,但是消费者仍然需要按照生产者产生消息的顺序进行执行。
需求2:重复消息处理
- 由于网络阻塞会出现消息重复传输,消息队列要能够处理重复消息,消费者要能够避免重复处理
需求3:消息可靠性保证
- 消费者在处理消息过程中,发生故障或宕机,消息队列要能够提供可靠性,保证消费者可以重新读取消息继续处理。
基于list消息队列方案
![图片alt](/media/article/image/2021-01-06/1609914155265.png ‘‘图片title’’)
基于stream消息队列方案
![图片alt](/media/article/image/2021-01-06/1609914232870.png ‘‘图片title’’)
关于Redis是否适合做消息队列
- Redis是轻量级消息队列,kafka和RabbitMQ是重量级的。
Redis性能的影响因素
- Redis内部阻塞式操作
- CPU核和NUMA架构影响
- Redis关键系统配置
- Redis内存碎片
- Redis缓冲区
Redis阻塞操作
-
客户端:网络IO,键值对增删改查,数据库操作
-
磁盘:生成RDB快照,记录AOF日志,AOF日志重写
-
主从节点:主库生成,传输RDB文件,从库接受RDB文件、清空数据库、加载RDB文件。
-
切片:向其他实例发送哈希槽,数据迁移
-
![图片alt](/media/article/image/2021-01-06/1609914689762.png ‘‘图片title’’)
Redis变慢的原因
- 响应延迟
- 基线性能
- 使用复杂度过高的命令或一次查询全量数据;
- 操作 bigkey;
- 大量 key 集中过期;
- 内存达到 maxmemory;
- 客户端使用短连接和 Redis 相连;
- 当 Redis 实例的数据量大时,无论是生成 RDB,还是 AOF 重写,都会导致 fork 耗时 严重;
- AOF 的写回策略为 always,导致每个操作都要同步刷回磁盘;
- Redis 实例运行机器的内存不足,导致 swap 发生,Redis 需要到 swap 分区读取数 据;
- 进程绑定 CPU 不合理;
- Redis 实例运行机器上开启了透明内存大页机制;
- 网卡压力过大。
解决方案
- 获取当前Redis实例的当前环境下的基线性能
# 打印120秒内的最大延迟,实例运行延时大于这个的两倍就说明变慢了
redis-cli --intrinsic-latency 120
- 是否用了慢命令,若有可改成其他命令,例:把聚合命令放到客户端
showlog get 1 # 查看最近一条慢查询的日志
# 使用latency monitor命令监控慢查询
config set latency-monitor-threshold 1000 # 设置慢查询的时间阈值为1000微秒
latency monitor # 开启监控
- 是否对过期key设置了相同的过期时间,若有则在每个key的过期时间加上一个随机数,避免同时删除
- 是否存在bigkey,bigkey的删除可以用异步删除,bigkey的集合查询和聚合操作,可以用SCAN命令在客户端完成
./redis-cli --bigkeys
- Redis的AOF是什么级别,若允许少量数据丢失,可以采用everysec模式;将配置项no-appendfsync-on-rewrite改为yes,避免aof重写与fsync竞争磁盘IO资源。
- Redis内存使用是否过大?发生swap了吗,若是则需要增加内存或者使用集群
- Redis运行环境是否启用了透明大页机制,若有则需要关闭。
- 是否运行了Redis主从集群,主库控制在2-4G,避免加载过大的RDB而阻塞
- 是否使用多核CPU?若有,则可以给Redis实例绑定物理核
- CPU是否是NUMA架构,若是,则把Redis实例和网络中断处理程序运行在一个CPU socket上
内存碎片
形成原因
- 内因:操作系统内存分配机制
- 外因:Redis负载特征,键值对大小不一样和删改操作
查看内存碎片
INFO memory
- 其中mem_fragmentation_ratio = (used_memory_rss/used_memory)就是内存碎片率
- 其中mem_fragmentation_ratio 在1-1.5之间是正常的,如果大于1.5说明内存占用率超过了50%,需要采取措施降低。
- 如果其中mem_fragmentation_ratio小于1,可能是发生swap了。
清理内存碎片
- 重启Redis
- 设置Redis自动清理
config set activedefrag yes
内存碎片自动清理参数
- active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理;
- active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的 总空间比例达到 10% 时,开始清理。
- active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展;
- active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致 响应延迟升高。
缓冲区
客户端和服务器的缓冲区
- 输入缓存区,缓存客户端输入的命令
- 输出缓存区,缓存服务端输出的结果
![图片alt](/media/article/image/2021-01-06/1609923579803.png ‘‘图片title’’)
如何应对输入缓冲区溢出
原因
- 写入了bigkey,写入多个百万级别的集合类型数据
- 服务端处理请求过慢
查看缓冲区命令
CLIENT LIST
解决
- 把缓冲区调大
- 数据命令的发送和处理速度调优
如何应对输出缓冲区溢出
原因
- 服务端返回bigkey的大量结果
- 执行了MONITOR命令(这个是监测Redis行为的,不要在生成环境使用)
- 缓冲区大小设置不合适
解决
- 设置缓冲区大小,normal代表普通客户端,第一个0是缓冲区大小,第二个0是缓冲区持续写入量限制,第三个0是缓冲区持续写入时间限制。
client-output-buffer-limit normal 0 0 0
例如:client-output-buffer-limit pubsub 8mb 2mb 60
主从集群的缓冲区
- 全量复制
- 增量复制
复制缓冲区的溢出问题
- 在全量复制时,主节点在发送RDB文件给从节点时,会把接受的新的写命令保存到复制缓冲区中,等RDB传输完成时,再把缓冲区的命令发送给从节点。如果在主节点在发送RDB文件给从节点时,接收到大量写命令时,可能会发送缓冲区溢出问题。
![图片alt](/media/article/image/2021-01-06/1609925226910.png ‘‘图片title’’)
缓存
缓存的特征
- 一个系统不同层的之间的访问速度不同,所以才要缓存。
- 一个快速的子系统要连接一个慢速的子系统,为了避免每次都要去连接慢速的子系统,自身会存储部分数据。 快速的子系统缓存系统的容量总是小于慢速子系统的。
![图片alt](/media/article/image/2021-01-07/1609987314696.png ‘‘图片title’’)
处理缓存的两种情况
- 命中缓存
- 缓存缺失,缓存中没有数据,需要重新读取数据,还有额外存储到缓存中。
![图片alt](/media/article/image/2021-01-07/1609987919052.png ‘‘图片title’’)
只读缓存
- 应用在进行读请求,会调用Redis的GET接口,查询Redis中的数据。
- 应用在进行写请求,会直接访问数据库,在数据库进行增删改操作。对于删改的数据,如果Redis中有这些数据的话,则会直接删除,下次应用再读取这些数据时,会发生缓存缺失,应用会先读取数据库的数据然后,再写入到Redis。
![图片alt](/media/article/image/2021-01-07/1609989035194.png ‘‘图片title’’)
特点
- 应用的写操作会直接到数据库,数据没有丢失的风险。
应用场景
- 读多写少
读写缓存
- 和只读缓存不同的是,应用在进行写操作时,会先写入Redis。
- 写回有两种策略:同步写回(Redis和数据库一起写都完成才返回),异步写回()
特点
- 性能高,但是Redis出现故障或宕机时会丢失数据。
应用场景
- 写入比较多
缓存容量
- 一般建议缓存容量设置为总数据的15%—30%,兼顾访问性能和内存空间开销。
set memory 4gb
缓存淘汰机制
-
缓存被写满是无法避免的,此时面临两个问题,淘汰哪些数据?如何淘汰?
-
7种淘汰机制
![图片alt](/media/article/image/2021-01-07/1609992184595.png ‘‘图片title’’)
缓存淘汰方案
方案一
- allkeys-lru策略,重复利用LRU算法优势,把最近最常访问的数据留在缓存中,提升性能
适用场景
- 业务数据有明显冷热数据区分。
方案二
- allkeys-random策略,随机选择淘汰数据进行删除。
使用场景
- 业务数据访问频率相差不大
方案三
- volatile-lru策略,可以给置顶的数据不设置过期时间
适用场景
- 适合有置顶需求的业务
Redis缓存注意
- 在Redis中,对应淘汰的数据,无论是否是脏数据,都会被删除。因此当数据被设置为脏数据时也需要在数据库中把数据修改回来。
缓存异常
缓存数据跟数据库数据不一致问题
- 缓存中有数据,缓存中的数据跟数据库数据不一致。
- 缓存中没有数据,数据库的值不是最新的。
![图片alt](/media/article/image/2021-01-07/1610005118439.png ‘‘图片title’’)
通常使用只读缓存
- 建议是先更新数据库在删除缓存。
原因
- 先删除缓存值在更新数据库,会导致请求因为缓存缺失而访问数据库,造成数据库压力。
- 而且业务应用读取数据库数据,跟删除缓存值的时间不好估算,延迟双删中等待时间不好设置。
缓存雪崩
定义
- 大量应用请求无法在Redis缓存中进行处理,导致大量请求发送到数据库层,导致数据库层压力激增。
原因一
- 缓存中有大量数据同时过期,导致大量请求无法处理。
解决办法一
- 数据设置过期时间时,加上一个小的随机数。(expire命令,随机增加1-3命令)
解决办法二
- 服务降级,针对不同的数据采取不同的策略
- 业务应用访问非核心数据时(如商品属性):暂停从缓存中读取,而是直接返回预定义信息、空值、错误信息。
- 业务应用访问核心数据时(如商品库存):仍然从缓存中读取,如果缓存缺失,则读取数据库。
原因二
- Redis宕机
解决办法一(事后处理)
- 在业务系统中实现服务熔断或者请求限流机制。
- 服务熔断,暂停业务应用访问Redis实例和数据库。
- 请求限流,限制数据库层的每秒访问次数。
解决办法二(事前预防)
- 通过主从方式构建Redis高可靠集群。
缓存击穿
定义
- 访问非常频繁的热点数据过期
解决
- 热点数据不设置过期时间
缓存穿透
定义
- 访问的数据Redis缓存中没有,数据库层也没有,此时应用无法从数据库层读取数据并写入到Redis中,如果有持续大量请求,就会给缓存和数据库带来压力。
原因一
- 业务层误操作,缓存和数据库中的数据被删了。
解决办法一
- 缓存空值和缺省值
解决办法二
- 使用布隆过滤器,快速判断数据是否存在,避免从数据库中查询数据是否存在,减小数据库压力。
原因二
- 恶意攻击,专门访问数据库中没有的数据
解决办法
- 在请求入口前端实现恶意代码检测
![图片alt](/media/article/image/2021-01-07/1610008628309.png ‘‘图片title’’)
缓存污染
定义
- 留存在缓存中的数据,实际上不会再访问了,但又占据缓存空间。
解决
- LRU策略:关注数据的实效性。
- LFU策略:关注数据的访问频次(优先使用)。