Redis常见知识点总结(下)
Redis 事务
什么是 Redis 事务?
你可以将 Redis 中的事务理解为:Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
Redis 事务实际开发中使用的非常少,功能比较鸡肋,不要将其和我们平时理解的关系型数据库的事务混淆了。
除了不满足原子性和持久性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。明明一次批量执行多个命令就可以了,这种操作实在是看不懂。
因此,Redis 事务是不建议在日常开发中使用的。
如何使用 Redis 事务?
Redis 事务通过 MULTI
、EXEC
、DISCARD
和 WATCH
等命令来实现。其核心概念是将多个命令打包,按顺序执行,并且确保在事务提交之前这些命令不会被其他操作中断。
1. 基本使用
首先,通过 MULTI
命令开启事务,接着执行多个 Redis 命令,这些命令将被队列化。当执行 EXEC
命令时,这些命令会被按顺序执行。
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
> GET PROJECT
QUEUED
> EXEC
1) OK
2) "JavaGuide"
解释:
MULTI
:开始一个事务。SET PROJECT "JavaGuide"
和GET PROJECT
被放入队列,QUEUED
表示命令已被排队。EXEC
:提交事务,执行所有队列中的命令。
2. 取消事务
如果你想取消一个事务,可以使用 DISCARD
命令,它会清空所有队列中的命令。
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
> GET PROJECT
QUEUED
> DISCARD
OK
在这个例子中,DISCARD
命令清空了所有事务队列,所以命令不会被执行。
3. 使用 WATCH 命令
WATCH
命令用于监视某个或某些键。如果这些键在事务提交之前被其他客户端修改,事务将被放弃,不会执行。
# 客户端 1
> SET PROJECT "RustGuide"
OK
> WATCH PROJECT
OK
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
# 客户端 2
# 在客户端 1 执行 EXEC 命令提交事务之前修改 PROJECT 的值
> SET PROJECT "GoGuide"
# 客户端 1
# 修改失败,因为 PROJECT 的值被客户端2修改了
> EXEC
(nil)
> GET PROJECT
"GoGuide"
在这个例子中,客户端 2 修改了 PROJECT
的值,这导致客户端 1 的事务提交失败。WATCH
命令确保了事务的一致性。
4. 事务内部修改 WATCH 监视的 Key
如果事务中的命令修改了 WATCH
命令监视的键,则事务会成功执行。
> SET PROJECT "JavaGuide"
OK
> WATCH PROJECT
OK
> MULTI
OK
> SET PROJECT "JavaGuide1"
QUEUED
> SET PROJECT "JavaGuide2"
QUEUED
> SET PROJECT "JavaGuide3"
QUEUED
> EXEC
1) OK
2) OK
3) OK
127.0.0.1:6379> GET PROJECT
"JavaGuide3"
在这个例子中,事务内的 SET
命令修改了 PROJECT
键,因此事务成功执行。
5. 事务外部修改 WATCH 监视的 Key
如果在事务外部修改了 WATCH
监视的键,事务会被取消。
> SET PROJECT "JavaGuide"
OK
> WATCH PROJECT
OK
> SET PROJECT "JavaGuide2"
OK
> MULTI
OK
> GET USER
QUEUED
> EXEC
(nil)
在这个例子中,PROJECT
键被 WATCH
监视,并且事务外部修改了该键。由于键被修改,事务没有执行。
总结
MULTI
和EXEC
是 Redis 事务的核心,前者开始事务,后者提交事务。DISCARD
取消事务,清空命令队列。WATCH
用于确保事务的键在事务提交前不被修改,否则事务会失败。
Redis 事务适用于简单的批量操作,但由于其缺乏传统事务的原子性和持久性保障,通常不建议在需要强一致性保障的场景中使用。
Redis 事务支持原子性吗?
Redis 的事务与关系型数据库的事务在原子性方面存在显著差异。虽然 Redis 事务保证按顺序执行命令,但它并不完全支持原子性。
1. Redis 事务中的原子性问题
在传统关系型数据库中,原子性意味着要么所有操作成功,要么所有操作都失败,事务中的操作不可分割。然而,Redis 的事务并不完全满足这一要求,具体表现在以下几个方面:
- 命令执行错误时不会回滚:如果事务中的某条命令执行失败,事务中的其他命令依然会继续执行,Redis 不会回滚已经执行的命令。这意味着,Redis 并不保证原子性,特别是当命令执行过程中出错时,其他命令可能已经执行成功。
- 没有回滚机制:在 Redis 中,一旦事务中的某个命令被执行并提交,就无法回滚。这是 Redis 开发者设计选择的一部分,他们认为回滚机制会增加复杂性和性能开销。
2. 为什么 Redis 不支持回滚
Redis 不支持事务回滚的主要原因是开发者认为,回滚功能会影响性能并增加复杂度。Redis 的目标是高性能和简洁性,开发者认为:
- 简单性优先:没有回滚的事务模型使 Redis 的设计更加简洁,用户可以在操作中立即发现问题,而不必依赖于事务中的回滚来恢复。
- 高效执行:回滚操作涉及到额外的计算和数据恢复工作,而 Redis 追求的是尽可能高效的执行。
3. 原子性示例
举个例子,如果 Redis 事务中的命令执行过程中某个命令失败,后续的命令仍然会执行,不会回滚:
> MULTI
OK
> SET key1 "value1"
QUEUED
> SET key2 "value2"
QUEUED
> SET key3 "value3"
QUEUED
> EXEC
1) OK
2) OK
3) (error) ERR syntax error
在这个例子中,SET key3
命令由于语法错误失败了,但 SET key1
和 SET key2
命令仍然成功执行了。事务并没有回滚前两条成功的命令。
结论
Redis 事务不满足传统的原子性要求,它保证了命令的顺序执行,但无法保证事务中所有命令要么都成功,要么都失败。如果事务中的某个命令失败,后续命令仍然会执行,且 Redis 不提供回滚功能。
因此,如果你需要严格的原子性保障,Redis 的事务可能不适合这种场景。在这些情况下,可以考虑使用其他方式来确保操作的原子性,如使用乐观锁(通过 WATCH
命令)或者通过外部事务管理工具来控制。
Redis 事务支持持久性吗?
Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式:
- 快照(snapshotting,RDB)
- 只追加文件(append-only file, AOF)
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
与 RDB 持久化相比,AOF 持久化的实时性更好。在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( fsync
策略),它们分别是:
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
appendfsync no #让操作系统决定何时进行同步,一般为30秒一次
AOF 持久化的fsync
策略为 no、everysec 时都会存在数据丢失的情况 。always 下可以基本是可以满足持久性要求的,但性能太差,实际开发过程中不会使用。
因此,Redis 事务的持久性也是没办法保证的。
如何解决 Redis 事务的缺陷?
Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常类似。我们可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。
一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。
不过,如果 Lua 脚本运行时出错并中途结束,出错之后的命令是不会被执行的。并且,出错之前执行的命令是无法被撤销的,无法实现类似关系型数据库执行失败可以回滚的那种原子性效果。因此, 严格来说的话,通过 Lua 脚本来批量执行 Redis 命令实际也是不完全满足原子性的。
如果想要让 Lua 脚本中的命令全部执行,必须保证语句语法和命令都是对的。
另外,Redis 7.0 新增了 Redis functions 特性,你可以将 Redis functions 看作是比 Lua 更强大的脚本。
Redis 性能优化(重要)
除了下面介绍的内容之外,再推荐两篇不错的文章:
使用批量操作减少网络传输
一个 Redis 命令的执行可以简化为以下 4 步:
- 发送命令
- 命令排队
- 命令执行
- 返回结果
其中,第 1 步和第 4 步耗费时间之和称为 Round Trip Time (RTT,往返时间) ,也就是数据在网络上传输的时间。
使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。
另外,除了能减少 RTT 之外,发送一次命令的 socket I/O 成本也比较高(涉及上下文切换,存在read()
和write()
系统调用),批量操作还可以减少 socket I/O 成本。这个在官方对 pipeline 的介绍中有提到:https://redis.io/docs/manual/pipelining/ 。
原生批量操作命令
Redis 中有一些原生支持批量操作的命令,比如:
MGET
(获取一个或多个指定 key 的值)、MSET
(设置一个或多个指定 key 的值)、HMGET
(获取指定哈希表中一个或者多个指定字段的值)、HMSET
(同时将一个或多个 field-value 对设置到指定哈希表中)、SADD
(向指定集合添加一个或多个元素)- ……
不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 MGET
无法保证所有的 key 都在同一个 hash slot(哈希槽)上,MGET
可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
整个步骤的简化版如下(通常由 Redis 客户端实现,无需我们自己再手动实现):
- 找到 key 对应的所有 hash slot;
- 分别向对应的 Redis 节点发起
MGET
请求获取数据; - 等待所有请求执行结束,重新组装结果数据,保持跟入参 key 的顺序一致,然后返回结果。
如果想要解决这个多次网络传输的问题,比较常用的办法是自己维护 key 与 slot 的关系。不过这样不太灵活,虽然带来了性能提升,但同样让系统复杂性提升。
Redis Cluster 并没有使用一致性哈希,采用的是 哈希槽分区 ,每一个键值对都属于一个 hash slot(哈希槽) 。当客户端发送命令请求的时候,需要先根据 key 通过上面的计算公示找到的对应的哈希槽,然后再查询哈希槽和节点的映射关系,即可找到目标 Redis 节点。
pipeline
对于不支持批量操作的命令,我们可以利用 pipeline(流水线) 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 元素个数(例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。
与MGET
、MSET
等原生批量操作命令一样,pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 hash slot(哈希槽)上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:
- 原生批量操作命令是原子操作,pipeline 是非原子操作。
- pipeline 可以打包不同的命令,原生批量操作命令不可以。
- 原生批量操作命令是 Redis 服务端支持实现的,而 pipeline 需要服务端和客户端的共同实现。
顺带补充一下 pipeline 和 Redis 事务的对比:
- 事务是原子操作,pipeline 是非原子操作。两个不同的事务不会同时运行,而 pipeline 可以同时以交错方式执行。
- Redis 事务中每个命令都需要发送到服务端,而 Pipeline 只需要发送一次,请求次数更少。
事务可以看作是一个原子操作,但其实并不满足原子性。当我们提到 Redis 中的原子操作时,主要指的是这个操作(比如事务、Lua 脚本)不会被其他操作(比如其他事务、Lua 脚本)打扰,并不能完全保证这个操作中的所有写命令要么都执行要么都不执行。这主要也是因为 Redis 是不支持回滚操作。
另外,pipeline 不适用于执行顺序有依赖关系的一批命令。就比如说,你需要将前一个命令的结果给后续的命令使用,pipeline 就没办法满足你的需求了。对于这种需求,我们可以使用 Lua 脚本 。
Lua 脚本
Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是 原子操作 。也就是说,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。
并且,Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。
不过, Lua 脚本依然存在下面这些缺陷:
- 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
- Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 hash slot(哈希槽)上。
大量 key 集中过期问题
我在前面提到过:对于过期 key,Redis 采用的是 定期删除+惰性/懒汉式删除 策略。
定期删除执行过程中,如果突然遇到大量过期 key 的话,客户端请求必须等待定期清理过期 key 任务线程执行完成,因为这个这个定期任务线程是在 Redis 主线程中执行的。这就导致客户端请求没办法被及时处理,响应速度会比较慢。
如何解决呢? 下面是两种常见的方法:
- 给 key 设置随机过期时间。
- 开启 lazy-free(惰性删除/延迟释放) 。lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。
个人建议不管是否开启 lazy-free,我们都尽量给 key 设置随机过期时间。
Redis Bigkey(大 Key)
什么是 Bigkey?
Bigkey 是指在 Redis 中占用大量内存的 key。通常,如果一个 key 对应的 value 占用的内存较大,那么这个 key 就会被认为是 Bigkey。以下是一个粗略的判定标准:
- String 类型的 value 占用内存超过 1MB。
- 复合数据类型(如 List、Hash、Set、Sorted Set)的 value 包含超过 5000 个元素。
Bigkey 是怎么产生的?有什么危害?
Bigkey 主要有以下几种产生原因:
- 程序设计不当:例如,直接使用 String 类型来存储大型二进制数据(如文件)。
- 数据规模考虑不周:当使用集合类型时,未考虑到数据量的快速增长。
- 垃圾数据未清理:例如,哈希中可能包含大量无用的键值对。
Bigkey 带来的危害:
内存消耗增加:Bigkey 占用大量内存,可能导致 Redis 内存不足,甚至发生 OOM(Out of Memory)。
性能问题:
- 客户端超时阻塞:由于 Redis 是单线程的,大 key 的操作会占用更多时间,导致客户端长时间无法得到响应。
- 网络阻塞:如果大 key 的大小达到 1MB,频繁访问时可能导致大量数据传输,造成网络拥堵。
- 工作线程阻塞:删除大 key 时,Redis 会阻塞工作线程,影响其他操作的执行。
影响主从同步和集群扩容:Bigkey 会影响 Redis 的复制和集群扩容性能,导致主从同步延迟,甚至出现故障。
如何发现 Bigkey?
使用 Redis 自带的
--bigkeys
参数:redis-cli --bigkeys
命令可以扫描 Redis 数据库中的所有 key,并找出最大的 key。这将帮助你找到大 key,但注意执行该命令会对 Redis 性能有一定影响。redis-cli -p 6379 --bigkeys
运行结果会显示每种数据类型占用内存最多的 key。例如:
[00.00%] Biggest string found so far '"my-big-string"' with 4532 bytes [00.00%] Biggest list found so far '"my-big-list"' with 1024 items
使用
SCAN
命令结合其他命令:通过
SCAN
命令逐一遍历所有 key,配合STRLEN
、HLEN
、LLEN
等命令,可以查看每个 key 的长度或成员数量,从而判断是否为 Bigkey。STRLEN <key> HLEN <key> LLEN <key> SCARD <key>
例如,获取字符串类型 key 的长度:
STRLEN my-big-string
使用开源工具分析 RDB 文件:
如果 Redis 配置了 RDB 持久化,可以使用一些开源工具分析 RDB 文件,找出大 key。
- redis-rdb-tools:Python 工具,用来分析 Redis 的 RDB 快照。
- rdb_bigkeys:Go 工具,性能更高,适用于分析 RDB 文件。
利用公有云 Redis 分析服务:
如果你使用的是公有云服务(如阿里云 Redis),可以利用其提供的实时分析功能,发现并管理 Bigkey。
如何处理 Bigkey?
分割 Bigkey:
对于大的集合类型(如 Hash、List),可以将其分割为多个较小的 key。比如,将一个包含大量字段的 Hash 按某种策略拆分为多个 Hash。
手动清理:
使用
UNLINK
命令异步删除大 key(适用于 Redis 4.0+)。如果 Redis 版本较低,可以使用SCAN
命令配合DEL
来分批删除。采用合适的数据结构:
- 使用更适合的结构来存储数据,例如:
- 二进制数据:不要用 String 来保存大型文件数据,可以使用其他工具(如文件系统)。
- 统计数据:对于页面 UV 统计,可以使用 HyperLogLog;对于状态信息(0/1),可以使用 Bitmap。
- 使用更适合的结构来存储数据,例如:
开启惰性删除(Lazy-Free):
从 Redis 4.0 开始,引入了惰性删除特性。使用
UNLINK
命令删除 key 时,Redis 会异步释放内存,避免阻塞主线程。UNLINK big-key
总结
Bigkey 是 Redis 中占用大量内存的 key,会带来一系列的性能问题,如客户端超时、网络阻塞等。通过合理的设计和及时清理,可以避免 Bigkey 的产生。若已经存在 Bigkey,可以通过分割 key、清理垃圾数据、优化数据结构等方式进行处理。
Redis Hotkey(热 Key)
什么是 Hotkey?
Hotkey 是指在 Redis 中访问频率特别高的 key。一个 key 如果比其他大部分 key 的访问频次明显高,就可以被视为热 key。例如,如果 Redis 实例每秒处理 5000 次请求,而某个 key 的访问量达到 2000 次,那么这个 key 就属于 Hotkey。
产生原因:Hotkey 通常是由于某些业务数据在短时间内频繁被访问,比如热门商品、重大新闻事件、秒杀活动等。
Hotkey 有什么危害?
Hotkey 可能会引发以下几个问题:
高 CPU 和带宽消耗:频繁访问的 Hotkey 会消耗大量的 CPU 资源和带宽,从而影响 Redis 实例对其他请求的响应速度。
Redis 宕机:如果某个 key 的访问量超出了 Redis 的处理能力,Redis 可能会由于处理压力过大导致宕机。
后端数据库压力:一旦 Redis 无法处理大量请求,后端数据库的负载可能暴增,甚至导致数据库崩溃。
因此,Hotkey 可能会成为性能瓶颈,影响系统的高可用性和稳定性。
如何发现 Hotkey?
使用 Redis 自带的
--hotkeys
参数:Redis 4.0.3 及以上版本提供了
--hotkeys
参数,可以用来查看所有 key 的访问次数。这需要 Redis 的maxmemory-policy
设置为 LFU 策略。redis-cli -p 6379 --hotkeys
注意:此命令会扫描整个 key 空间,可能对 Redis 性能有一定影响,建议在低峰期执行。
使用
MONITOR
命令:MONITOR
命令可以实时显示 Redis 执行的所有命令,帮助分析哪些 key 被频繁访问。尽管该命令对性能有较大影响,但可以在合适的时机使用,并将输出重定向至文件进行分析。redis-cli 127.0.0.1:6379> MONITOR OK
然后可以对日志文件中的请求进行分析,找出频繁访问的 key。
借助开源项目:
例如,京东零售的 hotkey 项目,不仅可以发现 Hotkey,还支持处理 Hotkey,适用于大规模 Redis 系统的 Hotkey 发现和处理。
根据业务情况提前预估:
根据业务的特点,可以提前预估一些 Hotkey。例如,参与秒杀活动的商品,或者重大新闻事件中的相关信息。这些预估有助于提前对 Hotkey 进行优化。
在业务代码中记录分析:
在业务代码中嵌入逻辑来记录每个 key 的访问情况,并根据访问量进行分析。这种方式需要额外的开发工作,通常在系统复杂时使用较少。
借助公有云 Redis 分析服务:
如果使用的是公有云的 Redis 服务(如阿里云 Redis),可以利用云服务提供的实时分析工具来监控并发现 Hotkey。例如,阿里云 Redis 提供了热 Key 实时分析功能,帮助用户快速发现和管理 Hotkey。
如何解决 Hotkey?
读写分离:
通过将写请求交给主节点,读请求交给从节点,可以有效减轻主节点的负载,避免 Hotkey 对 Redis 实例的性能造成单点压力。
使用 Redis Cluster:
将数据分布到多个 Redis 节点上。通过 Redis Cluster,热数据可以分散到不同的节点,从而避免单个节点过载。Redis Cluster 支持数据分片,可以将热点数据分散存储。
二级缓存:
将频繁访问的 Hotkey 缓存在 JVM 本地内存中(如使用 Caffeine 等缓存库),避免每次访问都落到 Redis,从而降低 Redis 的压力。
公有云 Redis 优化:
如果使用的是公有云 Redis 服务,可以参考云服务提供的优化方案。例如,阿里云提供的 Proxy Query Cache 功能,可以帮助优化 Hotkey 问题。该功能通过代理缓存热点数据,减少 Redis 实例的负担。
总结
Hotkey 是指在 Redis 中访问频率极高的 key,可能会导致 Redis 实例过载,影响系统性能和稳定性。发现和处理 Hotkey 需要通过多种手段,包括 Redis 提供的 --hotkeys
参数、MONITOR
命令、开源项目等。解决 Hotkey 的方法包括读写分离、使用 Redis Cluster、二级缓存等。同时,公有云 Redis 服务也提供了一些针对 Hotkey 的优化方案。
Redis 慢查询命令
为什么会有慢查询命令?
Redis 的大部分命令时间复杂度为 O(1),即无论数据量大小,执行时间固定。但也有一些命令的时间复杂度是 O(n),例如 KEYS *
、HGETALL
、LRANGE
等。这些命令会根据数据量的不同导致执行时间增加,随着数据量的增大,命令执行时间可能会变得很长,甚至达到慢查询的标准。
慢查询命令是指执行时间较长的命令。虽然这些命令并不一定不能使用,但需要特别注意,当数据量增大时,这些命令的执行时间也会增长,可能影响 Redis 的性能。
常见的慢查询命令包括:
KEYS *
:获取所有的 keys,随着 key 数量增加,执行时间增长。HGETALL
:获取 Hash 中所有的键值对。LRANGE
:获取 List 中指定范围的元素。SMEMBERS
:获取 Set 中的所有元素。SINTER
/SUNION
/SDIFF
:计算多个 Set 的交集、并集或差集。
除了上述的 O(n) 时间复杂度命令,还有一些命令,其时间复杂度在 O(N) 以上,例如:
ZRANGE
/ZREVRANGE
:返回指定排名范围的所有元素,复杂度为 O(log(n) + m),其中 n 是总元素数,m 是返回元素的数量,若 m 和 n 很大,则可能接近 O(n)。ZREMRANGEBYRANK
/ZREMRANGEBYSCORE
:移除指定范围内的元素,复杂度为 O(log(n) + m)。
如何找到慢查询命令?
可以通过 redis.conf
文件中的配置,或者通过 CONFIG
命令直接修改配置来启用慢查询日志。慢查询日志会记录那些执行时间超过设定阈值的命令。
配置慢查询阈值:
slowlog-log-slower-than
:设置执行时间超过该值的命令会被记录到慢查询日志。单位为微秒。slowlog-max-len
:设置慢查询日志的最大条数,超过此条数时,最早的慢查询记录将被丢弃。
默认配置如下:
slowlog-log-slower-than 10000 slowlog-max-len 128
这表示执行时间超过 10ms 的命令会被记录,最多保留 128 条慢查询日志。
动态修改配置:
可以通过
CONFIG
命令动态修改慢查询的配置:CONFIG SET slowlog-log-slower-than 10000 # 设置阈值为 10ms CONFIG SET slowlog-max-len 128 # 设置最多记录 128 条慢查询日志
获取慢查询日志:
使用
SLOWLOG GET
命令获取慢查询日志:127.0.0.1:6379> SLOWLOG GET 1) 1) (integer) 5 2) (integer) 1684326682 3) (integer) 12000 4) 1) "KEYS" 2) "*" 5) "172.17.0.1:61152" 6) ""
每条慢查询记录包含:
- 唯一的日志标识符
- 命令执行的 Unix 时间戳
- 执行所需的时间(单位微秒)
- 命令及其参数
- 客户端的 IP 地址和端口
- 客户端名称
默认返回最近 10 条慢查询记录,你也可以指定返回的条数,例如
SLOWLOG GET 20
。其他常用命令:
查询慢查询记录数:
127.0.0.1:6379> SLOWLOG LEN (integer) 128
清空慢查询记录:
127.0.0.1:6379> SLOWLOG RESET OK
总结
慢查询命令是 Redis 执行时间较长的命令,通常是因为使用了时间复杂度为 O(n) 或更高的命令。通过配置 slowlog-log-slower-than
和 slowlog-max-len
,可以记录慢查询命令,并根据这些日志优化 Redis 的使用。例如,替代全表扫描命令(如 KEYS
、HGETALL
)为扫描版本(如 SCAN
、HSCAN
)来减少对性能的影响。
Redis 内存碎片
什么是内存碎片?为什么会有 Redis 内存碎片?
1. 什么是内存碎片?
内存碎片是指内存中分配的内存块不连续、零散的情况,导致一部分内存虽然没有被使用,但却无法被重新分配或利用。内存碎片会导致实际可用内存比总内存要少,进而影响程序的性能,甚至可能导致内存不足的问题。
内存碎片可以分为两种类型:
- 外部碎片:指的是内存中有许多小块的空闲空间,但这些空闲空间无法组合成一个足够大的连续块来满足较大的内存请求。
- 内部碎片:指的是内存块内部有空闲空间,尽管内存块被分配了,但实际使用的空间比分配的空间要少。例如,分配了 1MB 内存,但实际只用了 500KB,剩余的 500KB 就成了内部碎片。
2. 为什么会有 Redis 内存碎片?
在 Redis 中,内存碎片的产生主要与以下几个因素相关:
内存分配器的工作原理:Redis 使用了
jemalloc
内存分配器(或者tcmalloc
,具体取决于配置)。这些分配器的工作原理是动态分配内存块,而这些内存块的大小和数量不一定是完全连续的。当 Redis 中存储的数据对象大小和删除/更新时机不一致时,就会导致内存空间不连续,形成碎片。数据对象的生命周期:Redis 存储的数据是以对象的形式存在的,例如字符串、哈希、列表等。当 Redis 创建和删除这些对象时,内存的分配和回收并不是按顺序进行的,可能会留下空闲的内存块。这种空闲内存不会被立即合并,导致了内存碎片。
内存预分配:为了提高性能,Redis 会对某些数据结构进行内存预分配。这意味着 Redis 为了避免频繁的内存申请,可能会多分配一些内存,这些额外分配的内存可能无法完全利用,导致空间浪费和碎片化。
对象大小的不均衡:Redis 存储的不同数据结构(如
List
、Set
、Hash
等)对内存的要求不同。如果某些数据结构的大小不规则,或者在存储大量数据时频繁改变其大小,就可能导致内存碎片的产生。内存回收机制:Redis 的内存回收是基于
jemalloc
的垃圾回收机制的,当一个对象的生命周期结束时,它占用的内存会被释放。然而,释放的内存不一定会立即合并成一个大的连续空闲区,因此,内存碎片可能会随着 Redis 的运行时间而增加。
因此,内存碎片的出现通常是因为 Redis 的内存管理和数据结构的动态性,尤其是在高并发、大数据量的情况下,内存碎片的影响尤为明显。
如何清理 Redis 内存碎片?
Redis 内存碎片问题可能会影响系统性能,因此需要定期清理碎片以确保 Redis 的高效运行。以下是几种清理 Redis 内存碎片的方法:
1. 使用 MEMORY PURGE
命令
Redis 5.0 及以上版本引入了 MEMORY PURGE
命令,用于清理内存碎片。当执行这个命令时,Redis 会尝试回收所有不再使用的内存块,并释放碎片。
127.0.0.1:6379> MEMORY PURGE
此命令会触发 jemalloc
(Redis 默认内存分配器)进行内存碎片整理。执行后,Redis 会通过系统调用尝试回收内存,减轻内存碎片带来的影响。
注意: MEMORY PURGE
命令的效果是有限的,不会彻底消除内存碎片,尤其是在内存占用很高或内存对象生命周期非常短的情况下。
2. 重启 Redis 实例
另一种常见的方法是通过 重启 Redis 实例 来清理内存碎片。重启 Redis 后,内存会重新分配,所有的碎片都会被清除掉。虽然这种方式有效,但会导致 Redis 服务短暂不可用,因此在生产环境中应当谨慎使用,最好在负载较低的时间段进行。
# 停止 Redis 服务
sudo systemctl stop redis
# 启动 Redis 服务
sudo systemctl start redis
注意: 重启可能会导致数据丢失(如果没有持久化),所以要确保 Redis 的持久化配置(如 AOF 或 RDB)已启用。
3. 调整内存分配策略(使用 jemalloc
配置)
jemalloc
是 Redis 默认的内存分配器,它提供了多种调节内存分配行为的配置选项。通过调整这些配置,可以减少内存碎片的产生。
内存回收机制:
jemalloc
提供了内存回收机制,自动清理不再使用的内存块。可以通过 Redis 配置文件或者命令行参数来调整内存回收策略。通常,默认配置已经能较好地处理内存碎片问题,但在一些极端情况下可以通过调整配置来优化。调整
jemalloc
参数:可以调整jemalloc
的配置,例如arena
配置来控制内存的分配和释放策略,从而减少碎片。
4. 定期使用 BGSAVE
或 SAVE
触发持久化
通过手动或者定时触发 RDB 持久化(BGSAVE
或 SAVE
命令),也能间接减少碎片。执行持久化操作时,Redis 会在后台创建一个新的 RDB 文件,并在此过程中清理内存中的碎片。
# 手动触发 RDB 持久化
127.0.0.1:6379> BGSAVE
通过定期触发持久化,Redis 会在执行持久化时对内存进行整理,减少碎片问题。
5. 定期重启 Redis 实例(自动化)
对于一些高负载的 Redis 实例,可以设置定期重启(例如每隔几天重启一次)来清理内存碎片。这种方式尤其适用于长时间运行的实例,可以通过系统调度工具如 cron
来自动化执行重启。
例如,设置每天凌晨重启 Redis:
# 使用 crontab 设置定时重启 Redis 服务
0 3 * * * /bin/systemctl restart redis
6. 监控并调整内存使用
除了定期清理碎片外,还需要通过监控 Redis 的内存使用情况,及时发现异常并调整内存配置。例如,使用 Redis 的 MEMORY STATS
命令查看内存碎片的详细信息。
127.0.0.1:6379> MEMORY STATS
通过分析返回的数据,可以了解内存碎片的具体情况,帮助优化 Redis 配置。
总结
清理 Redis 内存碎片的常见方法包括:
- 使用
MEMORY PURGE
命令进行碎片清理。 - 重启 Redis 实例以清理内存碎片。
- 调整
jemalloc
配置,优化内存分配策略。 - 定期触发持久化操作(
BGSAVE
或SAVE
)。 - 设置自动重启 Redis 实例清理碎片。
- 监控 Redis 内存使用,及时发现并优化内存碎片。
这些方法可以配合使用,帮助降低内存碎片的影响,确保 Redis 高效稳定地运行。
Redis 生产问题(重要)
什么是缓存穿透?
缓存穿透是指大量请求的 key 不存在于缓存中,也不在数据库中。由于请求没有经过缓存,直接访问数据库,导致数据库承受了巨大的压力。最终可能会因为数据库的高负载而导致宕机。
例如,恶意请求或者错误请求发送大量的 key,导致这些 key 一直查询数据库,而数据库根本没有数据返回。由于没有经过缓存的缓解,所有的请求都会对数据库产生压力,严重时可能会导致数据库崩溃。
解决缓存穿透的办法
1. 缓存空值
当某个 key 在缓存和数据库中都没有找到时,可以将这个空值缓存起来,并设置过期时间。这能避免相同的无效请求频繁访问数据库。通过将不存在的数据也缓存一段时间,可以有效减少数据库的访问。
示例代码:
public Object getObjectInclNullById(Integer id) {
// 从缓存中获取数据
Object cacheValue = cache.get(id);
// 缓存为空
if (cacheValue == null) {
// 从数据库中获取
Object storageValue = storage.get(id);
// 缓存空对象,设置过期时间
cache.set(id, storageValue);
// 如果数据库中没有数据,设置短时间过期时间
if (storageValue == null) {
cache.expire(id, 60 * 5); // 设置 5 分钟过期时间
}
return storageValue;
}
return cacheValue;
}
通过这种方式,虽然缓存了一个空值,但可以确保对不存在的数据的重复请求不再访问数据库,减轻数据库压力。
2. 布隆过滤器
布隆过滤器是一种空间效率高、查找速度快的数据结构,适用于解决缓存穿透问题。其原理是利用哈希函数对可能存在的 key 进行处理,并存储在一个位数组中。查询时,首先检查该 key 是否存在于布隆过滤器中,如果不存在,则直接返回错误,避免无效请求落到数据库上。
布隆过滤器的特点是概率性,即会有一定的误判,但永远不会漏判。它适用于请求频率较高但数据量大的场景。
示意图:
- 请求到来时,先查询布隆过滤器,如果结果为不存在,直接返回错误;
- 如果布隆过滤器认为 key 存在,则继续查询缓存,最后访问数据库。
布隆过滤器的流程图:
这种方法可以大幅减少不必要的数据库访问。
3. 接口限流
通过对 API 接口进行限流,可以有效避免大量非法请求频繁访问数据库。限流的基本策略是根据 IP 或者用户进行流量控制,限制每个 IP 或用户在一定时间内的请求次数。如果请求超过限制,可以拒绝该请求或进行处理。
在缓存穿透的场景中,恶意请求常常表现为高频次访问,可以使用限流或者黑名单策略,拒绝异常请求。例如,将异常 IP 列入黑名单,或者在访问高峰期实施流量控制。
4. 结合缓存击穿和雪崩
缓存穿透问题可以与缓存击穿、缓存雪崩等问题结合使用限流策略。对于这些问题,限流可以有效控制恶意请求的频次,减少对后端系统(尤其是数据库)的压力。
总结
常见的解决缓存穿透的方法包括:
- 缓存空值:当数据不存在时,将空值缓存一定时间,减少重复查询。
- 布隆过滤器:使用布隆过滤器避免非法请求访问数据库,减少不必要的数据库压力。
- 接口限流:对请求进行频率限制,防止恶意请求过多访问数据库。
- 结合缓存击穿、雪崩和限流:多种缓存策略结合,确保系统稳定性。
这些方法可以单独使用,也可以组合使用,以确保系统的高可用性和高性能。
什么是缓存击穿?
缓存击穿是指某个 热点数据 在 缓存中已过期,但由于缓存中没有有效数据,导致大量请求直接访问数据库,从而对数据库产生极大的压力。通常这种情况发生在某个热点数据的缓存过期后,多个请求同时发起,导致数据库承受过多的并发查询。
举个例子:
在秒杀过程中,某个秒杀商品的缓存数据过期了,而大量用户请求该商品的秒杀信息时,所有请求都会直接访问数据库,造成数据库过载甚至崩溃。
解决缓存击穿的办法
1. 永不过期(不推荐)
一种解决方案是将热点数据设置为永不过期,或者设置较长的过期时间,确保缓存中始终有数据。
缺点:
- 热点数据的过期时间设置为永不过期,可能会导致数据不更新,或者与数据库中的数据不一致。
- 对某些场景不适用,比如数据更新较频繁的情况。
因此,这种方法通常不推荐作为长期的解决方案。
2. 提前预热(推荐)
针对热点数据,可以在数据变动或者活动开始之前,将数据提前加载到缓存中,并设置合理的过期时间。比如秒杀活动开始前,可以提前将商品数据加载到缓存中,确保在活动期间缓存数据不会过期。
具体做法:
- 在秒杀、促销等活动开始之前,提前将相关的数据加载到缓存中,并设置合适的过期时间,确保高并发请求能命中缓存。
- 对一些周期性热点数据,可以设置定时任务定期加载或更新缓存。
3. 加锁(看情况)
当缓存失效后,可以通过分布式锁或者互斥锁确保在短时间内只有一个请求能查询数据库并更新缓存。其他请求需要等待锁释放后才能获取到数据,从而避免大量请求同时访问数据库。
实现思路:
- 使用 Redis 等分布式缓存系统提供的锁机制(如
SETNX
命令或者 Redisson 提供的分布式锁)。 - 当缓存失效时,只有第一个请求能查询数据库并更新缓存,其他请求会等待直到缓存更新完毕。
代码示例:
public Object getCache(String key) {
Object value = cache.get(key);
if (value == null) {
// 加锁,确保只有一个请求能查询数据库并更新缓存
synchronized (this) {
value = cache.get(key);
if (value == null) {
value = database.query(key); // 查询数据库
cache.set(key, value); // 更新缓存
}
}
}
return value;
}
缓存穿透与缓存击穿的区别
- 缓存穿透:请求的 key 既不在缓存中,也不在数据库中。通常是由于非法请求或错误的请求导致,这类请求直接绕过缓存访问数据库,产生不必要的压力。解决方案一般是使用布隆过滤器等技术提前过滤非法请求。
- 缓存击穿:请求的 key 对应的是 热点数据,该数据 在数据库中存在,但缓存已经过期。这种情况会导致大量请求同时访问数据库。解决方案包括提前预热、设置过期策略、加锁等。
总结
- 缓存击穿的关键是热点数据缓存过期,导致大量请求直接访问数据库。
- 常见的解决方案包括提前预热数据、加锁以及设置合理的过期策略。
- 与 缓存穿透 不同,缓存击穿是因为缓存失效,而缓存穿透是因为请求的 key 根本就不在缓存或数据库中。
什么是缓存雪崩?
缓存雪崩指的是缓存中大量数据在同一时刻失效,导致大量请求直接访问数据库,从而对数据库造成巨大的压力,甚至可能导致数据库宕机。这种情况类似于雪崩,突发的请求激增会把数据库压垮。
举个例子:
假设有大量的数据在缓存中设置了相同的过期时间,所有数据在同一时刻过期。此时,所有请求都会尝试去访问数据库,导致数据库瞬间被大量请求压垮,甚至崩溃。
另外,如果缓存服务本身宕机,所有缓存请求都将直接落到数据库,造成同样的雪崩效应。
解决缓存雪崩的办法
1. 针对 Redis 服务不可用的情况
- Redis 集群:采用 Redis 集群来避免单点故障。当单个 Redis 节点出现问题时,集群中的其他节点仍然可以继续提供缓存服务。Redis Cluster 和 Redis Sentinel 是常用的 Redis 集群实现方案。
- 多级缓存:除了 Redis,可以使用本地缓存与 Redis 结合的二级缓存策略。当 Redis 服务不可用时,可以从本地缓存获取数据,避免全部请求落到数据库上。
2. 针对大量缓存同时失效的情况
设置随机失效时间(可选):可以在设置缓存的过期时间时,加入随机值,使得缓存的失效时间不再集中在同一时刻。这样可以避免大量缓存同时过期,减少缓存雪崩的风险。
提前预热(推荐):针对热点数据,可以在缓存失效之前,提前将数据加载到缓存中。尤其是对于秒杀、促销等高并发场景,可以在活动开始前预热缓存,确保缓存数据在高流量时刻不会失效。
持久缓存策略(看情况):虽然不推荐所有数据都设置永不过期,但对于一些关键且变化不频繁的数据,可以考虑使用持久化缓存策略,确保这些数据不会频繁失效。
3. 缓存预热的实现
缓存预热可以通过以下两种方式实现:
- 定时任务:使用定时任务(如
xxl-job
)来定期触发缓存预热逻辑,将热点数据提前查询并存入缓存。 - 消息队列:使用消息队列(如 Kafka)来异步进行缓存预热。将热点数据的主键或 ID 发送到消息队列中,然后由缓存服务根据这些 ID 查询数据库并更新缓存。
4. 缓存雪崩和缓存击穿的区别
- 缓存雪崩:是指缓存中的大量或所有数据同时失效,导致大量请求直接访问数据库,造成数据库的巨大压力。常见的解决方法包括随机失效时间、缓存预热、Redis 集群等。
- 缓存击穿:是指某些热点数据在缓存中失效,导致大量请求直接访问数据库,主要是因为缓存中的热点数据在失效后没有及时更新或被并发请求击穿。解决方法包括提前预热和加锁。
总结
缓存雪崩通常是由于大量缓存数据在同一时间过期,导致所有请求集中到数据库,从而造成数据库宕机。解决方法包括设置缓存失效时间的随机性、提前预热热点数据、以及保证缓存服务高可用(如 Redis 集群)。
如何保证缓存和数据库数据的一致性?
在引入缓存后,数据一致性是一个比较棘手的问题。尤其是在缓存失效或更新时,如何保证缓存和数据库的数据始终保持一致,成为了一个难点。为了应对这一问题,最常见的模式之一是 Cache Aside Pattern(旁路缓存模式)。
1. Cache Aside Pattern(旁路缓存模式)
在旁路缓存模式中,缓存的读取和写入都由应用程序直接控制。具体操作流程如下:
- 读取缓存:首先从缓存中获取数据,如果缓存命中,则直接返回;如果缓存未命中,再从数据库中查询,并将数据写入缓存。
- 写数据库:在接收到写请求时,首先更新数据库,然后删除缓存中的数据。
该模式能够有效解决缓存和数据库数据一致性的问题。关键在于:更新数据库后,删除缓存,以确保缓存中的数据不会因为过期或无效而导致读取到过时数据。
2. 解决写操作时缓存删除失败的问题
当更新数据库后删除缓存时,可能会遇到缓存删除失败的情况,这种情况有两个常见的解决方案:
缓存失效时间变短(不推荐):减少缓存的过期时间,使得缓存数据较快失效,从而迫使系统在下次请求时重新加载数据库数据。虽然这种方法简单,但治标不治本,因为它并没有解决缓存删除失败的问题。对于某些场景,尤其是先操作缓存再操作数据库的情况,这种方法并不适用。
增加缓存更新重试机制(常用):当缓存删除失败时,可以通过设置重试机制来进行重试。这样,当缓存服务不可用时,应用可以在一段时间后再尝试删除缓存。为了避免对系统的直接影响,可以使用消息队列实现异步重试。具体流程是,当缓存删除失败时,将删除缓存的请求投递到消息队列,消费者从队列中获取任务并重试,直到成功删除缓存。
总结
- 旁路缓存模式适合于缓存和数据库数据一致性管理,它简单有效,特别适用于读多写少的场景。
- 缓存删除失败的处理:通过设置缓存失效时间,或者增加缓存更新的重试机制来处理缓存更新失败的情况,后者更加可靠。
- 引入消息队列来做异步重试,可以确保系统在面对缓存服务故障时,依然能够保持较高的可用性。
如果不想为了一点短期的不一致性而使系统设计变得过于复杂,采用这种简单而有效的方案可能会是一个较为理想的选择。
Redis 阻塞的可能原因
Redis 是一个高性能的内存数据库,但在一些特定的操作或场景下,可能会出现阻塞的情况。以下是一些常见的会导致 Redis 阻塞的原因:
1. 长时间运行的阻塞命令
某些 Redis 命令在执行时会阻塞客户端,特别是在进行较长时间的操作时。这些命令会导致客户端阻塞直到命令执行完毕。常见的阻塞命令包括:
BLPOP
/BRPOP
/BRPOPLPUSH
:这些命令用于从列表中阻塞地弹出元素,直到列表中有数据可供弹出。阻塞会持续,直到满足条件(列表非空)。WAIT
:用于等待一个或多个从节点同步完成写操作。该命令会阻塞直到指定数量的从节点同步完成。
如果应用程序发出这些命令,而数据未能及时满足条件,就会导致 Redis 服务器阻塞等待,可能会影响系统的性能和响应时间。
2. 大量的KEYS
命令
KEYS
命令会遍历整个数据库,查找匹配的键,导致 Redis 对所有的键进行扫描。在大量数据情况下,这个操作可能非常耗时,特别是在包含数百万甚至数十亿个键的 Redis 实例上。虽然它不是一个本质上的阻塞操作,但它会对 Redis 的性能产生严重影响,尤其是在高并发的情况下,可能会导致阻塞其他请求。
- 示例命令:
KEYS *
会列出所有的键。尽量避免在生产环境中使用该命令,尤其是在大数据量的情况下。
3. FLUSHALL
/ FLUSHDB
命令
FLUSHALL
:清空 Redis 所有数据库中的所有数据。FLUSHDB
:清空当前数据库中的所有数据。
这些命令会删除 Redis 中的大量数据,并且会占用较长时间,特别是在数据量庞大的情况下,会导致 Redis 暂时无法响应其他请求,从而产生阻塞。
4. SAVE
/ BGSAVE
命令
SAVE
和 BGSAVE
是用于持久化数据的命令,尤其是 SAVE
命令会阻塞 Redis,直到数据持久化完成。虽然 BGSAVE
是在后台异步执行的,但它会在数据量较大时导致 Redis 负载较高,影响性能。
5. 内存限制导致的阻塞
当 Redis 实例的内存达到最大限制时,Redis 会阻塞新的写请求。具体来说:
- 如果启用了
maxmemory
配置并且内存达到限制,Redis 会根据设置的maxmemory-policy
来决定如何处理新的写入操作。比如,noeviction
策略会阻止新的写入请求,直到有足够的内存可用为止,这会导致请求阻塞。 OOM
错误:当 Redis 内存不足,且配置了maxmemory
后,Redis 会返回 OOM(Out of Memory)错误,如果写入数据超过了配置的内存限制,写请求就会被阻塞或失败。
6. 网络延迟或连接问题
如果 Redis 客户端与 Redis 服务器之间的网络连接质量较差,或者 Redis 服务端出现负载过高的情况,可能会导致客户端的请求因网络延迟而阻塞。特别是使用远程客户端访问 Redis 时,网络问题可能导致请求响应延迟。
7. Lua 脚本执行
Redis 中的 Lua 脚本在执行时是原子性的,这意味着整个脚本执行过程中,Redis 会阻塞其他客户端的请求,直到脚本执行完成。如果 Lua 脚本中包含了复杂或长时间运行的操作(如遍历大量数据、进行复杂计算等),可能会导致 Redis 阻塞,影响其他请求的处理。
总结
Redis 阻塞通常与长时间运行的操作、内存问题、命令使用不当等因素有关。为了避免阻塞问题,可以采取以下措施:
- 尽量避免使用可能导致长时间阻塞的命令,如
KEYS
、FLUSHALL
、FLUSHDB
等。 - 对于阻塞命令,如
BLPOP
,在合适的场景下使用,避免不必要的等待。 - 监控 Redis 的内存使用情况,避免达到内存上限。
- 在执行大规模持久化或 Lua 脚本时,要注意它们对系统性能的影响。
Redis 集群
什么是 Redis Sentinel?
Redis Sentinel 是 Redis 提供的一种高可用性解决方案,用于监控 Redis 集群中的主节点和从节点的状态,自动进行故障转移,并且保证在主节点故障时能够自动将一个从节点提升为主节点。Sentinel 是一个分布式的高可用管理系统,能够监控 Redis 实例,确保 Redis 服务的高可用性,并能够在故障发生时自动恢复服务。
Redis Sentinel 的主要功能
监控
Sentinel 会持续监控 Redis 主节点和从节点的状态,及时发现节点的故障和不可用状态。它会通过发送 PING 命令来检查 Redis 实例的健康状况。如果 Sentinel 发现某个 Redis 实例无法响应,它会将该实例标记为 下线 状态。故障转移
当 Sentinel 发现主节点不可用时,它会自动选择一个从节点并将其升级为新的主节点。这个过程是自动进行的,客户端无需进行干预。新的主节点会自动通知其他从节点进行同步,以保证数据的一致性。通知
Sentinel 可以将节点状态的变化(如节点宕机、故障恢复等)通过事件通知机制告知系统管理员。它可以通过 API 或邮件、短信等方式发送通知。配置管理
Sentinel 还可以动态修改客户端连接配置,在主节点发生切换时自动更新主节点的连接信息,以确保客户端能够连接到新的主节点。
Redis Sentinel 的工作原理
Redis Sentinel 是由多个独立的 Sentinel 进程组成的,这些进程通过互相通信来进行协调和决策。Sentinel 的工作原理如下:
监控节点状态:
Sentinel 会定期向 Redis 主节点和从节点发送 PING 命令,以监控节点的健康状态。检测故障:
如果 Sentinel 检测到某个 Redis 节点无法响应,它会尝试判断该节点是否确实故障。通过多数 Sentinel 的投票机制,Sentinel 确定故障节点后会宣布该节点不可用。故障转移:
当一个主节点被判定为故障节点时,Sentinel 会从现有的从节点中选举一个作为新的主节点。新的主节点会开始接收写请求,同时,其他从节点会自动将数据同步到新的主节点。通知:
Sentinel 会通过事件通知机制告知管理员节点的状态变化,帮助管理员及时了解系统的健康状况。
Redis Sentinel 的部署方式
为了确保 Redis 的高可用性,通常会部署多个 Sentinel 实例。通过部署多个 Sentinel 实例来保证当一个 Sentinel 实例出现故障时,其他 Sentinel 实例依然能够正常工作。
- 奇数数量的 Sentinel 实例:推荐部署奇数个 Sentinel 实例(例如 3 个、5 个等),这样可以通过多数票的方式来决定主节点是否发生故障。
- 独立的机器部署:为了避免单点故障,Sentinel 实例应该部署在不同的机器上。
Redis Sentinel 与 Redis Cluster 的区别
- 高可用性 vs. 分片:
- Redis Sentinel 主要提供的是单个 Redis 实例(主从复制)的高可用性,它的重点是监控和故障转移。
- Redis Cluster 提供的是分布式数据存储和分片机制,适用于大规模数据集。它通过将数据分布到不同的节点上来提高可扩展性,并且内置了故障转移机制。
- 复杂性:
Redis Cluster 相较于 Redis Sentinel 配置更加复杂,适用于更复杂的分布式场景,而 Redis Sentinel 更专注于故障转移和高可用性,不涉及数据分片。
总结
Redis Sentinel 通过提供监控、故障转移和通知等功能,确保 Redis 服务的高可用性。它在 Redis 集群中扮演着至关重要的角色,尤其是在处理主从切换、避免服务中断和自动化运维方面。通过合理的部署,Sentinel 能够帮助 Redis 保持高可用,并降低人工干预的需求。
Sentinel 如何检测节点是否下线?
Redis Sentinel 通过监控 Redis 实例(包括主节点和从节点)的健康状况来判断节点是否下线。它主要通过发送 PING 命令来检测节点的可用性,结合多次失败的响应、节点的心跳监测,以及 主观下线 和 客观下线 的投票机制来判断节点的状态。
具体来说,Sentinel 监控节点的过程包括:
定期发送 PING 命令:
Sentinel 会周期性地向 Redis 节点(主节点和从节点)发送 PING 请求,检查节点是否能够响应。每个 Sentinel 实例都在独立地执行此操作。响应时间判断:
如果某个 Redis 节点未能在规定时间内响应 Sentinel 的 PING 请求,Sentinel 会认为该节点存在异常。尝试检测故障:
如果节点无法响应 Sentinel 的 PING 命令,Sentinel 会尝试确定该节点是否真的不可用。这是通过 主观下线 和 客观下线 两个阶段来实现的。
主观下线与客观下线的区别
1. 主观下线 (Subjective Offline)
主观下线是 Sentinel 中一个初步的判断阶段。当 Sentinel 发现某个 Redis 节点无法响应 PING 命令时,它会将该节点标记为 主观下线,表示 Sentinel 自己认为该节点可能已经故障。
- 触发条件:
如果 Sentinel 在一定时间内没有收到来自某个节点的响应,它会将该节点标记为主观下线。 - 作用:
主观下线是 Sentinel 内部的初步状态,仅仅表示 Sentinel 认为该节点不可用,但其他 Sentinel 实例还没有投票确认。在这个阶段,主节点的故障状态并不意味着一定会进行故障转移。
2. 客观下线 (Objective Offline)
客观下线是通过 多数 Sentinel 实例的投票 确认主节点故障的过程。当一个节点被标记为主观下线后,其他 Sentinel 实例会参与投票,判断是否将该节点标记为 客观下线。
- 触发条件:
当 大多数 Sentinel 实例 都认为某个节点无法正常工作时,该节点会被正式标记为客观下线,表示它已经不可用了。 - 作用:
一旦节点被标记为客观下线,Sentinel 会触发 故障转移 机制,选择一个从节点来替代故障的主节点,保证 Redis 集群的高可用性。
过程总结
主观下线:
- 当 Sentinel 发现某个节点不可用时,会立即将该节点标记为 主观下线,但这只是 Sentinel 的单方面判断。
投票机制:
- 其他 Sentinel 实例会根据其监控结果参与投票。如果大多数 Sentinel 实例认为该节点故障,它会将该节点标记为 客观下线。
故障转移:
- 一旦节点被标记为客观下线,Sentinel 会启动故障转移,将一个从节点提升为主节点,确保服务不受影响。
主观下线和客观下线的区别
特性 | 主观下线 (Subjective Offline) | 客观下线 (Objective Offline) |
---|---|---|
定义 | Sentinel 自己认为节点不可用,但其他 Sentinel 实例未必认同 | 多数 Sentinel 实例认为节点不可用,达成一致 |
触发条件 | Sentinel 检测到节点无法响应 PING 命令 | 多数 Sentinel 实例认为节点不可用,形成共识 |
影响范围 | 仅在当前 Sentinel 实例内部有效 | 影响整个 Redis 集群,触发故障转移 |
状态变化 | 标记节点为主观下线,但不触发故障转移 | 标记节点为客观下线,触发故障转移 |
总结
- 主观下线 是 Sentinel 内部的初步状态,表示某个节点可能发生故障,但此时并没有得到整个 Sentinel 集群的确认。
- 客观下线 是经过多数 Sentinel 实例投票确认后,表示节点已经发生故障,Sentinel 会自动进行故障转移,确保 Redis 集群的高可用性。
Redis Sentinel:
Sentinel 如何实现故障转移
Sentinel 是阿里巴巴开源的分布式系统流量控制组件,主要用于服务降级、熔断、流量控制等功能。其故障转移机制通过结合流量控制、熔断和降级策略来确保系统在部分服务故障时能够尽量保持可用性。
1. 熔断与降级机制
Sentinel 的熔断机制基于流量指标来判断某个服务是否处于故障状态。当某个服务的调用出现异常(如响应时间过长、失败率过高等),Sentinel 会触发熔断规则,直接返回降级结果,避免将故障传播到其他服务。熔断规则包括:
- 失败比例:当请求失败率超过阈值时触发熔断。
- 响应时间:当请求的平均响应时间超过设定的时间阈值时触发熔断。
通过这些策略,Sentinel 能够在系统检测到故障时,及时断开连接,避免故障蔓延。
2. 流量控制
Sentinel 提供了流量控制机制,当系统负载过高时,可以对请求进行限流。流量控制可以基于:
- QPS(每秒请求数):限制每秒请求数,防止系统被过多请求压垮。
- 并发线程数:限制当前并发的线程数,避免系统资源被耗尽。
流量控制可以帮助系统在负载过高时,避免崩溃,从而间接实现故障转移。
3. 热点参数降级
Sentinel 还支持对特定请求参数进行降级。例如,当某个特定接口参数的访问量异常高,可能导致系统负载过重时,系统会优先进行参数级的降级处理,转而处理其它较为正常的请求。
4. 服务分组与流量隔离
Sentinel 支持对服务进行分组,进行流量隔离。通过为不同的服务设置流量控制、熔断等规则,可以在一个服务故障时,只影响该服务,不会影响到其他服务。
5. 多实例容灾
在集群模式下,Sentinel 会通过协调多个实例之间的状态来实现故障转移。如果某个实例发生故障,其他实例可以继续承担流量,确保系统的高可用性。
总结:
Sentinel 的故障转移主要是通过流量控制、熔断、降级等机制,在检测到系统故障时及时做出响应,避免服务故障的扩散,同时通过服务隔离和容灾策略实现高可用性。
为什么建议部署多个 Sentinel 节点(哨兵集群)
部署多个 Sentinel 节点(即哨兵集群)是为了提高 高可用性 和 容错性,并避免单点故障对整个系统的影响。以下是多个 Sentinel 节点的主要优势:
1. 高可用性
Sentinel 节点会以主从模式协同工作,其中一个 Sentinel 节点作为主节点,负责监控和管理规则,其他 Sentinel 节点作为从节点进行备份。如果主节点出现故障,从节点可以接管主节点的工作,保证系统的正常运行。
优点:
- 如果只有单个 Sentinel 节点,节点故障会导致整个流量控制、熔断等机制失效,影响系统的稳定性。
- 多个 Sentinel 节点可以互相备份,在主节点发生故障时,其他节点能够迅速接管,保证系统的持续运行。
2. 负载均衡
在多节点架构下,Sentinel 节点可以分担流量控制、规则检查等负载,避免单一节点处理大量请求时出现性能瓶颈。多个节点可以通过负载均衡的方式分担不同服务的流量控制请求,从而提高系统的整体性能和吞吐量。
优点:
- 单一节点可能成为性能瓶颈,影响服务响应速度和稳定性。
- 多个节点能够分担流量,提升系统的响应能力和吞吐量。
3. 容灾与灾难恢复
哨兵集群部署多个节点,可以实现灾难恢复(disaster recovery)。如果一个节点发生故障,集群中的其他节点会接管其职责,确保监控、流量控制、熔断等机制继续生效。
优点:
- 提高系统的容灾能力,防止由于单点故障导致整个流量控制的不可用。
- 在节点故障时,系统可以迅速恢复正常状态,最大程度地减少服务中断。
4. 数据一致性
哨兵集群可以通过分布式协调机制保持节点间的数据一致性。当多个 Sentinel 节点协同工作时,它们能够通过一致性协议(如心跳检测、主从同步等)保持各自的规则和状态一致。如果主节点的规则发生变更,从节点会及时同步这些变更。
优点:
- 保证集群中所有节点对流量控制、熔断等规则的理解是一致的,避免规则不一致导致的异常行为。
- 节点之间同步数据,确保系统的健康状态得到统一维护。
5. 弹性伸缩
在高并发、复杂业务场景下,单个 Sentinel 节点可能无法满足性能需求。通过部署多个 Sentinel 节点,可以灵活增加新的节点来处理更多流量,并通过负载均衡策略进行流量分发,提升系统的弹性伸缩能力。
优点:
- 可以根据实际流量和需求动态扩展集群中的 Sentinel 节点,满足高并发场景下的稳定性要求。
- 轻松应对流量波动,保证服务的高可用性。
6. 故障隔离
部署多个 Sentinel 节点有助于实现服务级别的故障隔离。即使某些节点或服务出现故障,其他节点依然可以继续工作,不会影响整个系统的健康状况。通过分布式部署,能够有效降低单点故障的风险。
优点:
- 通过隔离机制,避免故障在节点之间的传播。
- 能够更细粒度地控制流量和资源,降低全局故障的影响范围。
总结:
部署多个 Sentinel 节点(哨兵集群)能显著提高系统的 高可用性、性能、容灾能力 和 故障隔离能力,从而保证在复杂、动态的生产环境下,流量控制和熔断机制始终处于可用状态。这对于大规模、分布式系统至关重要。
Sentinel 如何选择出新的 Master(选举机制)
在 Sentinel 哨兵集群中,如果主节点(Master)出现故障或无法工作,其他 Sentinel 节点需要通过选举机制来选出新的主节点,确保系统能够继续处理流量控制、熔断、降级等操作。这个过程称为 Master 选举,主要是通过 哨兵节点之间的投票 来实现。
Sentinel 的 Master 选举机制基于以下几个步骤:
1. 心跳机制和故障检测
Sentinel 节点之间会定期通过心跳机制交换健康状态信息。如果一个 Sentinel 节点发现某个主节点长时间没有响应心跳,认为该主节点不可用(如宕机),就会触发故障转移流程。这是选举新主节点的前提条件。
- 心跳时间:每个 Sentinel 节点会定期向监控的主节点发送心跳请求,确保主节点是活跃的。
- 故障判定:如果超过了设定的心跳超时时间(
downAfterMillis
),哨兵节点认为该主节点不可用。
2. Master 节点故障发现
一旦 Sentinel 节点判断主节点不可用,它会标记该节点为 "故障" 状态,并将故障信息广播给集群中的其他 Sentinel 节点。其他节点通过收到故障通知,确认该主节点的状态。
3. 投票选举
在多个 Sentinel 节点确认主节点故障后,集群会发起选举过程来选出新的主节点。选举过程由以下几个步骤组成:
- 候选节点确定:通常情况下,故障转移的候选主节点是原来从节点中最新的、状态最健康的一个。Sentinel 会优先选择那些数据同步情况较好的从节点。
- 投票过程:所有哨兵节点参与投票,每个哨兵节点投票支持它认为最适合作为新主节点的从节点。选票依据通常是从节点的同步延迟、数据一致性等因素。
选举规则如下:
- 各个哨兵节点会对候选从节点投票,通常是投给它们认为数据最同步且健康的从节点。
- 为了确保选举结果的合理性,至少需要超过半数(过半数)哨兵节点投票支持某个从节点,才能将其选为新的主节点。
4. 新主节点的选举确认
一旦某个从节点获得超过半数的支持票,哨兵集群会将这个从节点提升为主节点。选举结束后,所有哨兵节点将会更新他们的配置,将新的主节点信息同步到所有节点中。
选举成功后,新的主节点开始接管流量,并且其他从节点开始同步新的主节点数据。
5. 通知系统其他部分
选举过程中,新的主节点选出后,Sentinel 集群会通知其他相关的系统和服务(如应用程序、客户端等),以便它们能正确地路由到新的主节点。Sentinel 会在集群内部维护最新的主节点信息。
6. 恢复与稳定性
如果原主节点重新恢复正常,它将成为从节点并同步新的主节点数据。在恢复过程中,原主节点不会重新自动成为主节点,除非手动配置或再次经过选举。
选举机制总结:
- 心跳机制:Sentinel 通过定期的心跳检测,判断主节点是否故障。
- 故障发现:如果主节点故障,Sentinel 会通过广播通知其他节点。
- 投票选举:所有 Sentinel 节点参与投票,支持数据最同步、健康的从节点成为新的主节点。
- 过半数规则:当超过半数的 Sentinel 节点支持某个从节点时,它被提升为新的主节点。
- 更新和通知:新主节点选举成功后,所有节点更新主节点信息,并通知其他系统。
这种基于投票机制的选举流程确保了在主节点故障时,集群能够稳定地选举出新的主节点,从而保证了系统的高可用性。
从 Sentinel 集群中选择出 Leader
在 Redis Sentinel 集群中,Leader 是指负责监控 Redis 主节点(master)的 Sentinel 实例。当 Sentinel 集群需要进行故障转移时,Leader 会协调其他 Sentinel 实例进行选举,并决定新的主节点。
要选择出当前的 Leader Sentinel,可以通过以下几种方式:
1. 使用 sentinel leaders
命令
Redis Sentinel 提供了一个 sentinel
命令,通过该命令可以查询当前哪个 Sentinel 是 Leader。
$ redis-cli -p <sentinel-port> sentinel get-leader <master-name>
<sentinel-port>
:你正在连接的 Sentinel 实例的端口。<master-name>
:你的 Redis 主节点名称,通常是你在 Sentinel 配置中定义的名称。
返回结果会显示当前的 Leader Sentinel 信息。
2. 查看 Sentinel 配置文件
如果你已经知道 Sentinel 实例的配置文件位置,可以通过查看配置文件来判断哪个 Sentinel 是 Leader。Leader Sentinel 通常会在配置中被标记为 "leader" 或类似的标识。
3. 使用 Redis INFO 命令
通过连接 Sentinel 实例并使用 INFO
命令,你可以查看 Sentinel 实例的详细信息,包括它是否为 Leader。
$ redis-cli -p <sentinel-port> info Sentinel
在返回的输出中,查找类似 leader
的字段,确认当前的 Leader Sentinel。
4. 使用 Redis Sentinel 的故障转移流程
当 Sentinel 集群执行故障转移时,Leader Sentinel 会自动协调选举过程。在故障转移过程中,你可以观察哪个 Sentinel 实例发起了选举操作,它就是 Leader。
总结
最常用的方式是使用 sentinel get-leader
命令,这个方法可以直接从 Sentinel 集群中获取当前的 Leader Sentinel 实例。
Sentinel 能否防止脑裂(Split Brain)?
脑裂(Split Brain) 是指在分布式系统中,由于网络分区或其他故障,集群中的不同节点无法相互通信,导致集群中的多个节点认为自己是主节点,从而产生数据不一致或行为异常的情况。这是分布式系统中的一种常见问题,尤其在主从复制或选举机制中尤为严重。
Sentinel 中如何避免脑裂?
在 Sentinel 中,避免脑裂的设计主要依赖于以下几个机制:
1. 选举过程中的过半数规则
Sentinel 的选举机制是基于 过半数规则(Quorum)。在发生主节点故障时,只有超过半数的 Sentinel 节点投票支持某个从节点,才能将其选举为新的主节点。这个过半数规则对于防止脑裂至关重要:
- 过半数的 Sentinel 节点投票是确保系统一致性的关键。如果网络分区导致部分 Sentinel 节点无法与其他节点通信,这些节点无法参与选举,因此不会影响选举结果。
- 只有当大多数 Sentinel 节点达成一致时,才会选举出新的主节点,这有效避免了由于网络分区而产生多个主节点的情况。
2. 心跳与健康检查
Sentinel 定期向主节点发送心跳消息,检查主节点是否仍然健康。在主节点故障的情况下,只有收到来自 多数 Sentinel 节点的故障确认,才会触发选举过程。如果网络分区,导致一部分 Sentinel 节点无法感知主节点的故障,它们将无法参与选举,避免了在网络分区的情况下产生多个主节点。
3. 网络分区与故障转移的延迟
Sentinel 的选举过程需要一定的时间。它不会在发生故障时立即进行选举,而是会等待一段时间来确认故障的可靠性。这种延迟能帮助避免网络分区的情况下发生错误的选举。
- 如果两个节点由于网络分区而分别认为自己是主节点,随着时间的推移,只有多数节点能参与选举,从而减少了脑裂的可能性。
4. 配置文件和故障转移
在选举过程中,Sentinel 会选择数据同步最优的从节点作为新的主节点。因为它会基于节点的同步延迟、健康状况等因素进行选择,而不是随便选择一个节点,这也有助于减少脑裂的风险,确保选举出的主节点具有较高的稳定性。
5. 故障恢复与自动化控制
一旦原主节点恢复,它会被降级为从节点,不会自动恢复为主节点。这种设计避免了在脑裂恢复后,原主节点和新主节点之间的冲突。通过这种自动化的控制机制,Sentinel 确保集群的健康性和一致性。
6. Sentinel 集群的数量和选举稳定性
为了进一步增强系统的可靠性,建议部署 多个 Sentinel 节点,至少部署 3 个或更多节点。这样即使发生网络分区,仍然能保证选举过程中有足够的投票支持某个从节点成为新的主节点,避免了由于分区导致的脑裂。
总结
Sentinel 的设计本身通过 过半数选举规则、健康检查、投票机制 和 延迟机制 有效减少了脑裂的风险。通过确保选举结果得到大多数节点的一致确认,Sentinel 能够在主节点故障时,避免因为网络分区产生多个主节点的情况,从而防止脑裂问题的发生。不过,在复杂的网络环境中,仍然建议部署足够数量的 Sentinel 节点,并确保网络稳定,以进一步降低脑裂的风险。
Redis Cluster:
为什么需要 Redis Cluster?
Redis 是一个高性能的键值数据库,但在高并发和大规模数据存储的场景下,单一 Redis 实例往往会面临性能瓶颈。随着数据量和访问量的增加,单台 Redis 实例的内存限制和单点故障的风险逐渐变得无法满足需求。为了扩展 Redis 的存储能力和提高系统的可用性,引入 Redis Cluster 就显得尤为重要。
Redis Cluster 提供了分布式的 Redis 解决方案,能够将数据分散存储在多个 Redis 实例上,解决了单一实例无法处理大规模数据的问题,同时提供高可用性和自动故障转移机制。
Redis Cluster 解决了什么问题?
数据水平扩展(Sharding)
Redis Cluster 可以将数据分布到多个节点上,每个节点只负责一部分数据。通过 数据分片(sharding) 技术,可以突破单一 Redis 实例内存限制,从而支持更大的数据量。高可用性与容错性
Redis Cluster 通过副本(replication)机制,实现了每个主节点的备份。如果主节点宕机,Cluster 会自动将其副本提升为新的主节点,确保服务持续可用。去中心化架构
Redis Cluster 采用去中心化的架构,没有单一的协调节点,所有节点都可以相互通信。每个节点都知道集群中其他节点的存在,能够独立地进行路由操作,这提高了集群的容错能力。自动故障转移(Failover)
在 Redis Cluster 中,当一个主节点发生故障时,其副本会自动成为新的主节点,系统会自动恢复,不需要人工干预。这减少了运维成本,提升了系统的稳定性。动态扩展
Redis Cluster 允许动态地添加和移除节点,通过重新平衡数据的方式,可以很容易地扩展或缩减集群容量。
Redis Cluster 的优势
高可用性
Redis Cluster 能够通过副本和故障转移机制,保证即使部分节点出现故障,整个集群仍然能够继续工作,提供高可用性。自动数据分片
Redis Cluster 会自动将数据按照哈希槽分配到各个节点中,无需手动配置分片。每个节点负责一部分数据,可以高效地存储和检索。容错和恢复能力
当节点故障时,Redis Cluster 会自动将副本提升为主节点,最大限度地减少故障对业务的影响。同时,客户端会自动重试故障节点的请求。扩展性
由于 Redis Cluster 支持动态增加或减少节点,可以根据业务需求扩展集群容量,而不需要停机维护。分布式锁支持
Redis Cluster 可以用于实现分布式锁,保证分布式环境下资源的互斥访问。无单点故障
Redis Cluster 的去中心化架构意味着集群中没有单点故障的风险。每个节点都可以独立工作,避免了传统主从架构中主节点宕机导致的系统不可用问题。
总结
Redis Cluster 主要解决了单一 Redis 实例的性能瓶颈和可用性问题,通过数据分片、主从复制、自动故障转移等机制,提供了一个高性能、高可用的分布式 Redis 解决方案,适用于需要处理大量数据和高并发请求的应用场景。
Redis Cluster 如何分片
Redis Cluster 使用 哈希槽(hash slots)机制来实现数据的分片。整个集群的键(key)被均匀分配到多个节点上,而每个节点只负责一部分哈希槽。Redis Cluster 提供了一种自动化的方式来处理数据分布,使得数据能够分散到集群中的不同节点上,从而实现水平扩展。
1. 哈希槽(Hash Slots)
Redis Cluster 会将键值对(key-value)映射到 16384 个哈希槽 中。这是一个固定的数量,每个槽在集群内被分配到一个节点,节点只负责处理自己分配的哈希槽范围的请求。
哈希槽的数量:Redis Cluster 设计了 16384 个哈希槽,这意味着所有的键会通过哈希计算映射到这 16384 个槽中。
哈希算法:当你向 Redis Cluster 存储数据时,Cluster 会通过对键进行哈希计算(通常是 CRC16 算法)来决定该键应该存储在哪个哈希槽中。
计算方式:
哈希槽 = CRC16(键) % 16384
通过这个哈希算法,Redis 可以确保每个键总是映射到相同的哈希槽上,即使是在集群扩展的过程中也能保证键值对的正确分配。
2. 数据分配到不同节点
每个节点在 Redis Cluster 中负责一部分哈希槽。假设集群有 6 个节点,那么这些 16384 个哈希槽会被均匀地分配到 6 个节点上,具体分配方式如下:
- 假设集群中有 6 个节点,每个节点会负责大约 16384 / 6 ≈ 2730 个哈希槽。
- 每个节点的工作范围就是自己负责的哈希槽,比如一个节点可能负责哈希槽 0 到 2729,另一个节点负责 2730 到 5459,以此类推。
每当有请求发送到 Redis Cluster 时,集群会根据请求的键计算哈希槽,并通过 哈希槽映射表 找到对应的节点来处理这个请求。
3. 节点的增减与哈希槽的重新分配
Redis Cluster 支持 动态扩展,当集群需要增加或减少节点时,哈希槽会重新分配给新节点或者移除节点。例如:
- 增加节点:如果一个新节点被添加到集群中,Redis Cluster 会将一些哈希槽从其他节点迁移到新节点,保证数据的均衡分布。
- 移除节点:如果一个节点被移除,负责该节点的哈希槽会被重新分配给集群中的其他节点,确保数据不会丢失。
在节点增加或减少时,Redis Cluster 会自动重新平衡哈希槽的分布,而客户端通常不需要感知这些变化。Redis Cluster 通过对集群的元数据进行更新,确保客户端能够正确访问集群中的数据。
4. 特殊情况:键之间的关系
如果两个键需要一起存储在同一节点上(例如,它们之间需要进行原子操作),Redis Cluster 提供了一个 分片键(hash tag)机制来确保这些键落在同一个哈希槽内。
分片键:通过在键名中指定一个 哈希标记(hash tag),可以确保多个键被哈希到同一个槽。哈希标记是通过
{}
括起来的部分来指定的,Redis 会使用{}
内的内容来计算哈希槽,而忽略{}
外的部分。例如:
key1 = "user:{1234}:name" key2 = "user:{1234}:email"
这里
key1
和key2
都使用了相同的{1234}
标记,因此它们会被映射到同一个哈希槽,无论 Redis Cluster 中有多少节点。
5. 客户端与 Redis Cluster 的交互
客户端在访问 Redis Cluster 时,需要了解数据分布的情况。由于每个节点只负责一部分哈希槽,客户端需要知道键所属的哈希槽并向正确的节点发起请求。为了简化这一过程,Redis Cluster 提供了以下机制:
- 节点信息获取:当客户端连接到一个节点时,它会得到集群中所有节点的信息,包括哪些哈希槽由哪个节点负责。这样,客户端就能根据哈希槽选择正确的节点进行访问。
- 重定向:如果客户端向错误的节点发起请求,集群会返回一个重定向命令 (
MOVED
),告诉客户端该请求应该发送到哪个节点。客户端收到重定向信息后,会自动向正确的节点重新发起请求。
总结
Redis Cluster 使用哈希槽(16384 个)来将数据分配到多个节点上,从而实现数据的分片。每个节点负责一部分哈希槽,并通过哈希算法确定每个键对应的槽。Redis Cluster 的设计能够确保数据的均匀分布、故障恢复和水平扩展,并且支持动态扩展和节点变更。
为什么 Redis Cluster 的哈希槽是 16384 个?
Redis Cluster 设计时选择了 16384 个哈希槽,这一数值并非随意选择,而是基于以下几个原因来进行的权衡:
1. 哈希槽数量与性能的平衡
Redis Cluster 将键映射到 16384 个哈希槽中。选择 16384 个槽是为了在 性能 和 分布均衡 之间找到一个良好的平衡点:
- 如果哈希槽太少,分配给每个节点的哈希槽就会过多,导致节点负载不均衡,数据分布不充分。
- 如果哈希槽太多,系统需要更多的计算和存储来维护这些槽的元数据,从而影响集群的性能和复杂度。
16384 这个数字使得每个节点可以相对平均地分配哈希槽,同时也确保了集群可以扩展到较大的规模而不会造成显著的性能开销。
2. 与 16 位哈希值的匹配
Redis Cluster 的哈希槽计算是基于 CRC16 哈希算法,生成的哈希值是 16 位的。因此,16384 个哈希槽与 16 位哈希值非常契合,因为:
- 16 位哈希值的最大值是
2^16 = 65536
。 - 16384 是
65536
的一个小的合理分割因子,能够更高效地分配和管理哈希槽。
从数学上讲,16384 是 2^14
,使得哈希槽数量与 Redis 集群中的数据分布和负载均衡非常匹配,同时便于通过位操作来快速计算槽号。
3. 支持节点扩展和数据均衡
如果哈希槽数量过少,数据分布就会不够精细,导致集群在增加或减少节点时,负载会过于集中在部分节点上。选择 16384 使得在集群节点数目较少时,仍能保持数据的均匀分布;而随着节点数量增加,数据能够更平滑地进行迁移和分配,避免了单个节点承载过重负担的问题。
4. 哈希槽迁移的效率
在 Redis Cluster 中,增加节点时需要重新分配部分哈希槽。如果哈希槽数量过多,会使得迁移和重新分配的成本增加。16384 个哈希槽的数量适中,使得哈希槽迁移的开销合理,既能保持数据均匀分布,又不会因为哈希槽过多导致频繁的负载迁移,进而影响性能。
5. 兼容性的考虑
16384 这个数字和一些其他分布式系统中常见的哈希槽数量(比如某些分布式数据库或键值存储)是相对接近的,因此这种设计具有一定的行业兼容性。它在分布式环境中能够广泛应用且符合许多技术架构的习惯。
总结
Redis Cluster 选择 16384 个哈希槽是一个经过设计权衡后的结果,主要目的是为了确保数据的均匀分布、负载平衡、高效的节点扩展与迁移,同时使得哈希槽计算简单高效。这个数量既能保证集群的性能,也能适应集群规模的动态变化。
如何确定给定 Key 应该分布到哪个哈希槽中?
在分布式缓存或数据库系统中,数据需要分布到不同的节点上以实现水平扩展(sharding)。这种分布通常是基于 哈希槽(Hash Slot) 的概念,常见于像 Redis Cluster 这样的分布式系统。
每个键(Key)都会被映射到一个哈希槽,然后该哈希槽决定了该键存储在哪个节点上。以下是如何确定给定 key 应该分布到哪个哈希槽中的详细说明。
1. 哈希槽的总数
在 Redis Cluster 中,哈希槽的数量是固定的,通常是 16384 个哈希槽。这个数量可以在不同的实现中有所变化,但在 Redis Cluster 中默认是 16384。
2. 哈希函数(Hash Function)
确定 key 分配到哪个哈希槽,通常通过对 key 的哈希值进行处理,下面是 Redis Cluster 使用的常见方法:
2.1 计算哈希值
对于给定的 key,首先要计算它的哈希值。Redis 使用的哈希算法是 MurmurHash2,这是一种高效且均匀分布的哈希算法。
2.2 哈希槽映射
将 key 的哈希值映射到哈希槽,具体方法是使用取余操作。计算方式如下:
slot = hash(key) % 16384
hash(key)
是通过哈希算法计算得到的 key 的哈希值。% 16384
是将哈希值映射到哈希槽范围内的一个步骤,因为总共有 16384 个哈希槽。
这样,通过哈希算法和哈希槽数量的取余操作,可以将任何给定的 key 映射到 0 到 16383 之间的一个哈希槽。
3. 使用哈希标签(Hash Tag)
为了在特定条件下控制数据如何分布,有时会使用 哈希标签。哈希标签允许你将多个键“强制”分配到同一个哈希槽。
哈希标签是指键中 大括号
{}
中的部分,例如,假设有以下两个键:user:1000
user:2000
如果我们想确保这两个键分配到同一个哈希槽,可以将它们都修改为相同的哈希标签:
user{1000}
user{2000}
在这种情况下,Redis 只会使用
{}
括起来的部分(如1000
和2000
),而忽略键的其余部分。这样,user{1000}
和user{2000}
会被映射到相同的哈希槽。
哈希标签的作用是控制数据的分布,以便同一组相关数据可以被存储在同一个节点上,从而避免在查询时需要跨节点访问。
4. 哈希槽与节点映射
Redis Cluster 会将这些哈希槽均匀分配到多个节点中。每个节点负责一部分哈希槽。通过确定每个哈希槽的映射关系,Redis Cluster 可以将数据存储到不同的节点。
总结:
- 哈希槽数量:Redis Cluster 默认使用 16384 个哈希槽。
- 哈希函数:通过对给定 key 进行哈希计算,得到哈希值。
- 哈希槽映射:通过取余操作
hash(key) % 16384
将 key 映射到对应的哈希槽。 - 哈希标签:使用
{}
包裹的部分来确保同类数据分配到同一个哈希槽。
通过这些机制,Redis Cluster 确保了数据的均匀分布,同时还可以灵活地控制特定数据的分布。
Redis Cluster 是否支持重新分配哈希槽?
是的,Redis Cluster 支持 重新分配哈希槽,这也是 Redis Cluster 设计中的一个重要特性。这个功能允许集群根据需求进行 动态扩展 或 缩减,以应对集群规模变化和负载均衡问题。
如何进行哈希槽重新分配?
当 Redis Cluster 需要添加或删除节点时,哈希槽的分配会发生变化。Redis Cluster 会自动将一部分哈希槽从某些节点迁移到其他节点,以确保数据的均衡分布。这一过程通常是透明的,不会对客户端产生显著的影响。
1. 增加节点时的哈希槽迁移
当集群需要 增加一个新节点 时,Redis Cluster 会从现有节点中选择一部分哈希槽,将它们迁移到新加入的节点上。通过这种方式,Redis 集群可以均匀地分配负载,避免某些节点过载。
步骤:
- 集群会选择一些现有节点负责的哈希槽,将它们分配给新节点。
- 集群会在后台自动迁移这些哈希槽的数据,确保数据不丢失。
- 客户端会收到 MOVED 重定向命令,指导客户端访问正确的节点。
这可以通过 Redis Cluster 提供的管理命令(如
CLUSTER ADDSLOTS
)来实现。
2. 删除节点时的哈希槽迁移
当集群需要 删除一个节点 时,Redis Cluster 会将该节点负责的哈希槽重新分配到其他节点。这个过程也会保持数据的一致性和可用性。
- 步骤:
- 集群会选取需要迁移的哈希槽,并将它们分配给集群中的其他节点。
- 然后,Redis Cluster 会开始迁移数据,确保目标节点接管这些槽的数据。
- 客户端同样会收到重定向命令,确保请求能够转发到新的节点。
这种情况下,可以通过 CLUSTER DELSLOTS
命令来从集群中移除某些哈希槽。
3. 哈希槽迁移的管理
Redis Cluster 会通过以下命令来管理哈希槽迁移:
- CLUSTER ADDSLOTS:将指定的哈希槽分配给一个节点。
- CLUSTER DELSLOTS:从一个节点移除指定的哈希槽。
- CLUSTER SETSLOT:设置一个哈希槽的状态,可以用来手动管理某些槽的分配状态。
- CLUSTER INFO:显示集群的状态和哈希槽分布情况。
这些命令用于集群管理时,可以手动调整哈希槽分配,确保集群的负载均衡。
4. 自动负载均衡
Redis Cluster 在某些情况下会通过 自动平衡机制 重新分配哈希槽,尤其是在节点状态发生变化时(如节点失效或加入新的节点)。这个过程由 Redis Cluster 自动处理,无需人工干预,确保数据始终保持均匀分布。
重新分配哈希槽的影响
- 数据迁移:重新分配哈希槽的过程中,集群需要进行数据迁移。虽然数据迁移是自动的,但可能会对性能产生一定影响,特别是在大规模数据迁移时。
- 客户端重定向:当哈希槽分配发生变化时,客户端会收到 MOVED 错误信息,并会自动被重定向到新的节点。客户端需要处理这些重定向,以确保数据访问不受影响。
总结
Redis Cluster 确实支持哈希槽的重新分配,尤其在节点扩展或缩减时。这个功能确保了集群能够在节点变化时动态地调整哈希槽分布,从而实现数据的均衡分配和负载均衡。哈希槽的迁移是自动进行的,且 Redis Cluster 会通过透明的方式来处理数据的迁移和客户端的重定向。
Redis Cluster 扩容缩容期间是否可以提供服务?
在 Redis Cluster 中进行扩容或缩容时,是否能持续提供服务,取决于具体的操作和集群的状态。Redis Cluster 设计时考虑了高可用性和无停机的扩展性,理论上是可以在集群扩容和缩容过程中持续提供服务的,但存在一些关键点和限制。
1. 扩容(增加节点)
扩容是指向现有的 Redis 集群中添加新的节点。扩容过程中,Redis Cluster 需要重新分配哈希槽以均匀地分布数据到新的节点上。虽然 Redis Cluster 允许在扩容时继续处理请求,但扩容过程涉及的细节如下:
扩容步骤:
- 添加节点:你可以将新的 Redis 节点加入到集群中,Redis 会自动识别并开始进行哈希槽的重新分配。
- 数据迁移:Redis 会将部分哈希槽从现有节点迁移到新的节点上。这个过程是渐进的,Redis 会迁移数据而不影响集群的可用性。
- 重新分配哈希槽:在扩容期间,Redis 会重新计算哪些哈希槽应该迁移到新节点,并将数据转移到这些槽对应的节点上。
扩容期间的服务可用性:
- 可以提供服务:Redis 集群可以在扩容期间继续处理客户端请求。因为扩容过程是渐进的,不会导致集群不可用。
- 性能波动:虽然服务是可用的,但在扩容过程中,节点之间的通信和数据迁移会对集群的性能产生一定的影响,可能会导致延迟的增加,特别是在迁移大量数据时。
- 部分节点不可用:在某些情况下,如果集群中的节点数减少到不足以满足故障容忍度(比如副本不足),则可能会影响集群的高可用性。
2. 缩容(移除节点)
缩容是指从 Redis 集群中移除节点。与扩容相比,缩容操作可能会对服务的可用性产生更大的影响,尤其是在移除节点时需要重新分配哈希槽并确保数据不丢失。
缩容步骤:
- 移除节点:从集群中移除一个或多个节点。
- 数据迁移:Redis 会将从被移除节点上存储的数据迁移到其他节点,确保没有数据丢失。
- 重新分配哈希槽:类似于扩容,Redis 会将哈希槽从被移除节点迁移到集群中的其他节点。
缩容期间的服务可用性:
- 可以提供服务:如果移除节点前已经做好了数据迁移,且集群的副本机制能保证数据的高可用性,Redis 集群在缩容过程中仍然可以提供服务。
- 性能波动:缩容过程中,同样会有数据迁移,可能会导致集群的性能出现波动。
- 潜在的风险:如果在移除节点时集群的副本数不足,或者迁移的数据量过大,可能会导致某些请求的延迟增加,或者短暂的不可用。
3. 节点故障与容错机制
在扩容或缩容过程中,Redis 集群会确保至少有一个副本存在,以避免数据丢失。如果有多个副本并且分片均匀,集群将能够继续提供服务。
- 故障转移:Redis 集群能够自动进行故障转移。即使在缩容过程中,若有副本节点被移除,Redis 会尽量保证数据的安全。
- 保证副本一致性:扩容或缩容过程中,Redis 会确保副本节点的数据同步到主节点。
4. 客户端的连接与请求处理
在扩容或缩容期间,客户端依然可以与集群交互,Redis 集群会处理请求并根据新的哈希槽分布进行路由:
- Slot 移动通知:集群中的节点会定期与客户端交换槽迁移信息,以便客户端能够更新路由表。
- 不影响客户端:对于客户端来说,集群扩容和缩容是透明的,客户端只需知道集群的变化即可(例如,使用 Redis Cluster 客户端会自动感知新的节点和槽的变化)。
总结
- 扩容期间:Redis 集群可以提供服务,但在数据迁移时可能会出现一定的性能波动。扩容过程通常是渐进的,不会导致集群停机。
- 缩容期间:Redis 集群也可以提供服务,但在移除节点时需要确保副本数充足且数据迁移顺利,否则可能会影响服务的可用性或性能。
- 高可用性保障:在扩容和缩容过程中,Redis 集群的高可用性机制(如副本、故障转移)可以确保数据不丢失,并且集群能够继续处理请求。
总体来说,Redis Cluster 在扩容和缩容过程中是 可以继续提供服务的,但会有一定的性能波动,特别是在大规模数据迁移和节点数目不足的情况下。
Redis Cluster 是否支持重新分配哈希槽?
是的,Redis Cluster 支持 重新分配哈希槽,这也是 Redis Cluster 设计中的一个重要特性。这个功能允许集群根据需求进行 动态扩展 或 缩减,以应对集群规模变化和负载均衡问题。
如何进行哈希槽重新分配?
当 Redis Cluster 需要添加或删除节点时,哈希槽的分配会发生变化。Redis Cluster 会自动将一部分哈希槽从某些节点迁移到其他节点,以确保数据的均衡分布。这一过程通常是透明的,不会对客户端产生显著的影响。
1. 增加节点时的哈希槽迁移
当集群需要 增加一个新节点 时,Redis Cluster 会从现有节点中选择一部分哈希槽,将它们迁移到新加入的节点上。通过这种方式,Redis 集群可以均匀地分配负载,避免某些节点过载。
步骤:
- 集群会选择一些现有节点负责的哈希槽,将它们分配给新节点。
- 集群会在后台自动迁移这些哈希槽的数据,确保数据不丢失。
- 客户端会收到 MOVED 重定向命令,指导客户端访问正确的节点。
这可以通过 Redis Cluster 提供的管理命令(如
CLUSTER ADDSLOTS
)来实现。
2. 删除节点时的哈希槽迁移
当集群需要 删除一个节点 时,Redis Cluster 会将该节点负责的哈希槽重新分配到其他节点。这个过程也会保持数据的一致性和可用性。
- 步骤:
- 集群会选取需要迁移的哈希槽,并将它们分配给集群中的其他节点。
- 然后,Redis Cluster 会开始迁移数据,确保目标节点接管这些槽的数据。
- 客户端同样会收到重定向命令,确保请求能够转发到新的节点。
这种情况下,可以通过 CLUSTER DELSLOTS
命令来从集群中移除某些哈希槽。
3. 哈希槽迁移的管理
Redis Cluster 会通过以下命令来管理哈希槽迁移:
- CLUSTER ADDSLOTS:将指定的哈希槽分配给一个节点。
- CLUSTER DELSLOTS:从一个节点移除指定的哈希槽。
- CLUSTER SETSLOT:设置一个哈希槽的状态,可以用来手动管理某些槽的分配状态。
- CLUSTER INFO:显示集群的状态和哈希槽分布情况。
这些命令用于集群管理时,可以手动调整哈希槽分配,确保集群的负载均衡。
4. 自动负载均衡
Redis Cluster 在某些情况下会通过 自动平衡机制 重新分配哈希槽,尤其是在节点状态发生变化时(如节点失效或加入新的节点)。这个过程由 Redis Cluster 自动处理,无需人工干预,确保数据始终保持均匀分布。
重新分配哈希槽的影响
- 数据迁移:重新分配哈希槽的过程中,集群需要进行数据迁移。虽然数据迁移是自动的,但可能会对性能产生一定影响,特别是在大规模数据迁移时。
- 客户端重定向:当哈希槽分配发生变化时,客户端会收到 MOVED 错误信息,并会自动被重定向到新的节点。客户端需要处理这些重定向,以确保数据访问不受影响。
总结
Redis Cluster 确实支持哈希槽的重新分配,尤其在节点扩展或缩减时。这个功能确保了集群能够在节点变化时动态地调整哈希槽分布,从而实现数据的均衡分配和负载均衡。哈希槽的迁移是自动进行的,且 Redis Cluster 会通过透明的方式来处理数据的迁移和客户端的重定向。
Redis Cluster 中的节点如何进行通信
在 Redis Cluster 中,节点之间需要进行频繁的通信,以确保数据一致性、健康检查、哈希槽分配、故障转移等功能的实现。Redis Cluster 的节点通信采用了专门的协议和通信方式,确保了集群的高效运行。
1. 通信协议
Redis Cluster 节点之间的通信是基于 Redis 自定义的 Cluster协议,该协议与普通的 Redis 协议有所不同,具有以下特点:
- 消息格式:Redis Cluster 节点之间使用特定的消息格式进行通信。这些消息包含了集群状态、槽分配、主从关系等信息。
- 通信通道:Redis Cluster 节点之间通过 TCP 连接进行通信。每个节点通过与其他节点建立持久化的 TCP 连接来交换数据。
2. 节点通信的主要功能
Redis Cluster 节点之间的通信主要涉及以下几个方面:
2.1 槽信息同步
Redis Cluster 的数据通过哈希槽分配到不同的节点,每个节点负责一定范围的哈希槽。为了确保哈希槽的映射关系和节点的数据一致性,节点之间需要同步哈希槽信息。
- 当集群发生扩容、缩容或故障转移时,节点之间会交换哈希槽的信息,确保每个节点知道当前哪些槽由自己负责,哪些槽由其他节点负责。
- 集群管理信息(Cluster state):集群会将每个节点的槽分配情况广播到所有节点,确保各个节点始终拥有最新的槽分配状态。
2.2 健康检查与心跳
节点之间通过发送心跳消息来检查彼此的健康状况。每个节点定期向其他节点发送心跳(ping)请求,以确认它们是否在线,确保集群成员的状态是最新的。
- 如果一个节点长时间没有回应心跳请求,或者无法访问该节点,其他节点会将其标记为故障节点,触发 故障转移 流程。
- 健康检查还包括节点的 副本状态,确保主从节点之间的数据同步情况。
2.3 主从复制和数据同步
在 Redis Cluster 中,数据的主从复制非常重要。主节点(Master)将数据复制到从节点(Replica)。节点之间会交换数据,以确保从节点同步主节点的数据。
- 如果主节点发生故障,集群会选择一个健康的从节点进行故障转移,新的主节点会替代原主节点的角色。
- 在正常运行时,节点间的通信会确保数据从主节点同步到其从节点。
2.4 故障转移和重新分配槽
当集群的某个节点出现故障时,其他节点会相互通信,协作进行故障转移。根据 过半数投票机制,集群会选举出新的主节点并重新分配其负责的哈希槽。
- 故障检测时,节点会相互广播故障信息。比如,如果一个节点无法与主节点通信,它会向其他节点报告故障情况。
- 故障转移完成后,新的主节点会承担故障节点原来负责的哈希槽,并更新其他节点的槽分配信息。
2.5 数据请求路由
当客户端向某个节点发起请求时,该节点可能无法直接处理请求(例如,客户端请求的键对应的哈希槽由其他节点负责)。此时,节点会通过集群协议将请求转发给负责该哈希槽的节点。
- 如果请求的键在本节点的哈希槽范围内,则直接处理请求。
- 如果请求的键不在本节点的哈希槽范围内,节点会返回一个
MOVED
响应,指示客户端将请求重定向到正确的节点。
2.6 数据迁移
在 Redis Cluster 扩容、缩容或者进行负载均衡时,节点之间需要进行 数据迁移。这意味着某些哈希槽的数据需要从一个节点迁移到另一个节点,保证集群中各节点的负载均衡。
- 节点之间会通过 迁移协议 进行数据的传输。数据迁移通常是通过复制操作来实现,即源节点先将数据复制到目标节点,确保数据的一致性,然后删除源节点的数据。
3. 节点之间的通信方式
- TCP 连接:每个 Redis Cluster 节点都保持与其他节点的持久 TCP 连接。这些连接用于节点之间的通信,不同于客户端和节点之间的连接。
- 协议格式:节点之间通信的数据格式与普通的 Redis 协议略有不同。它包含集群特有的信息,如节点的状态、槽分配等。节点会发送特定的命令来交换集群信息,如
CLUSTER MEET
(通知新节点加入集群)、CLUSTER INFO
(获取集群状态)等。
4. 集群节点通信的安全性
Redis Cluster 本身没有内建的加密或认证机制来保护节点之间的通信,因此集群节点之间的通信是明文的。如果需要安全性较高的集群环境,通常需要通过 VPN、加密隧道(如 SSL/TLS)等方式加密节点间的通信。
5. 节点之间的协作和一致性
- 共识机制:Redis Cluster 不使用传统的分布式一致性协议(如 Paxos 或 Raft),而是通过 过半数投票 和 节点协作 来确保集群状态的一致性。例如,在故障转移时,集群中的大多数节点需要投票支持某个从节点成为新的主节点。
- 最终一致性:尽管 Redis Cluster 会尽力保证数据一致性,但它使用的是 最终一致性 模型,这意味着集群中的节点在某些情况下可能会出现短暂的不一致,但会在一定时间内最终达到一致。
总结
Redis Cluster 中的节点通过 自定义的 Cluster 协议 使用 TCP 连接 进行频繁的通信。节点之间的通信主要用于:
- 同步哈希槽的分配信息。
- 健康检查和故障检测。
- 主从复制和数据同步。
- 请求路由和数据迁移。
这种通信机制确保了 Redis Cluster 的高可用性、负载均衡和容错性,使集群能够处理节点扩容、缩容、故障转移等操作时,仍能保持服务可用性。
Redis 使用规范
实际使用 Redis 的过程中,我们尽量要准守一些常见的规范,比如:
- 使用连接池:避免频繁创建关闭客户端连接。
- 尽量不使用 O(n)指令,使用 O(n) 命令时要关注 n 的数量:像
KEYS *
、HGETALL
、LRANGE
、SMEMBERS
、SINTER
/SUNION
/SDIFF
等 O(n) 命令并非不能使用,但是需要明确 n 的值。另外,有遍历的需求可以使用HSCAN
、SSCAN
、ZSCAN
代替。 - 使用批量操作减少网络传输:原生批量操作命令(比如
MGET
、MSET
等等)、pipeline、Lua 脚本。 - 尽量不适用 Redis 事务:Redis 事务实现的功能比较鸡肋,可以使用 Lua 脚本代替。
- 禁止长时间开启 monitor:对性能影响比较大。
- 控制 key 的生命周期:避免 Redis 中存放了太多不经常被访问的数据。
- ……
相关文章推荐:阿里云 Redis 开发规范 。
参考
- 《Redis 开发与运维》
- 《Redis 设计与实现》
- Redis Transactions : https://redis.io/docs/manual/transactions/
- What is Redis Pipeline:https://buildatscale.tech/what-is-redis-pipeline/
- 一文详解 Redis 中 BigKey、HotKey 的发现与处理:https://mp.weixin.qq.com/s/FPYE1B839_8Yk1-YSiW-1Q
- Bigkey 问题的解决思路与方式探索:https://mp.weixin.qq.com/s/Sej7D9TpdAobcCmdYdMIyA
- Redis 延迟问题全面排障指南:https://mp.weixin.qq.com/s/mIc6a9mfEGdaNDD3MmfFsg