内容简介:Redis集群实现原理探讨
Redis集群是一个distribute、fault-tolerant的 Redis 实现,主要设计目标是达到线性可扩展性、可用性、数据一致性。
线性拓展官方推荐最大的节点数量为1000,由于Cluster架构中无Proxy层,Master与Slave之间使用异步replication。
数据一致性客户端容忍一定程度的数据丢失,集群尽可能保存Client write操作的数据,保证数据一致性。
可用性Redis集群通过partition来提供一定程度的可用性,当集群中的一部分节点失效或者无法进行通讯时,集群仍可以继续提供服务。这里有两点补充:
- 只要集群中大多数Master可达、且失效的Master至少有一个Slave可达,即集群非Fail状态,集群都是可用的,如下图:
- Redis集群的replicas migration机制可以将拥有多个Slave的Master的某个Slave,迁移到没有Slave的Master下,即Slave分布相对平衡,确保Master都有一定数量的Slave备份。
集群节点属性集群中每个Master node负责存储数据、集群状态,包括slots与nodes对应关系。Master nodes能够自动发现其他nodes,检测failure节点,当某个Master节点失效时,集群能将核实的Slave提升为Master。下图是节点的关联信息,节点定时会将这些信息发送给其他节点:
1fc2412b7429e4ab5d8704fcd39520815ea2727b 10.9.42.37:6103 master - 0 1494082584680 9 connected 10923-13652 08e70bb3edd7d3cabda7a2ab220f2f3610db38cd 10.9.33.204:6202 slave ad1334bd09ee73fdeb7b8f16194550fc2bf3a038 0 1494082586686 8 connected edaafc250f616e9e12c5182f0322445ea9a89085 10.9.33.204:6203 slave 1fc2412b7429e4ab5d8704fcd39520815ea2727b 0 1494082586184 9 connected 06cd6f24caf98a1c1df0862eadac2b05254f909d 10.9.33.204:6201 slave d458c22ccced2f29358b6e6814a206d08285374e 0 1494082584179 7 connected 3892b7fb410a4d6339364dbdda2ebc666ffee843 10.9.42.37:6203 slave 73f7d44c03ada58bf5adaeb340359e2c043ecfa0 0 1494082582679 12 connected 73f7d44c03ada58bf5adaeb340359e2c043ecfa0 10.9.33.204:6103 master - 0 1494082585181 3 connected 13653-16383 4004a64211bea5050a8f46b8436564d40380cd60 10.9.33.204:6101 master - 0 1494082583678 1 connected 2731-5460 d458c22ccced2f29358b6e6814a206d08285374e 10.9.42.37:6101 master - 0 1494082588189 7 connected 0-2730 f8868d59c0f3d935d3dbe35601506039520f7107 10.9.42.37:6201 slave 4004a64211bea5050a8f46b8436564d40380cd60 0 1494082587187 10 connected 45ba0d6fc3d48a43ff72e10bcc17d2d8b2592cdf 10.9.33.204:6102 master - 0 1494082583179 2 connected 8192-10922 007d7e17bfd26a3c1e21992bb5b656a92eb65686 10.9.42.37:6202 slave 45ba0d6fc3d48a43ff72e10bcc17d2d8b2592cdf 0 1494082588189 11 connected ad1334bd09ee73fdeb7b8f16194550fc2bf3a038 10.9.42.37:6102 myself,master - 0 0 8 connected 5461-8191
从左至右分别是:节点ID、IP地址和端口,节点角色标志、最后发送ping时间、最后接收到pong时间、连接状态、节点负责处理的hash slot。集群可以自动识别出ip/port的变化,并通过Gossip(最终一致性,分布式服务数据同步算法)协议广播给其他节点知道。Gossip也称“病毒感染算法”、“谣言传播算法”(附录一)。
Keys分布模型集群的键空间被分割为16384个slots(即hash槽),slot是数据映射的基本单位,即集群的最大节点数量是16384(官方推荐最大节点数量为1000个左右)。集群中的每个Master节点负责处理16384个hash槽其中的一部分,当集群处于“stable”状态时(无slots在节点间迁移),任意一个hash slot只会被单个node所服务。以下是键映射到hash槽的算法:
HASH_SLOT = CRC16(key) mod 16384
Redis集群是在多个Redis节点之间进行数据共享,它不支持“multi-key”操作(即执行的命令需要在多个Redis节点之间移动数据,比如Set类型的并集、交集等(除非这些key属于同一个node),即Cluster不能进行跨Nodes操作。如下:
10.9.42.37:6102> smembers set1 -> Redirected to slot [3037] located at 10.9.33.204:6101 1) "d" 2) "b" 3) "g" 4) "c" 5) "a" 6) "f" 7) "e" (1.08s) 10.9.33.204:6101> smembers set2 -> Redirected to slot [15294] located at 10.9.33.204:6103 1) "b" 2) "c" 3) "f" 4) "g" 5) "h" 6) "i" 7) "a" 10.9.33.204:6103> sunion set1 set2 (error) CROSSSLOT Keys in request don't hash to the same slot
Redis为了兼容multi-key操作,提供了 “hash tags” 操作,每个key可以包含自定义的“tags”,在存储的时候根据tags计算此key应该映射到哪个node上。通过“hash tags”可以强制某些keys被保存到同一个节点上,便于进行“multi key”操作。基本上如果关键字包含“{...}”,那么在{和}之间的字符串被hash,然而可能有多个匹配的{或}该算法由以下规则规定:如果key包含{,在{的右边有一个},并在第一次出现{与第一次出现}之间有一个或者多个字符串,那么就作为key进行hash。例如,{user1000}.following和{user1000}.followed就在同一个hash slot;foo{}{bar}整个字符被hash,foo{{bar}},{bar被hash;foo{bar}{zap},bar被hash。如下所示:
10.9.33.204:6103> set {user1000}.following 1000 10.9.33.204:6101> set {user1000}.followed 1000 10.9.33.204:6101> keys * 4) {user1000}.following 6) {user1000}.followed
特殊说明一点,在 resharding 期间,原来同一个slot的keys被迁移到不同的node中,multi-key操作可能不可用。
数据一致性保证Redis集群尽可能保证数据的强一致性,但在特定条件下会丢失数据,原因有两点:异步replication机制以及network partition。
Master以及对应的Slaves之间使用异步的机制,在节点failover后,新的Master将会最终替代其他的replicas:
write命令提交到Master,Master执行完毕后向Client返回“OK”,但由于一部分replication,此时数据还没传播给Slave;如果此时Master不可达的时间超过阀值,此时集群将触发对应的slave选举为新的Master,此时没有replication同步到slave的数据将丢失。
在network partition时,总有一个窗口期(node timeout)可能会导致数据丢失:
由于网络分区,此时master不可达,且Client与Master处于一个分区,且此时集群处于“OK”。此时Failover机制,将其中一个Slave提升为新的Master,等待网络分区消除后,老的Master再次可达,此时节点被切换为Slave,而在这段期间,处于网络分区期间,Client仍然将write提交到老的Master,因为该Master被认为是仍然有效的。当老的Master再次加入集群,被切换成Slave后,这些数据将永远丢失。
集群可用性上述谈到多次集群状态的概念,那集群什么时候处于“OK”,什么时候处于“FAIL”,节点什么时候可用等,详见下面的解释: 当NODE_TIMEOUT时,触发failover,此时集群仍然可用的前提是:“大分区”(相对发生网络分区的Client-Master小分区端而言)端必须持有大部份Masters,且每个不可达的Master至少有一个Slave也在“大分区”端,且集群在小部分Nodes失效后仍然可以恢复有效性。 举个例子:
集群有N个Master,且每个Master都有一个Slave,那么集群的可用性只能容忍一个Master节点被分区隔离,也就是说只有一个Master处于小分区端,当第二个Master节点被分区隔离之前扔保持可用性的概率为1-(1 /(N*2-1)),这里的意思是:当第一个节点失效后,剩余N*2-1节点,此时没有Slave的Master失效的概率为1 /(N*2-1)。比如有10个节点,每个Master有一个Slave,当2个nodes被隔离或失效后,集群可用性的概率是:1/(10*2-1)=5.26%,此时集群不再可用。
为了避免上述情况发生,Redis Cluster提供了“replicas migration”机制,当Master节点发生failover后,集群会动态重新分配、平衡Slaves的分布,有效地提高了集群的可用性。
从节点选举逻辑
-
节点是已下线Master对应的Slave
-
FAIL状态的Master负责的hash slot 非空
-
主从节点之间的replication link断线的时长不能超过
NODE_TIMEOUT * REDIS_CLUSTER_SLAVE_VALIDITY_MULT
Nodes handshakeNodes通过端口发送Ping、Pong,除了Ping之外,节点会拒绝其他所有非本集群节点的packets,一个节点注册成为集群的新成员有2中方法:
-
通过“Cluster meet”指令引入,即将指定的node加入集群,集群将认为指定的node为“可信任”。
-
当其他nodes通过gossip引入了新的nodes,这些nodes也是被认为是“可信任的”。即:如果A信任B,B信任C,且B向A传播关于C的信息,那么A也信任C,并尝试连接C。
MOVED重定向Client可以将请求发给任意一个Node,包含Slaves,Node解析命令,检查语法,multiple keys是否在同一个slot。如果当前node持有该slot,那么命令直接执行并返回,否则当前Node向Client返回“MOVED”错误。
10.9.33.204:6101> keys * 1) test9 10.9.33.204:6101> get test9 value9 10.9.33.204:6101> get test8 (error) MOVED 905 10.9.42.37:6101
905指test8对应的slot,10.9.42.37:6101指slot所在的Node的ip:port,Client根据返回信息,重定向至指定的Node。若此过程中集群发生变更(配置调整、failover、resharding等),原来返回到Client可能已失真,重新发送命令时,可能会再次发生MOVED错误。
Redis集群提供集群模式的客户端,在跳转时会自动进行节点转向,以下是常用的:
Shell终端:redis-cli -c -h 10.9.33.204 -p 6101,集群提示重定向至Key所在的Slot:
10.9.33.204:6101> keys * 1) test9 10.9.33.204:6101> get test8 -> Redirected to slot [905] located at 10.9.42.37:6101 value8
Java:JedisCluster,需要配置集群信息,其他API如Jedis差异不大
<bean id="jedisClusterRaw" class="redis.clients.jedis.JedisCluster"> <constructor-arg index="0"> <set> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg type="String" value="${redis.host1}"/> <constructor-arg type="int" value="${redis.port1}"/> </bean> </set> </constructor-arg> <constructor-arg index="1" ref="jedisPoolConfig" /> </bean>
private static String configLocation = "classpath*:config-spring.xml"; private static ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocation); private void testCluster() { JedisCluster jedisCluster = ctx.getBean("jedisClusterRaw", JedisCluster.class); String v = jedisCluster.get("test6"); System.out.println("v:" + v); }
ASK重定向ASK重定向与MOVED重定向非常相似,两者最大的区别在于在resharding期间,当前的Client发送的命令暂时与指定的Node交互,在迁移期间,slot原来的keys仍有可能在原来的节点上,所以Client的命令仍然先经过原来的节点,对于不存的节点,再到新的节点进行尝试获取,一旦完成slot的迁移,原来slot接收到Client命令请求,则节点向客户端返回MOVED转向。对比ASK重定向,MOVED重定向指hash slots已经永久地被另一个node接管,后续Client的命令都是与该Node交互。ASK是Redis集群非阻塞的表现,即Redis集群不会因slot resharding而导致整个集群不可用。
节点失效检测跟大部份分布式框架一样,Redis Cluster节点间通过持续的心跳来保持信息同步,不过Redis Cluster节点信息同步是内部实现的,不依赖第三方组件,如zk。集群中的nodes持续交换ping、pong数据,消息协议使用Gossip,这两种packet数据结构一样,它们之间通过type字段区分。
节点定时向其他节点发送ping命令,它会随机选择存储的其他集群节点的其中三个进行信息“广播”,例如广播的信息包含一项是节点是否被标记为PFAIL/FAIL。PFAIL表示“可能已失效”,是尚未完全确认的失效状态(即可能是某个节点或少数Master认为其不可达);FAIL表示Node被集群大多数的Masters认定为失效(即大多数Master已认定为不可达,且不可达的时间已经超过配置的NODE_TIMEOUT)。
当节点收到其他节点广播的信息,它会记录被其他节点标记为失效的节点。举个例子,如果节点被某个节点标记为PFAIL,集群中大部份其他主节点也认为该节点进入了失效状态,那么该节点的状态会被标志为FAIL。当节点被标志为FAIL,这个节点已失效的信息会被广播至整个集群,所有集群中的节点都会将失效的节点标志为FAIL。
集群失效检测当某个Master或者Slave不能被大多数Nodes可达时,用于故障迁移并将合适Slave提升为Master。当Slave提升未能成功,集群不能正常工作。即集群不能处理Client的命令的请求,当Client发出命令请求时,集群节点都将返回错误内容的respone。
集群正常工作时,负责处理16384个slots的节点中,全部节点均正常。反之,若集群中有一部分hash slot不能正常使用,集群亦将停止工作,即集群进入了FAIL状态。对于集群进入FAIL状态,会有以下两种情况:
-
至少有一个hash slot不可用。
-
集群中大部份Master都进入了PFAIL状态。
上述为Redis集群原理概述,下面我们对比一下其他的Redis集群方案。
-
逻辑都是可控的,不依赖第三方分布式中间件
-
静态的数据分片,需要增加或减少Redis实例的数量,需要手工调整分片的程序
-
运维成本高,拓展需要手工操作
-
跨系统、平台维护相同的分片逻辑成本高,例如一个终端是 PHP 、另一个终端是JAVA,需要实现两套不同的分片逻辑
-
Redis客户端把请求发送到Twemproxy,路由规则发送到正确的Redis实例
-
LVS集群:实现twemproxy的负载均衡,提高proxy的可用性和扩容能力,使得twemproxy对应用透明
-
Sentinel集群:检测Redis主从的存活状态,当redis master失效,把slave提升为新的master
-
支持无效Redis实例的自动删除
-
减少客户端与Redis实例的连接数
但由于需要依赖组件较多和Redis请求都需要经过代理,在这过程中会造成性能损失,Twemproxy单节点的吞吐量对比Redis单实例,吞吐量要低不少,另外Twemproxy无法支持平滑支持Redis节点。
-
支持平滑增加/减少Reids实例
-
Codis Proxy:客户端连接的Redis代理服务
-
Codis Manager:Codis的管理工具
-
Codis Redis:维护Redis分支,基于2.8.13开发
-
Zookeeper:存放数据路由表和codis-proxy节点的元信息
附几张CodisManager的使用截图:
概览Dashboard
Slots分布
Slot迁移操作
当然Codis也有自身的一些缺陷,例如主从同步需要用户自身实现。
Redis集群根据上述说明,可以了解到,框架是采用P2P的模式,完全去中心化,数据存储模块和分布式的逻辑模块耦合在一起。这样带来的好处是部署非常简单,一体式部署,相对Codis而言,没有太多的其他概念、组件和依赖。但缺点也是比较明显,譬如分布式逻辑出现bug,只能回滚重启整个集群。
同时,我们通过上述可以了解到,Redis集群对协议进行了较大的修改,对客户端的交互升级不少,见上述“MOVED重定向”的客户端实现。由于历史原因,历史的应用均使用传统的Redis API,若业务更换Redis Client,存在不少问题,例如升级工作、数据迁移及测试,所以业内暂未被大规模使用。
综上所述,回答了以下问题:
Redis集群为了解决什么问题而存在的?解决线性可扩展性。
Redis集群诞生以前怎么解决这个问题?客户端分片、代理协助分片(Twemproxy)、查询路由、预分片、一致性哈希、客户端代理/转发等。
Redis集群采用什么方式保证线性可扩展性、可用性、数据一致性?Hash槽、查询路由、节点互联的混合模式。
Redis集群化面临的问题是什么?Redis集群本身要解决的是可伸缩问题,同时数据一致、集群可用等一系列问题。前者涉及到了节点的哈希槽的分配(含重分配),节点的增删,主从关系指定与变更(含自动迁移)这些具体的交互过程;后者则是故障发现,故障转移,选举过程等详细的过程。
Redis集群实现的核心思想和思路是什么?通过消息的交互(Gossip)实现去中心化(指的是集群自身的实现,不是指数据),通过Hash槽分配,实现集群线性可拓展。
以上是在研究/使用Redis集群过程中的一点思考与总结,好记性不如烂笔头,多多积累,以开阔思路,更好解决工作中遇到的问题。
Gossip协议Gossip也称“病毒感染算法”、“谣言传播算法”,Redis集群节点间使用Gossip协议交互。以下是Gossip算法的描述:
-
在总数为n+1的人群中,被感染的人数初始化为1,并向周围传播。(一个节点状态发生变化,并向邻近节点发送更新信息)
-
在每个周期内总有未被感染的人转变为被感染的人,方式为每个被感染的人随机感染b个人。(对于节点状态变化的信息随机发送给b个节点,下图b值为2,Redis Cluster中默认值为3)
-
经过足够的时间,所有人都会被感染。(随着时间推移,信息能够传递到所有节点)
对于Redis Cluster而言,node首先需要知道集群中至少一个seed node,此node向seed发送ping请求,接收到seed节点pong返回自身节点已知的所有nodes列表,然后与node解析返回的nodes列表并与之建立连接,同时也会向每个nodes发送ping,并从pong结果中merge出全局的nodes列表,并与之逐步建立连接。另数据传输的方式也是类似,如上述gossip协议。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Kafka从上手到实践-Kafka集群:重要配置和性能探讨
- 程序架构探讨—002 应用服务器集群的伸缩性之负载均衡
- 程序架构探讨—007 应用服务器集群的伸缩性之链路负载均衡
- 程序架构探讨—005 应用服务器集群的伸缩性之反向代理负载均衡
- 程序架构探讨—006 应用服务器集群的伸缩性之反向代理负载均衡
- 程序架构探讨—004 应用服务器集群的伸缩性之DNS域名解析负载均衡
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript设计模式
Ross Harmes、Dustin Diaz / 谢廷晟 / 人民邮电出版社 / 2008 / 45.00元
本书共有两部分。第一部分给出了实现具体设计模式所需要的面向对象特性的基础知识,主要包括接口、封装和信息隐藏、继承、单体模式等内容。第二部分则专注于各种具体的设计模式及其在JavaScript语言中的应用,主要介绍了工厂模式、桥接模式、组合模式、门面模式等几种常见的模式。为了让每一章中的示例都尽可能地贴近实际应用,书中同时列举了一些JavaScript 程序员最常见的任务,然后运用设计模式使其解决方......一起来看看 《JavaScript设计模式》 这本书的介绍吧!