Redis学习总结 -- 集群
在中,介绍了哨兵如何保证Redis服务高可用的。然而,哨兵是基于单机版Redis的,随着业务规模的不断变大,单机版Redis服务会面临着内存、流量等瓶颈问题。单机扩容虽然能解一时燃眉之急,但最终也会达到单机极限,导致无法扩容。当单机扩容之旅行不通时,我们只能盯着水平扩容-多机Redis。
分布式解决方案
为了解决单机瓶颈问题,纷纷转战分布式解决方案,具体的解决方案如下:
1、客户端分区
多个Redis实例组成一个“分布式集群”,每个Redis实例都存储一部分数据,客户端根据key的hash值映射到特定的Redis实例上进行数据操作,比较有代表性的就是Redis Sharding。Redis Sharding是Redis Cluster出来之前业界常用的Redis分布式解决方案。
在部署时,多个Redis实例可以使用同一套哨兵系统以保证自身的高可用。在使用过程中,客户端只需要配置哨兵信息和各个Redis实例的mastername,客户端启动后,会保持跟各个Redis实例的链接。
由上图可知,客户端对数据分区逻辑绝对可控,可以根据业务特点选择合适的数据分区方案。虽然Redis实例间无关联便于水平扩展,但是客户端却无法支持动态增删Redis实例节点,每次增删Redis实例节点都需要修改相应的配置或逻辑。
在客户端分区方案中,客户端需要感知所有的Redis示例信息,维护与各个Redis实例节点的连接。那么有没有一种方案不需要感知后面的Redis实例信息呢?答案是肯定的,这就是下面所讲的代理方案。在计算机领域,是没有增加一层中间层解决不了的问题,如果有,那就再加一层呗!
2、代理方案
客户端和Redis实例间有个代理层,客户端通过代理层访问Redis实例,代理负责将请求转发到正确的Redis实例中。
在该方案中,由于代理层为客户端屏蔽了Redis实例信息,因此客户端接入非常方便,只需要连接代理。正是因为新增了代理层,所以多了一层性能损耗,也可能引入新的性能瓶颈,或者“单点”问题。
目前代理方案的主流实现 Twemproxy 和 Codis。
Twemproxy也叫 nutcraker,是Twitter开源的,实现了Redis和Memcache协议,即支持Redis和Memcache代理。Twemproxy需要使用Keepalived做高可用,在运行时,只有一台Twemproxy在工作,另外一台处于备机,当一台挂掉以后,vip自动漂移,备机接替工作。
在该方案中,客户端发送请求到Twemproxy中,Twemproxy根据相应的路由规则转发到相应的Redis节点中。为了保证Redis实例的高可用,还需要部署twemproxy-sentinel-agent。twemproxy-sentinel-agent监控sentinal里面master信息,如果sentinal mater信息发生变化,那么修改Twemproxy 的servers配置参数,并重启Twemproxy。
Twemproxy最大的问题是无法平滑的扩缩容,而Codis解决了Twemproxy扩缩容问题,并且兼容Twemproxy。Codis是一种分布式集群解决方案,由豌豆荚开源。它发展起来的一个重要原因是在Redis官方集群方案漏洞百出的时候率先成熟稳定的。
上图是官方的架构图,codis-proxy是客户端连接的代理服务,实现了Redis协议,可以看作是一个Redis服务器。Codis依赖ZooKeeper 来存放数据路由表和 codis-proxy 节点的元信息。
Codis的主要原理是将所有的key分为1024个槽,每个槽对应一个分组(Redis实例),当请求过来时,首先对key进行CRC32计算得到hash值,然后除以1024求余就得到对应的槽编号,最后根据槽编号与分组的对应关系得到具体的分组。
当进行扩容时,配置槽与新增分组的映射关系,只需要将对应槽中的数据迁移到新增分组上即可,避免大范围的数据移动;当进行缩容时,只需要将待缩容分组对应的槽迁移到其他分组即可,避免大范围的数据移动。
3、Redis Cluster
对于官方而言,缺少官方分布式解决方案是难以接受的。随着不停的迭代,官方终于推出了自己的分布式解决方案:去中心化。在整个方案中, 并不存在代理层等“类中心节点”,所有Redis实例节点都直接对外提供访问。Redis实例之间通过gossip协议交换互相的状态,以及探测新加入的节点信息。redis集群支持动态加入节点,动态迁移slot,以及自动故障转移。
后面我们主要学习Redis cluster的原理和流程。
Redis Cluster
Redis cluster是Redis去中心化的分布式解决方案,集群中的每个节点都是平等关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
对于Redis cluster我们首先了解它是如何使用的,然后了解学习其基本原理,比如数据如何分片的、Redis实例间是如何通讯的、Redis实例故障迁移、如何扩缩容等。
1、使用流程
在Codis中,数据的具体分发是由代理层来完成的,而在Redis cluster中,数据分发是由client来完成的,具体流程如下:
客户端连接到任一Redis实例上
客户端发送命令到该Redis实例上
如果命令中的key所在的槽就位于该Redis实例上,则直接进行操作,否则,返回MOVED错误以及key对应节点信息。
客户端收到MOVED错误和key对应的节点信息,则连接到该Redis实例进行数据操作。
目前客户端有两种方案获取数据分布表:
此外,需要注意的是,我们要操作的数据所在的槽可能正在进行迁移,如果key还在待迁移节点上,则会在该节点上直接执行,否则返回ASK错误和迁移的目标节点的信息。客户端收到目标节点的信息后,首先发送ASKING命令进行确认,当通过后执行原先的命令。
不过不用担心重复造轮子,因为这些细节一般都会提供统一的库或sdk,使用者很方便的就可以使用。
在使用的过程中,需要区分MOVED和ASK错误的区别:
MOVED:正常情况下,client会拥有全局的slot映射信息,但某些场景下(比如扩容后部分slot槽信息变更),client拥有的slot映射信息是错误的,此时访问该slot时就会访问错误的实例上,该实例就会返回MOVED错误,并告诉client正确的实例信息。
ASK:该错误只发生在数据迁移过程中,当访问的key数据所在slot正在迁移(slot会对应多个key,迁移过程中key是分批迁移),假设数据从A迁移到B,此时如果A中不存在该key,那么可能key可能位于B中,此时A就会返回ASK错误,并返回B的实例信息。
这里需要注意的是,在数据迁移(A->B)过程中,如果先访问到B,由于slot信息还未与B建立映射关系,此时哪怕key已经位于B上了,B也会返回MOVED错误。对于处于迁移过程中的key,B只会处理ASKING命令,其他命令都会返回MOVED错误,并重定向到A实例上,然后走正常的ASK、ASKING流程。
2、数据分片
Redis cluster采用虚拟槽分区算法,即将Redis实例的存储空间分为13684个槽。Redis cluster使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和。假设Redis cluster有三个Redis实例节点:
Redis1 对应的slot为0~8000
Redis2 对应的slot为8001~12000
Redis3对应的slot为12001~16383
对于key为my_name计算其槽编号CRC16('my_name')%16384 = 2412,由于2412位于0~8000之间,因此my_name这个key应该位于Redis1实例中。
通过以上公式得到的槽编号都是固定的,然而在某些场景下,我们希望使用MSET通过操作多个key。往往这些key处在不同的Redis实例中,这样我们并不能保证操作的原子性。为了保证操作的原子性,我们希望能够保证这些key在同一个Redis实例中。通过以上公式并不能保证这些,此时我们就需要采用其他方案--Hash Tag。
Hash Tag允许用key的部分字符串来计算hash,当一个key包含占位符 {} (占位符可配置,hash_tag)的时候,就不对整个key做hash,而仅对 {} 包括的字符串做hash。比如对user:{user1}:ids和user:{user1}:tweets这两个key,由于{}中的内容都是user1,因此这两个key hash值一致,所以在同一个槽中。
3、Redis实例间通讯
在Redis cluster中,Redis实例间是需要互通有无的。Redis实例间有多种消息,比如
Meet消息:新节点首次加入时发布,其他节点收到后需要回复pong消息。
Ping消息:经常用于心跳,一般会封装一些自身信息和其他节点状态数据,其他节点收到后需要回复Pong消息。
Pong消息:收到Meet/Ping消息后回复,通常也会携带自身信息。
Fail消息:节点下线或故障时,会广播这个信息。
为了便于消息在集群中传播,Redis Cluster采用Gossip协议来加快消息在集群中的传播。
4、故障发现和恢复
4.1 故障发现
在进行故障迁移之前,首先要判定Redis实例是否故障。在Redis cluster中,Redis实例故障判定逻辑跟哨兵一致,也分为主观下线和客观下线。当Redis实例被标记为客观下线才认为Redis实例节点故障。
主观下线:节点间会定时发送Ping消息,当超过 cluster-node-timeout时未收到Pong消息,则认为对方节点下线。
客观下线:当半数以上的Redis实例节点都认为某Redis实例节点下线时,才会认为Redis实例节点下线,即大部分节点认为下线才是真的下线。
当收到其他节点的主观下线消息后,首先会进行时效性检查,如果超过 cluster-node-timeout*2 的时间,就忽略这个消息;否则,如果有半数以上的节点都认为主观下线,则将其标记为客观下线,并向集群中广播Fail消息,通知所有的节点将故障节点标记为客观下线,这个消息指包含故障节点的 ID。
4.2 故障恢复
当确认节点故障时,后面就需要从众多从节点中选择一个从节点进行恢复,流程如下:
资格检查:过滤掉与故障主节点长时间断开的从节点,当断开时间超过了 cluster-node-timeout*cluster-slave-validity-factor(从节点有效因子,默认为 10),那么相应的从节点就没有故障转移的资格。
新主节点选举:对符合资格的从节点按照从节点上复制偏移量从大到小进行排序,依次选择偏移量最大的从节点来触发选举。选举原理跟Raft算法类似,从节点在发起选举前都会生成一个递增的epoch,在消息传播过程中,所有节点使用最大的epoch来更新本地epoch值。
数据更新:当从节点收到半数以上的主节点的票数时,表示主节点已经选定了,会触发替换主节点的操作:删除旧主节点的槽数据,并将这些槽数据加到自身上,并向集群广播主节点已选定的消息。
数据同步:从节点连接到最新的主节点,并进行数据同步复制。
这里需要注意的是,故障恢复是由故障主节点的从节点发起的,参与投票的是其他正常的主节点。参与选举的是主节点,一方面是因为主节点才有全量的slot信息等,另一方面是为了节省成本,从节点不参与投票,因此对故障主节点的从节点数量就没有要求。
5、扩缩容
作为一个分布式缓存集群,因为业务变化总会遇到扩缩容的问题。
5.1 Redis cluster集群扩容
为了支持Redis cluster的动态扩容,Redis cluster有一套标准的扩容流程,具体扩容流程如下:
准备新节点
配置和其他Redis实例节点保持一致,不需要设置主从节点
加入集群
执行cluster meet serverip serverport 命令加入集群
数据迁移
数据迁移分为两部分:槽迁移和数据迁移。在迁移过程中,先把槽准备好,然后才开始进行数据迁移,具体流程如下:
在数据迁移过程中,首先要确定哪些slot迁入目标节点,哪些slot从源节点迁出;然后分slot分批次从源节点中读取待迁移key列表,并将这些key迁移到目标节点;最后完成数据迁移后,向所有主节点广播目标节点的槽信息。
在key迁移过程中,client向源节点发送数据迁移请求,而源节点收到请求后,将作为client向目标节点发送数据,目标节点收到数据并处理完毕后将返回OK,源节点收到OK应答后,将删除本地key,并返回client处理完成。
在数据迁移过程中,会不会出现写冲突呢?答案是 不会。Redis是单线程服务,不会出现两个请求同时处理。
5.2 Redis cluster集群缩容
缩容有两种情况:
待移除节点不含槽数据或者从节点,可以直接通知集群内其他节点忘记下线节点,当所有节点忘记该节点后就可以正常关闭。
待移除节点包含槽数据,需要先将节点上槽数据迁移到其他节点上,保证节点下线后整个槽节点映射的完整性
在缩容的数据迁移流程跟扩容的数据迁移流程正好相反。首先确定待移除节点的槽,以及这些槽移到哪些节点上;然后,分slot分批次的读取源节点中的待迁移key列表,并将这些key迁移到指定节点上;最后,完成数据迁移后,向所有主节点广播下线信息后正常关闭。
redis-trib.rb是官方提供的Redis Cluster的管理工具,支持的操作如下:
create:创建集群
check:检查集群
info:查看集群信息
fix:修复集群
reshard:在线迁移slot
rebalance:平衡集群节点slot数量
add-node:添加新节点
del-node:删除节点
set-timeout:设置节点的超时时间
call:在集群所有节点上执行命令
import:将外部redis数据导入集群
有兴趣实操可以使用redis-trib.rb help得到更多帮助。
附录
Twemproxy github https://github.com/twitter/twemproxy/
Codis github https://github.com/CodisLabs/codis
Codis的架构设计 https://blog.csdn.net/shmiluwei/article/details/51958359
Redis技巧:分片技术和Hash Tag https://www.jianshu.com/p/c441b882c1c6
不懂Redis Cluster原理,我被同事diss了!https://baijiahao.baidu.com/s?id=1663270958212268352&wfr=spider&for=pc
【redis】深入理解redis cluster --- 扩容集群 https://www.cnblogs.com/chenYanfeng/articles/9210344.html