分布式Redis深度历险-Cluster
本文为分布式Redis深度历险系列的第三篇,主要内容为Redis的Cluster,也就是Redis集群功能。
Redis集群是Redis官方提供的分布式方案,整个集群通过将所有数据分成16384个槽来进行数据共享。
集群基础实现
一个集群由多个Redis节点组成,不同的节点通过CLUSTER MEET
命令进行连接:
CLUSTER MEET <ip> <port>
收到命令的节点会与命令中指定的目标节点进行握手,握手成功后目标节点会加入到集群中,看个例子,图片来自于Redis的设计与实现:
槽分配
一个集群的所有数据被分为16384个槽,可以通过CLUSTER ADDSLOTS
命令将槽指派给对应的节点。当所有的槽都有节点负责时,集群处于上线状态,否则处于下线状态不对外提供服务。
clusterNode的位数组slots代表一个节点负责的槽信息。
struct clusterNode { unsigned char slots[16384/8]; /* slots handled by this node */ int numslots; /* Number of slots handled by this node */ ... }
看个例子,下图中1、3、5、8、9、10位的值为1,代表该节点负责槽1、3、5、8、9、10。
每个Redis Server上都有一个ClusterState的对象,代表了该Server所在集群的信息,其中字段slots记录了集群中所有节点负责的槽信息。
typedef struct clusterState { // 负责处理各个槽的节点 // 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理 // slots[i] = null 代表该槽目前没有节点负责 clusterNode *slots[REDIS_CLUSTER_SLOTS]; }
槽重分配
可以通过redis-trib工具对槽重新分配,重分配的实现步骤如下:
- 通知目标节点准备好接收槽
- 通知源节点准备好发送槽
- 向源节点发送命令:
CLUSTER GETKEYSINSLOT <slot> <count>
从源节点获取最多count个槽slot的key - 对于步骤3的每个key,都向源节点发送一个
MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>
命令,将被选中的键原子的从源节点迁移至目标节点。 - 重复步骤3、4。直到槽slot的所有键值对都被迁移到目标节点
- 将槽slot指派给目标节点的信息发送到整个集群。
在槽重分配的过程中,槽中的一部分数据保存着源节点,另一部分保存在目标节点。这时如果要客户端向源节点发送一个命令,且相关数据在一个正在迁移槽中,源节点处理步骤如图:
当客户端收到一个ASK错误的时候,会根据返回的信息向目标节点重新发起一次请求。
ASK和MOVED的区别主要是ASK是一次性的,MOVED是永久性的,有点像Http协议中的301和302。
一次命令执行过程
我们来看cluster下一次命令的请求过程,假设执行命令 get testKey
- cluster client在运行前需要配置若干个server节点的ip和port。我们称这些节点为种子节点。
- cluster的客户端在执行命令时,会先通过计算得到key的槽信息,计算规则为:
getCRC16(key) & (16384 - 1)
,得到槽信息后,会从一个缓存map中获得槽对应的redis server信息,如果能获取到,则调到第4步 - 向种子节点发送
slots
命令以获得整个集群的槽分布信息,然后跳转到第2步重试命令 - 向负责该槽的server发起调用
server处理如图:
- 客户端如果收到MOVED错误,则根据对应的地址跳转到第4步重新请求,
- 客户段如果收到ASK错误,则根据对应的地址跳转到第4步重新请求,并在请求前带上ASKING标识。
以上步骤大致就是redis cluster下一次命令请求的过程,但忽略了一个细节,如果要查找的数据锁所在的槽正在重分配怎么办?
Redis故障转移
疑似下线与已下线
集群中每个Redis节点都会定期的向集群中的其他节点发送PING消息,如果目标节点没有在有效时间内回复PONG消息,则会被标记为疑似下线。同时将该信息发送给其他节点。当一个集群中有半数负责处理槽的主节点都将某个节点A标记为疑似下线后,那么A会被标记为已下线,将A标记为已下线的节点会将该信息发送给其他节点。
比如说有A,B,C,D,E 5个主节点。E有F、G两个从节点。
当E节点发生异常后,其他节点发送给A的PING消息将不能得到正常回复。当过了最大超时时间后,假设A,B先将E标记为疑似下线;之后C也会将E标记为疑似下线,这时C发现集群中由3个节点(A、B、C)都将E标记为疑似下线,超过集群复制槽的主节点个数的一半(>2.5)则会将E标记为已下线,并向集群广播E下线的消息。
选取新的主节点
当F、G(E的从节点)收到E被标记已下线的消息后,会根据Raft算法选举出一个新的主节点,新的主节点会将E复制的所有槽指派给自己,然后向集群广播消息,通知其他节点新的主节点信息。
选举新的主节点算法与选举Sentinel头节点的过程很像:
- 集群的配置纪元是一个自增计数器,它的初始值为0.
- 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一。
- 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。
- 档从节点发现自己正在复制的主节点进入已下线状态时,从节点会想集群广播一条CLUSTER_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有接收到这条消息、并且具有投票权的主节点向这个从节点投票。
- 如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。
- 每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条这种消息来同济自己获得了多少主节点的支持。
- 如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张支持票时,这个从节点就会当选为新的主节点。
- 因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个。
- 如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,知道选出新的主节点为止。
Redis常用分布式实现方案
最后,聊聊redis集群的其他两种实现方案。
client做分片
客户端做路由,采用一致性hash算法,将key映射到对应的redis节点上。
其优点是实现简单,没有引用其他中间件。
缺点也很明显:是一种静态分片方案,扩容性差。
Jedis中的ShardedJedis是该方案的实现。
proxy做分片
该方案在client与redis之间引入一个代理层。client的所有操作都发送给代理层,由代理层实现路由转发给不同的redis服务器。
其优点是: 路由规则可自定义,扩容方便。
缺点是: 代理层有单点问题,多一层转发的网络开销
原文:Java架构笔记
免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
马蜂窝 IM 系统架构的演化和升级
今天,越来越多的用户被马蜂窝持续积累的笔记、攻略、嗡嗡等优质的分享内容所吸引,在这里激发了去旅行的热情,同时也拉动了马蜂窝交易的增长。在帮助用户做出旅行决策、完成交易的过程中,IM 系统起到了重要的作用。 IM 系统为用户与商家建立了直接沟通的渠道,帮助用户解答购买旅行产品中的问题,既促成了订单交易,也帮用户打消了疑虑,促成用户旅行愿望的实现。伴随着业务的快速发展,几年间,马蜂窝 IM 系统也经历了几次比较重要的架构演化和转型。 IM 1.0 —— 初期阶段 初期为了支持业务快速上线,且当时版本流量较低,对并发要求不高,IM 系统的技术架构主要以简单和可用为目的,实现的功能也很基础。 IM 1.0 使用 PHP 开发,实现了 IM 基本的用户/客服接入、消息收发、咨询列表管理功能。用户咨询时,会通过平均分配的策略分配给客服,记录用户和客服的关联关系。用户/客服发送消息时,通过调用消息转发模块,将消息投递到对方的 Redis 阻塞队列里。收消息则通过 HTTP 长连接调用消息轮询模块,有消息时即刻返回,没有消息则阻塞一段时间返回,这里阻塞的目的是降低轮询的间隔。消息收发模型如下图所示:...
- 下一篇
RabbitMQ延迟消息的延迟极限是多少?
之前在写Spring Cloud Stream专题内容的时候,特地介绍了一下如何使用RabbitMQ的延迟消息来实现定时任务。最近正好因为开发碰到了使用过程中发现,延迟消息没有效果,消息直接就被消费了的情况。因此就继续深入研究了一下问题原因,在此记录下来,给碰到类似问题的童鞋们参考。 问题定位 因为不是所有的消息都出现了没有延迟消息效果的因素,通过有问题的消息特征,大致猜测可能是延迟时间过长导致了消息延迟失败。为了验证这个原因,先拿之前文章中的例子,来测试一下延迟时间是否与问题直接相关。 对之前的延迟消息使用样例(文末的Git仓库中可以获取完整代码)接口做一下微改,增加了一个请求参数delay来控制延迟时间: @GetMapping("/sendMessage") public String messageWithMQ(@RequestParam String message, @RequestParam Long delay) { log.info("Send: " + message); testTopic.output().send(MessageBuilder.withPayl...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- 2048小游戏-低调大师作品
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库