Redis 架构原理

Redis 的三种工作模式介绍

1. 主从模式

  • 主数据库可以执行读写功能,而从数据库只能执行读功能。
  • 主数据库数据发生变化,会自动同步到从数据库。
  • 主数据库为 master,从数据库为 slave,一个master可以有多个slave,一个slave只能有一个master
  • slave挂了,重新启动会从master同步数据, master挂了,服务器只能进行读功能,不能执行写功能,直到master重新启动同步数据后,才能提供写服务。

2. 哨兵模式

  • 可以解决主从模式的弊端:master挂掉之后不能提供写功能。
  • 哨兵模式是建立在主从模式的
  • 当master挂掉之后,会自动从slave中选一个作为master。
    • 若master重新启动,master则会转化为现有的master下的一个slave
  • 当slave切换时,会通过发布订阅方式,将slave所对应的master更改
  • 注意:
    • 因为哨兵也是一个进程,所以也有挂掉的可能,需要配置多个哨兵互相监督。
    • 一个哨兵可以监督多个主从数据库。同样,一个主从数据库可以被多个哨兵监督。

3. Cluster模式

  • Redis 3.0 之前,使用 哨兵sentinel)机制来监控各个节点之间的状态。Redis ClusterRedis分布式解决方案,在 3.0 版本正式推出,有效地解决了 Redis分布式 方面的需求。当遇到 单机内存并发流量 等瓶颈时,可以采用 Cluster 架构方案达到 负载均衡 的目的。
  • redis cluster是Redis的分布式解决方案,在3.0版本推出后有效地解决了redis分布式方面的需求,自动将数据进行分片,每个master上放一部分数据提供内置的高可用支持,部分master不可用时,还是可以继续工作的
  • 支撑N个redis master node,每个master node都可以挂载多个slave node
  • 高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master

Redis 主从数据同步过程

主从复制的方式

从节点复制主节点的数据后,就相当于给主从节点备份了,所谓的有备无患就是这个意思。那么主从复制的原理是怎么样的?

其实主要就是三种复制方式:持续复制、全量复制、部分复制。

持续复制

当有客户端的写命令请求到主节点后,主节点会做两件事:命令传播和将写命令写入到复制积压缓冲区。

原理图如下:
image-20220607191010777

  • 命令传播:将写命令持续发送给所有从服务器,保持主从数据一致。这个就可以理解为持续复制了。
  • 复制积压缓冲区:其实就是一个有界队列,保存着最近传播的写命令,而队列里面的每个字节都有一个偏移量标识。复制积压缓冲区的作用和原理在部分复制的时候再细讲。

全量复制

用于主从节点第一次复制的场景。这在我们的软件开发中也很常见,比如你要把第三方的用户数据同步到自己的系统中,一开始肯定是把存量用户一次性给复制过来,后续有新增或更新的用户就采用增量更新就可以了。

当然全量复制的时候,数据量很大时,就会对主从节点和网络造成很大的开销,也就是常说的复制风暴,所以要避免不必要的全量复制,这个后面再讲怎么避免。

我们先来看下全量复制的原理图,然后我再来详细解释每一步怎么做的。
image-20220607191159560

总结全量复制的步骤

  1. 从节点给主节点发送命令;

  2. 主节点回复从节点,要开始全量复制了;

  3. 从节点保存主节点信息;

  4. 主节点开始生成 RDB 快照文件;

  5. RDB 文件发给从节点,主节点发送 RDB 文件;

  6. 发送缓冲区数据给客户端;

  7. 从节点清空旧数据;

  8. 从节点加载 RDB 文件;

  9. 从节点执行 AOF 操作。

部分复制

这个可以理解为增量更新,比如和第三方系统对接时,如果第三方有数据更新,定期进行增量更新就可以了。

而 Redis 主从的部分复制就是指当主从之间的网络故障等原因造成持续复制中断了,当从节点再次连上主节点后,主节点就补发数据给从节点,避免了全量复制的过高开销。补发数据的来源就是复制积压缓冲的数据。

原理图如下所示:
image-20220607191547923

部分复制总共分为六步:

(1)当主节点之间失联后,如果时间超过了 repl-timeout 时间,主节点就认为从节点发生故障了,中断连接。

(2)主节点其实一直都在把客户端写命令放入复制积压缓冲区,所以即使断连了,主节点还是会保留断连期间的命令,但因为队列是固定的,当写命令太多时,就会导致部分命令被覆盖了。

(3)主从节点恢复连接。

(4)从节点发送 psync 命令给主节点,带有 runId 和 offset 参数,runId 是上一次复制时保存的主节点的 runId值,offset 是从节点的复制偏移量。

(5)主节点接收到从节点的命令后,先判断传过来的 runId 是否和自己匹配,如果不匹配,则进行全量复制;如果 runId 匹配,则响应 CONTINUE,告诉从节点,可以进行部分复制了。我要把复制积压缓冲区的数据发给你了哦,请准备好接收。

(6)主节点根据子节点发送的偏移量,将复制积压缓冲区的数据发送给子节点。

那复制积压缓冲区到底是怎么来根据偏移量来计算要发送哪些缓存数据的呢?我们接着往下看。

积压缓冲区

复制积压缓冲区的特点:

固定长度的队列。

最近传播的写命令,默认为 1 MB 大小,可调节大小。

队列中的每个字节都有对应的复制偏移量进行标识。如下图所示,每一个字节对应一个偏移量。

image-20220607192640931

复制积压缓冲区索引

从节点重新连上主节点后,会发送 psync 命令,携带着偏移量 offset。比如 offset = 125,然后主节点拿着这个 125 去复制积压缓冲区找,125 正好在里面,然后就会执行部分复制的操作,将 125 以后的缓冲数据发送给从节点。

image-20220607192702918

偏移量在复制积压缓冲区的作用

如果 offset =10,主节点拿着这个 10 去复制积压缓冲区找,发现队列中最早的 offset 是 100,所以 100 之前的字节都被覆盖了,那么子节点就不能通过复制积压缓冲区拿到完整数据,所以只能通过全量复制的方式来同步。这个时候主节点就会发送一个 +FULLRESYNC的命令给子节点,告诉子节点,兄弟,你来得太晚了,只能使用全量同步的方式了。

image-20220607192712645

Redis Cluster 模式

Redis Cluster 集群介绍

一、概述

在高并发的系统中当我们需要从海量数据中快速找到所需符合要求的数据, 我们可以按照某种规则对海量的数据进行划分,将其分散存储在多个redis服务节点中,从而通过实现数据分片来降低redis服务加点的压力。

架构图

image-20220608105455866

在这个图中, 每一个蓝色的圈都代表着一个redis的服务器节点。他们任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点, 对其进行存取和其他操作

二、redis集群的特点

cluster模式的优点

  1. 将数据自动切分到多个节点的能力
  2. 当集群中的一部分节点失效或者无法进行通讯时,仍然可以继续处理命令请求的能力,拥有自动故障转移的能力。

节点间内部通讯机制

基础通讯原理

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息,Redis 集群采用 Gossip(流言)协议,Gossip 协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播

  1. 集群中的每个节点都会单独开辟一个 TCP 通道,用于节点之间彼此通信,通信端口号在基础端口上加10000。
  2. 每个节点在固定周期内通过特定的规则选择介个节点发送ping消息。
  3. 接收到ping消息的节点用pong消息做为响应。

集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息变更等事件发生时,通过不断的 ping/pong 消息通信,经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。

gossip协议

gossip协议包含多种消息,包括ping,pong,meet,fail,等等

meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信

redis-trib.rb add-node

其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群

ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据

每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新

pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新

fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了

ping消息深入

ping很频繁,而且要携带一些元数据,所以可能会加重网络负担

每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点

当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了

比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题

所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率

每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换

至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息

基于重定向的客户端

(1)请求重定向

客户端可能会挑选任意一个redis实例去发送命令,每个redis实例接收到命令,都会计算key对应的hash slot

如果在本地就在本地处理,否则返回moved给客户端,让客户端进行重定向

cluster keyslot mykey,可以查看一个key对应的hash slot是什么

用redis-cli的时候,可以加入-c参数,支持自动的请求重定向,redis-cli接收到moved之后,会自动重定向到对应的节点执行命令

(2)计算hash slot

计算hash slot的算法,就是根据key计算CRC16值,然后对16384取模,拿到对应的hash slot

用hash tag可以手动指定key对应的slot,同一个hash tag下的key,都会在一个hash slot中,比如set mykey1:{100}和set mykey2:{100}

(3)hash slot查找

节点间通过gossip协议进行数据交换,就知道每个hash slot在哪个节点上

smart jedis

(1)什么是smart jedis

基于重定向的客户端,很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点

所以大部分的客户端,比如java redis客户端,就是jedis,都是smart的

本地维护一份hashslot -> node的映射表,缓存,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向

(2)JedisCluster的工作原理

在JedisCluster初始化的时候,就会随机选择一个node,初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池

每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点

如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved

如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存

重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException

jedis老版本,可能会出现在集群某个节点故障还没完成自动切换恢复时,频繁更新hash slot,频繁ping节点检查活跃,导致大量网络IO开销

jedis最新版本,对于这些过度的hash slot更新和ping,都进行了优化,避免了类似问题

(3)hashslot迁移和ask重定向

如果hash slot正在迁移,那么会返回ask重定向给jedis

jedis接收到ask重定向之后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以JedisCluster API收到ask是不会更新hashslot本地缓存

已经可以确定说,hashslot已经迁移完了,moved是会更新本地hashslot->node映射表缓存的

Redis Cluster 集群伸缩

Redis 集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。

image-20220608112400731

槽和数据与节点的对应关系

当主节点分别维护自己负责的槽和对应的数据,如果希望加入1个节点实现集群扩容时,需要通过相关命令把一部分槽和数据迁移给新节点

image-20220608112925942

上面图里的每个节点把一部分槽和数据迁移到新的节点6385,每个节点负责的槽和数据相比之前变少了从而达到了集群扩容的目的,集群伸缩=槽和数据在节点之间的移动。

扩容操作

扩容是分布式存储最常见的需求,redis集群扩容操作可分为如下步骤:

  1. 准备新节点
  2. 接入集群
  3. 迁移槽和数据

加入集群后需要为新节点迁移槽和相关数据,槽在迁移过程中集群可以正常提供读写服务,迁移过程是集群扩容最核心的环节,下面详细讲解。

  1. 槽是 Redis 集群管理数据的基本单位,首先需要为新节点制定槽的迁移计划,确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节点负责相似数量的槽,从而保证各节点的数据均匀,比如之前是三个节点,现在是四个节点,把节点槽分布在四个节点上。

    image-20220608113121888
  2. 槽迁移计划确定后开始逐个把槽内数据从源节点迁移到目标节点

image-20220608113352006

数据迁移过程是逐个槽进行的

流程说明:

1)对目标节点发送导入命令,让目标节点准备导入槽的数据。

2)对源节点发送导出命令,让源节点准备迁出槽的数据。

3)源节点循环执行迁移命令,将槽跟数据迁移到目标节点。

image-20220608113424727

集群master选举原理

当slave发现字的master变为fail状态时,便尝试进行failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下:

  1. slave发现自己的master变为fail
  2. 将自己的记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
  3. 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
  4. 尝试failover的slave手机FAILOVER_AUTH_ACK
  5. 超过半数后变成新的master
  6. 广播pong通知其他集群节点。
  • 从节点并不是在主节点一进入FAIL状态就马上尝试发起选举,而是有一定的延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其他的master或许尚未意识到FAIL状态,可能会拒绝投票
  • SLAVE_RANK 表示此slave已经从master复制数据的总量rank。Rank越小代表已经复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。

Redis集群方案

Redis Cluster集群模式通常具有高可用、可扩展性、分布式、容错等特性。redis分布式方案一般有两种:

1. 客户端分区方案

客户端 就已经决定数据会被 存储 到哪个 redis 节点或者从哪个 redis 节点 读取数据。其主要思想是采用 哈希算法Redis 数据的 key 进行散列,通过 hash 函数,特定的 key映射 到特定的 Redis 节点上。

image-20220608114506846

客户端分区方案 的代表为 Redis ShardingRedis ShardingRedis Cluster 出来之前,业界普遍使用的 Redis 多实例集群 方法。JavaRedis 客户端驱动库 Jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及 结合缓存池ShardedJedisPool

优点: 不适用第三方中间件,分区逻辑可控,配置简单,节点之间无关联,容易线性扩展,灵活性强。

缺点: 客户端无法动态增删服务节点,客户端需要自行维护分发逻辑,客户端之间无连接共享,会造成连接浪费。

2. 代理分区方案

客户端发送请求到一个代理组件,代理解析客户端的数,并将请求转发至正确的节点,最后将结果恢复给客户端。

优点: 简化客户端的分布式逻辑, 客户端透明接入,切换成本低,代理的转发和存储分离。

缺点: 多了一层代理等,加重了架构部署的复杂度和性能损耗。

image-20220608114831070

代理分区 主流实现的有方案有 TwemproxyCodis

代理分区组件介绍

1.Twemproxy

Twemproxy 也叫 nutcraker,是 twitter 开源的一个 redismemcache中间代理服务器 程序。Twemproxy 作为 代理,可接受来自多个程序的访问,按照 路由规则,转发给后台的各个 Redis 服务器,再原路返回。Twemproxy 存在 单点故障 问题,需要结合 LvsKeepalived高可用方案

image-20220608114920411
  • 优点:应用范围广,稳定性较高,中间代理层 高可用
  • 缺点:无法平滑地 水平扩容/缩容,无 可视化管理界面,运维不友好,出现故障,不能 自动转移

2. Codis

Codis 是一个 分布式 Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别。Codis 底层会 处理请求的转发,不停机的进行 数据迁移 等工作。Codis 采用了无状态的 代理层,对于 客户端 来说,一切都是透明的。

image-20220608115106315

优点: 实现了上层和底层reids的高可用,数据分片和自动平衡,提供命令行接口和RESTful API,提供监控和管理界面,可以动态添加和删除redis节点

缺点: 部署架构和配置复杂,不支持跨机房和多租户,不支持鉴权管理。

3.查询路由方案

客户端随机地 请求任意一个 Redis 实例,然后由 Redis 将请求 转发正确Redis 节点。Redis Cluster 实现了一种 混合形式查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向redirected)到正确的 Redis 节点。

image-20220608120011769

优点: 无中心节点,数据按槽存储分步在多个redis实例上,可以平滑的进行节点的扩容/缩容,支持高可用和自动故障转移,运维成本低。

缺点: 严重依赖 Redis-trib 工具,缺乏 监控管理,需要依赖 Smart Client (维护连接缓存路由表MultiOpPipeline 支持)。Failover 节点的 检测过慢,不如 中心节点 ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分 冷热数据

数据分步

数据分步理论

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上, 没个几点负责整体数据的一个子集

image-20220608123435366

数据分步通常有哈希分区和顺序分区两种方式,由于 Redis Cluster 采用 哈希分区规则,这里重点讨论 哈希分区

常见的 哈希分区 规则有几种,下面分别介绍:

节点取余分区

使用特定的数据,如redis 的键或用户ID,再根据节点数量N使用公式: hash(key) % N 计算出哈希值,用来决定数据映射到哪一个节点上。

image-20220608123716895
  • 优点

这种方式的突出优点是 简单性,常用于 数据库分库分表规则。一般采用 预分区 的方式,提前根据 数据量 规划好 分区数,比如划分为 5121024 张表,保证可支撑未来一段时间的 数据容量,再根据 负载情况 迁移到其他 数据库 中。扩容时通常采用 翻倍扩容,避免 数据映射 全部被 打乱,导致 全量迁移 的情况。

  • 缺点

节点数量 变化时,如 扩容收缩 节点,数据节点 映射关系 需要重新计算,会导致数据的 重新迁移

一致性哈希分区

一致性哈希可以很好的解决稳定性的问题,可以将所有的存储节点排列在首尾相接的hash环上,没个key在hash后会顺时针找到临近的存储节点存放。而当有节点加入或者退出时,仅影响改节点在hash环上顺时针相邻的后续节点。

优点: 加入和删除节点只影响哈希还中顺时针方向的相邻节点,对其他节点无影响。

缺点: 加减节点会造成哈希环中部分数据无法命中。当使用少量节点时,节点变化将大范围影响哈希环中数据映射,不适合少量数据节点的分布式方案。普通的一致性哈希分区在增减节点时需要增加一倍或减去一般节点才能保证数据的负载和均衡。

虚拟槽分区

Redis Cluster 采用 虚拟槽分区,所有的 根据 哈希函数 映射到 0~16383 整数槽内,计算公式:slot = CRC16(key)& 16383。每个节点负责维护一部分槽以及槽所映射的 键值数据,如图所示:

image-20220608134348677

redis虚拟槽分区的特点

  • 解耦数据和节点之间的关系, 简化了节点扩容和收缩的难度
  • 节点自身 维护槽的 映射关系,不需要 客户端 或者 代理服务 维护 槽分区元数据
  • 支持 节点 之间的 映射查询,用于 数据路由在线伸缩 等场景。

Redis 集群的功能限制

Redis 集群相对 单机 在功能上存在一些限制,需要 开发人员 提前了解,在使用时做好规避。

  • key批量操作 支持有限。

类似 msetmget 操作,目前只支持对具有相同 slot 值的 key 执行 批量操作。对于 映射为不同 slot 值的 key 由于执行 mgetmget 等操作可能存在于多个节点上,因此不被支持。

  • key事务操作 支持有限。

只支持 key同一节点上事务操作,当多个 key 分布在 不同 的节点上时 无法 使用事务功能。

  • key 作为 数据分区 的最小粒度

不能将一个 大的键值 对象如 hashlist 等映射到 不同的节点

  • 不支持 多数据库空间

单机 下的 Redis 可以支持 16 个数据库(db0 ~ db15),集群模式 下只能使用 一个 数据库空间,即 db0

  • 复制结构 只支持一层

从节点 只能复制 主节点,不支持 嵌套树状复制 结构。

这种结构很容易 添加 或者 删除 节点。如果 增加 一个节点 6,就需要从节点 1 ~ 5 获得部分 分配到节点 6 上。如果想 移除 节点 1,需要将节点 1 中的 移到节点 2 ~ 5 上,然后将 没有任何槽 的节点 1 从集群中 移除 即可。

由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除 或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.

Redus Cluster 故障转移与恢复

Redis集群中的节点分为主节点(master)和从节点(slave),主节点主要负责处理槽,从节点则用于复制某个主节点数据,并在被复制的主节点下线时,代替主节点处理后续的命令请求。

针对节点下线有两种状态:

  1. 主观下线:当节点A想节点B发送了一条PING消息时,节点B没有在规定的时间内(设置的cluster-node-timeout参数)返回PONG消息,那么节点A会将节点B标记为主观下线状态。

    这里的主观下线只是节点A主观的认为节点B下线了,有可能是因为节点A和节点B之间的网络断了,但是其他节点依然可以和节点B通讯,所以主观下线并不一定是节点B真的就下线了。

  2. 客观下线:由于节点A与集群内的其他节点仍然保持通讯,因此节点B的下线消息也通过Gossip协议传遍了集群内的其他节点。

    当集群内半数以上的节点都认为节点B主观下线了,那么节点B就会被认为客观下线了,同时将节点B标记为客观下线的节点会向集群中发送一条FAIL消息,所有收到这条消息的节点会立即将节点B标记为客观下线。

如果一个节点被认为客观下线了,那么就需要从它的从节点当中选出一个节点来代替它成为主节点。选举过程如下:

  1. 当从节点发现自己正在复制的主节点被标记为客观下线时,从节点会向集群中发送一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息的具有投票权的主节点向这个从节点投票
  2. 如果一个主节点具有投票权,并且未投票给其他从节点,那么这个主节点会向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持该从节点成为新的主节点
  3. 每个从节点都会接收返回的CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并会进行统计自己得到了多少主节点的支持
  4. 每个具有投票权的主节点只能投一次票,当一个从节点获得了一半以上的主节点的支持票时,那么这个节点就会成为新的主节点,
  5. 如果没有任何一个从节点获取大于半数的投票,那么将进行新的选举,直到选出新的主节点为止。
  6. 新的主节点产生后,它会撤销所有对已下线的主节点的槽指派,并将这些槽指派给自己。
  7. 新的主节点会向集群中广播一条PONG消息,让集群中的其他节点直到这个从节点已经成为了新的主节点,并且接管了原先主节点的所有槽。
  8. 新的主节点负责接收和自己处理的槽相关的指令,至此故障转移结束。

redis 多线程与后台线程

redis 单线程 + 后台线程

再次强调:我们经常听说的 redis 单线程模型(上图),其实仅仅指的是对客户端的请求处理过程,另外还有一些工作由部分特殊的独立线程来完成。

在 redis 6.0 以前,完整的 redis 线程模型是 主线程(1个)+ 后台线程(三个),我画了一张图,你可以看下:

image-20230110110144015

三个后台线程分别处理:

  • close_file:关闭 AOF、RDB 等过程中产生的大临时文件
  • aof_fsync:将追加至 AOF 文件的数据刷盘(一般情况下 write 调用之后,数据被写入内核缓冲区,通过 fsync 调用才将内核缓冲区的数据写入磁盘)
  • lazy_free:惰性释放大对象
    这三个线程有一个共同特点,都是用来处理耗时长的操作,也印证了我们常说的,专业的人做专业的事。

redis 多线程 + 后台线程
咱们继续将时钟往后拨到 redis6.0 版本,此版本出现了一种新的 IO 线程,也称为「多线程」。

我同样也画了张图,你可以看下:

image-20230110110301288

我们先思考下,引进 IO 线程解决了哪些问题?

在之前系列文章中,我们提到过,通常情况下,redis 性能在于网络和内存,而不是 CPU。针对 网络,一般是处理速度较慢的问题;针对内存,一般是指物理空间的限制。

所以到这,你应该很清楚了,究竟哪个模块需要引入多线程来处理?

对,就是网络模块,因此,引入的这些线程也叫 IO线程;由于主线程也会处理网络模块的工作,主线程习惯上也叫做主IO线程。

网络模块有接收连接、IO读(包括数据解析)、IO写等操作,其中,主线程负责接收新连接,然后分发到 IO线程进行处理(主线程也参与)。

默认情况下,只针对写操作启用IO线程,如果读操作也需要的话,需要在配置文件中进行配置

值得注意的是,命令处理仍然是单线程执行。

配置:

redis 默认情况下不会开启多线程处理,官方也建议,除非性能达到瓶颈,否则没必要开启多线程。

开启多线程:配置 io-thread 即可。io-thread = 1 表示只使用主 IO 线程 io-threads 4

开启之后,默认写操作会通过多线程来处理,而读操作则不会。

如果读操作也想要开启多线程,则需要配置:io-threads-do-reads yes

总结

本文从 redis 架构演进开始讲起,从单线程模型 => 单线程 + 后台线程 => 多线程 + 后台线程 演进。

每一次演进,都是为了解决某一类特殊问题;后台线程的出现,解决了一些耗时长的重操作。同样,多线程的出现,解决了网络模块的性能瓶颈。

刘小恺(Kyle) wechat
如有疑问可联系博主