笔记不易,如果喜欢点个关注吧
底层数据结构
Redis 解决哈希冲突的方式,就是链式哈希
如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。 为了避免这个问题,Redis 采用了渐进式 rehash。 简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求 时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的。
压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同的是,压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
有序链表只能逐一查找元素,导致操作起来非常缓慢,于是就出现了跳表。具体来说,跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。
持久化机制
AOF
Redis 是先执行命令,把数据写入内存,然后才记录日志。AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。在命令执行后才记录日志,不会阻塞当前的写操作。
潜在的风险
- 执行完命令,没有来得及记日志就宕机,那么这个命令和相应的数据就有丢失的风险。
- AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的。
三种写回策略
解决AOF文件过大带来的性能问题
- 文件系统对文件大小有限制,无法保存过大的文件。
- 文件过大追加命令效率变低。
- 发生宕机,使用AOF记录的命令去恢复,如果文件过大,恢复过程缓慢。
AOF重写机制
简单点就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件。和 AOF 日志由主线程写回不同,重写过程是由后台线程 bgrewriteaof 来完成的,避免阻塞主线程导致数据库性能下降。
RDB
记录的是某一时刻的数据而不是操作,在做数据恢复时,可以直接把 RDB 文件读入内存,很快完成恢复操作。Redis的数据都在内存中,为了提供所有数据的可靠性保证,执行的是**全量快照**,把内存中的所有数据都记录到磁盘中。
Redis 提供了两个命令来生成 RDB 文件
- save:在主线程中执行,会导致阻塞。
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这是RDB 文件生成的默认配置。
如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C), 那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
可以每秒做一次快照吗?
虽然 bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量快照,也会带来两方面的开销。
- 频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽
- fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。
一般使用增量快照。就是指做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
AOF和RDB选择问题
数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择; 如果允许分钟级别的数据丢失,可以只使用 RDB; 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
主从同步
主从库间数据第一次同步的三个阶段
** 通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力, 以级联的方式分散到从库上。
一旦主从库完成了全量复制,它们之间就会一 直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播**,可以避免频繁建立连接的开销。
主从库间网络断了怎么办?
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也
会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。 repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。
因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。
一般而言,我们可以调整 repl_backlog_size 这个参数。这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即
repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。
哨兵集群
场景
哨兵集群某个实例出现故障,其他哨兵完成主从切换工作(判断主库是否下线、选择新主库、通知从库以及客户端)
主观下线和客观下线
** 哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态**。如果哨兵发现主库或从库对 PING 命令的响应超时,哨兵就会先把它标记为“主观下线”。如果检测的是主库,那么,哨兵还不能简单地把它标记为“主观下线”。
哨兵机制也是类似的,它通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
“客观下线”的标准就是,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”。
选定新主库
先按照**一定的筛选条件**,把不符合条件的从库去掉。然后,我们再按照**一定的规则**,给剩下的从库逐个打分,将得分最高的从库选为新主库。在选主时,**除了要检查从库的当前在线状态,还要判断它之前的网络连接状态(**down-after-milliseconds**)。**
** **剩余从库打分筛选条件
- 优先级最高的从库得分高
- 和旧主库同步程度最接近的从库得分高
- ID 号小的从库得分高
切片集群
场景
用Redis保存5000万键值对,每个键值对约512B(5000万 * 512B)为了能对外提供服务,采用云主机来运行Redis实例,那么该如何选择云主机的内存容量?
方案一
采用选择一台32GB(或者更高)内存的云足迹来部署Redis。同时采用RDB对数据做持久化,以确保Redis实例故障后能从RDB恢复数据。
缺点:Redis响应会有点慢。通过INFO命令查看Redis的latest_fork_usec指标(最近一次fork的耗时),结果到秒级。这个跟持久化有关系。在使用RDB进行持久化时,Redis会fork子进程来完成,fork操作的用时和Redis的数据量是正相关的,fork在执行时会阻塞主线程,数据量越大,fork操作的主线程阻塞的时间越长。就会导致Redis响应变慢
方案二
切片集群也叫分片集群,就是启动多个Redis实例组成一个集群,按照一定规则,把收到的数据划分成多份,每一份用一个实例来保存。按照刚才的场景,可以如下图所示。
优点:与纵向扩展相比,横向扩展是拓展性更好的选择方案。这是因为,要想保留更多的数据,采用这种方案的话,只增加Redis实例个数就可以了,不用担心单个实例的硬件和成本限制。
这种方式切引入了新的问题。
- 数据切片后在多个实例之间如何分布
- 客户端怎么确定想要访问的数据在哪个实例上
Redis3.0之后使用Redis Cluster方案实现切片集群。在Redis Cluster的方案中,一个切片集群共有16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的key被映射到一个hash槽中。使用 cluster create 命令创建集群,此时,Redis会自动把这些槽平均分布在集群实例上。例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。假设集群中不同 Redis 实例的内存大小配置不一,如果把哈希槽均分在各个实例上,在保存相同数量的键值对时,和内存大的实例相比,内存小的实例就会有更大的容量压力。遇到这种情况时,可以根据不同实例的资源配置情况,使用 cluster addslots命令手动分配哈希槽。如下图所示,分配哈希槽的过程如下。
通过切面集群,实现了数据到哈希槽、哈希槽再到实例的分配。但是即使实例有了哈希槽的映射信息,客户端如何知道数据在哪个实例上呢?
客户端定位数据
一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实
例拥有的哈希槽信息的。Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。
集群中Redis实例新增/删除,需重新分配哈希槽,为负载均衡,需要把哈希槽在所有实例上重新分布一遍。客户端无法感知变化,会通过一种重定向机制实现。
以上是MOVED命令响应流程。
在实际应用时,如果 Slot 2 中的数据比较多,就可能会出现一种情况:客户端向实例 2 发送请求,但此时,Slot 2 中的数据只有一部分迁移到了实例 3,还有部分数据没有迁移。
在这种迁移部分完成的情况下,客户端就会收到一条ASK报错信息,ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。
MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息。
Redis性能的影响因素
单线程的阻塞
- 客户端
网络 IO,键值对增删改查操作,数据库操作; - 磁盘
生成 RDB 快照,记录 AOF 日志,AOF 日志重写; - 主从节点
主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB - 文件
- 切片集群实例
向其他实例传输哈希槽信息,数据迁移。
处理方式:非关键路径进行异步处理
波动的响应延迟
- 自身的操作
- 通过Redis日志或者latency monitor 工具查看慢查询命令 ,使用高效命令替代,
客户端去做数据的集合操作。 - 过期Key操作,使用随机数避免大量的key同时过期。
- 通过Redis日志或者latency monitor 工具查看慢查询命令 ,使用高效命令替代,
- 文件系统
fsync 的执行时间很长,如果是在 Redis 主线程中执行 fsync,就容易阻塞主线程。
AOF 重写会对磁盘进行大量 IO 操作,压力比较大时会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但主线程会监控 fsync 的执行进度。 - 操作系统
- 内存swap
是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写。
一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。
触发swap 主要原因是物理机器内存不足
解决思路:增加机器的内存或者使用 Redis 集群 - 内存大页
Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。
一般不建议使用内存大页机制。
- 内存swap
CPU
多CPU架构
在多CPU架构上,应用程序可以在多处理器上运行,在多Socket切换运行进行内存访问会增加应用程序延迟。
CPU多核
不同核之间切换运行时信息
解决的方式:应用程序绑核
内存碎片
- 内存分配器的分配策略
操作系统一般无法做到按需分配,一般是按照固定大小分配 - 键值对大小不一样和删改操作
不同的业务数据存储在Redis会带来大小不一的键值对
使用Redis自身的Info命令查看 mem_fragmentation_ratio,该指标表示当前的内存碎片率,是used_memory_rss 和 used_memory 相除的结果。
used_memory_rss 是操作系统实际分配给 Redis 的物理内存空间,里面就包含了碎片;而 used_memory 是 Redis 为了保存数据实际申请使用的空间。
一般来说1< mem_fragmentation_ratio <1.5
解决方法:
- 重启
如果 Redis 中的数据没有持久化数据会丢失;
即使 Redis 数据持久化了,还需要通过 AOF 或 RDB 进行恢复,恢复时长取决于AOF 或 RDB 的大小,如果只有一个 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,导致响应延迟升高。
碎片清理是有代价的,操作系统需要把多份数据拷贝到新位置,把原有空间释放出来,这会带来时间开销。
因为 Redis 是单线程,在数据拷贝时,Redis 只能等着,这就导致 Redis 无法及时处理请求,性能就会降低。
缓冲区
输入缓冲区就是用来暂存客户端发送的请求命令的,导致溢出的情况主要是下面两种:
- 写入了 bigkey,比如一下子写入了多个百万级别的集合类型数据;
- 服务器端处理请求的速度过慢,例如,Redis 主线程出现了间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在缓冲区越积越多。
输出缓冲区溢出的情况有
- 服务器端返回 bigkey 的大量结果;
- 执行了 MONITOR 命令,是用来监测 Redis 执行的,该命令不能用在线上生产环境
- 缓冲区大小设置得不合理。
缓存淘汰机制
缓存满了如何处理?缓存淘汰机制
- noeviction
Redis 在使用的内存空间超过 maxmemory 值时,并不会淘汰数据。 - volatile-random
在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。 - volatile-random
在设置了过期时间的键值对中,进行随机删除。 - volatile-lru
会使用 LRU 算法筛选设置了过期时间的键值对volatile-lru。 - volatile-lfu
会使用 LFU 算法选择设置了过期时间的键值对。 - allkeys-random
从所有键值对中随机选择并删除数据 - allkeys-lru
使用 LRU(按照最近最少使用)算法在所有数据中进行筛选。 - allkeys-lfu
使用 LFU 算法在所有数据中进行筛选。
LRU 会把所有的数据组织成一个链表,链表的头和尾分别表示MRU 端和 LRU 端,分别代表最近最常使用的数据和最近最不常用的数据。LRU 算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销。
在 Redis 中,LRU 算法被做了简化,以减轻数据淘汰对缓存性能的影响。具体来说,Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。
Redis 提供了一个配置参数 maxmemory-samples,这个参数就是 Redis 选出的数据个数N。例如,我们执行如下命令,可以让 Redis 选出 100 个数据作为候选数据集:
CONFIG SET maxmemory-samples 100
缓存容量设置多大?需结合应用数据实际访问特征和成本开销来综合考虑的。
一般来说,建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。
如何处理被淘汰的数据?** **
一般来说,一旦被淘汰的数据选定后,如果这个数据是干净数据,那么我们就直接删除。如果这个数据是脏数据,我们需要把它写回数据库。
如何解决缓存不一致?
使用重试机制,可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如Kafka消息队列)。并发大的情况下,也可能会存在读到不一致的数据。
- 先删除缓存再更新数据库
处理方式:在线程 A 更新完数据库值以后,我们可以让它先 sleep 一小段时间,再进行一次缓存删除操作。
(延迟双删策略)
- 先更新数据库值,再删除缓存值
这种方案对业务影响比较小。
如何解决缓存雪崩、击穿、穿透?
缓存雪崩
缓存雪崩是指大量的应用请求无法在 Redis 缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。产生的原因以及处理方式:
- 缓存中有大量数据同时过期,导致大量请求无法得到处理
- 给缓存的key过期四件增加随机数。
- 服务降级直接返回预定义信息、空值、错误信息或者允许部分请求直接走数据库。
- 缓存实例故障宕机
- 在业务系统中实现服务熔断或请求限流机制。
- 事前预发,通过主从节点的方式构建 Redis 缓存高可靠集群。
缓存击穿
针对某个访问非常频繁的热点数据的请求,无法在缓存中进行处理,导致访问该数据的大量请求都发送到了后端数据库,使数据库压力激增。处理方式:特别频繁的热点数据不设置过期时间。
缓存穿透
查询的数据既不在 Redis 缓存中,也不在数据库中,导致请求在访问缓存时,发生缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。
产生的原因
- 业务层误操作,缓存中的数据和数据库中的数据被误删除。
- 恶意攻击:专门访问数据库中没有的数据。
处理措施
- 缓存空值或缺省值。
- 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
- 前端进行请求合法性检测,过滤无效请求。
布隆过滤器原理
只要有一个为 0,那么,X 就肯定不在数据库中,都为1有可能在数据库中。
缓存污染
在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。这种情况,就是缓存污染。
处理方式
- LRU策略
** **只看数据的访问时间,使用 LRU 策略在处理扫描式单次查询操作时,无法解决缓存污染。
-
LFU策略
在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据 的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
并发访问
-
加锁
加锁保证了互斥性,但是加锁也会导致系统并发性能降低。
-
原子操作
- 把多个操作在 Redis 中实现成一个操作,也就是单命令操作。
- 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。
分布式锁实现
单Redis节点实现
//加锁, unique_value 作为客户端唯一标识,同时设置超时时间以免客户端发生异常而无法锁
SET lock_key unique_value NX PX 10000
//释放锁 比较unique_value是否相等,避免误释放
if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
多Redis节点的实现
Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,则认为客户端成功获得分布式锁,否则加锁失败。即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。
加锁的主要流程如下:
- 客户端获取当前时间
- **客户端按顺序依次向 N 个 Redis 实例执行加锁操作
**加锁操作和在单实例上执行的加锁操作一样。为了保证当某个Redis实例故障了,Redlock 算法可以继续运行,需要给加锁操作设置一个超时时间。 - 客户端一旦完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
- 判断加锁是否成功
- 客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁。
- 客户端获取锁的总耗时没有超过锁的有效时间。**
在Redlock算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的Lua脚本就可以了。
事务机制
Redis 通过 MULTI、EXEC、DISCARD 和 WATCH 四个命令来支持事务机制。事务的 ACID 属性是我们使用事务进行正确操作的基本要求。
主从同步与故障切换可能会遇到的问题
主从不一致
客户端从从库中读取到的值和主库中的最新值并不一致。出现的原因主要是主从库间的命令是异步进行的。
处理方式
-
在硬件环境配置方面尽量保证主从库间的网络连接状况良好。
-
开发一个外部程序来监控主从库间的复制进度。
如果某个从库的进度差值大于预设的阈值,让客户端不读取该从库数据,减少读到不一致数据的情况。
读取过期数据
由 Redis 的过期数据删除策略引起的。 Redis 同时使用了两种策略来删除过期的数据,分别是惰性删除策略和定期删除策略。在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免 了客户端读到过期数据。在应用主从集群时尽量使用Redis 3.2及以上版本。
不合理配置项导致的服务挂掉
-
protected-mode
这个配置项的作用是限定哨兵实例能否被其他服务器访问。当这个配置项设置为 yes 时,
哨兵实例只能在部署的服务器本地进行访问。
-
cluster-node-timeout
这个配置项设置了 Redis Cluster 中实例响应心跳消息的超时时间。如果执行主从切换的实例超过半数,而主从切换时间又过长的话,就可能有半数以上的实例心跳超时,从而可能导致整个集群挂掉。建议将 cluster-node-timeout 调大些(例如 10 到 20 秒)。
数据倾斜
-
数据量倾斜
在某些情况下,实例上的数据分布不均衡,某个实例上的数据特别多
-
bigkey
-
Slot 分配不均衡
-
Hash Tag
是指加在键值对 key 中的一对花括号{}。这对括号会把 key 的一部分括起来,客户端在计算 key 的 CRC16 值时,只对 Hash Tag 花括号中的 key 内容进行计算。如果没用 Hash Tag 的话,客户端计算整 个 key 的 CRC16 的值。
-
-
数据访问倾斜
每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁。
通常来说,热点数据以服务读操作为主,在这种情况下,我们可以采用热点数据多副本的方法来应 对。如果数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会
带来额外的开销。
通信开销:限制Redis Cluster规模的关键因素
** 实例间的通信开销会随着实例规模增加而增大**,在集群超过一定规模时(比如 800 节点),集群吞吐量反而会下降。所以,集群的实际规模会受到限制。
为了让集群中的每个实例都知道其它所有实例的状态信息,实例之间会按照一定的规则进
行通信。这个规则就是 Gossip 协议。
Gossip协议工作原理
- 每个实例之间会按照一定的频率,从集群中随机挑选一些实例,把 PING 消息发送给挑选出来的实例,用来检测这些实例是否在线,并交换彼此的状态信息。PING 消息中封装了发送消息的实例自身的状态信息、部分其它实例的状态信息,以及 Slot 映射表
- 一个实例在接收到 PING 消息后,会给发送 PING 消息的实例,发送一个 PONG 消息。PONG 消息包含的内容和 PING 消息一样。
实例间使用 Gossip协议进行通信时,通信开销受到通信消息大小和通信频率这两方面的影响,消息越大、频率越高,相应的通信开销也就越大。
如何降低实例间的通信开销?
配置项 cluster-node-timeout 定义了集群实例被判断为故障的心跳超时时间,默认是 15秒。如果 cluster-node-timeout 值比较小,那么,在大规模集群中,就会比较频繁地出现 PONG 消息接收超时的情况,从而导致实例每秒要执行 10 次“给 PONG 消息超时的实例发送 PING 消息”这个操作。 所以,为了避免过多的心跳消息挤占集群带宽,可以调大 cluster-node-timeout值,比如说调大到 20 秒或 25 秒。这样一来, PONG 消息接收超时的情况就会有所缓解,单实例也不用频繁地每秒执行 10 次心跳发送操作了。 当然也不要把 cluster-node-timeout 调得太大,否则,如果实例真的发生了故障就需要等待 cluster-node-timeout 时长后,才能检测出这个故障,这又会导致实际的故障恢复时间被延长,会影响到集群服务的正常使用。
Redis 6.0 特性
Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线程来处理。
主线程和多 IO 线程的协作分成四个阶段。
-
服务端和客户端建立 Socket 连接,并分配处理线程
主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时,主线程会创建和客户端的连 接,并把 Socket 放入全局等待队列中。紧接着,主线程通过轮询方法把 Socket 连接分配给 IO 线程。
-
IO 线程读取并解析请求
主线程一旦把 Socket 分配给 IO 线程,就会进入阻塞状态,等待 IO 线程完成客户端请求读取和解析。因
为有多个 IO 线程在并行处理,所以,这个过程很快就可以完成。 -
主线程执行请求操作
主线程还是会以单线程的方式执行这些命令操作
-
IO 线程回写 Socket 和主线程清空全局队列