NODE_TIMEOUT
时间内无法被多数masters(majority of maters)连接,因此如果分区在这一时间内被修复,则不会发生写入丢失。当分区持续时间超过NODE_TIMEOUT
时,所有在这段时间内对少数派master(minority side)的写入将会丢失。然而少数派一侧(minority side)将会在NODE_TIMEOUT
时间之后如果还没有连上多数派一侧,则它会立即开始拒绝写入,因此对少数派master而言,存在一个进入不可用状态的最大时间窗。在这一时间窗之外,不会再有写入被接受或丢失。NODE_TIMEOUT
外加重新选举所需的一小段时间(通常1~2秒)后恢复可用。1-(1/(N*2-1))
的可能性保持集群可用。1/(5*2-1)=11.11%
的可能性发生集群不可用。WAIT
命令)就可以回复client。HASH_SLOT = CRC16(key) mod 16384
{
字符{
的右面有一个}
字符{
和}
之间存在至少一个字符{
和}
之间的字符将用来计算HASH_SLOT,以保证这样的key保存在同一个slot中。{user1000}.following
和{user1000}.followers
这两个key会被hash到相同的hash slot中,因为只有user1000
会被用来计算hash slot值。foo{}{bar}
这个key不会启用hash tag因为第一个{
和}
之间没有字符。foo{{bar}}zap
这个key中的{bar
部分会被用来计算hash slotfoo{bar}{zap}
这个key中的bar
会被用来计算计算hash slot,而zap
不会CLUSTER RESET HARD
命令hard reset这个节点。configuration epoch
,链接状态,以及最后是该节点服务的hash slots。CLUSTER NODES
的描述。CLUSTER NODES
命令可以被发送到集群内的任意节点,他会提供基于该节点视角(view)下的集群状态以及每个节点的信息。CLUSTER NODES
输出的例子。
d1861060fe6a534d42d8a19aeb36600e18785e04 127.0.0.1:6379 myself - 0 1318428930 1 connected 0-1364$ redis-cli cluster nodes
cluster.h
和cluster.c
获得更多的细节。MEET
消息中。一条meet消息非常像一个PING
消息,但是它会强制接收者接受一个节点作为集群的一部分。节点只有在接收到系统管理员的如下命令后,才会向其他节点发送MEET
消息:CLUSTER MEET ip port
MOVED
错误,就像下面的例子:
$ redis-cli -p 7000 get foo10449
(error) MOVED 4995 127.0.0.1:7003
MOVED
错误告知3999 hash slot的所有权现在已经转移到了另一个节点。MOVED
错误之后)应该能记录下目前hash slot 3999由节点127.0.0.1:6381提供服务。这样如果一旦有一个新的命令需要发送,它就可以计算出目标key的hash slot并有很大的机会选择一个正确的节点。MOVED
重定向,客户端就通过CLUSTER NODES
或 CLUSTER SLOTS`命令刷新客户端侧的全部集群信息。当client遇到一个重定向错误,那么更有可能有多个hash slots被重新配置而不是仅仅这一个,所以尽快更新客户端配置通常是最有效的策略。CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
cluster_state:fail
状态,所有针对该节点的redis操作都将失败。
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node
ADDSLOTS
和DELSLOTS
,只是用来简单地在redis节点上分配或移除slot。分配slot意味着告诉一个master node他讲负责保存和服务指定hash slot的内容。ADDSLOTS
通常用在创建一个新cluster时为每个master节点指定16384个hash slots的一个子集。DELSLOTS
主要用在手动修改集群配置,或者用在调试相关的任务:在实际场景中这个命令极少被用到。
MIGRATING
,如果某个命令的keys在当前节点存在,那么节点将会接受对该slot的所有操作;如果有节点不存在,就会使用 -ASK
重定向到迁移的目标节点。IMPORTING
,则如果某个命令紧跟着一个ASKING
命令,那么该命令将会被执行。如果client没有给出ASKING
命令,该操作将会被-MOVED
重定向到它真正的hash slot所有者。CLUSTER SETSLOT 8 IMPORINT A
CLUSTER SETSLOT 8 MIGRATING B
CLUSTER GETKEYSINSLOT slot count
count
个keys。对返回的每个key,"redis-trib"向"A"发送一个MIGRATE
命令,这会将指定的key从"A"自动迁移到"B"。在迁移过程中,"A"和"B"两个实例都会被锁定很短的时间以确保没有竞争条件。这是MIGRATE
的工作方式:
MIGRATE target_host target_port key target_datebase id timeout
MIGRATE
将会连接对端实例,序列化并发送该key,一旦收到对方的OK回复,就会在本地删除旧的key。从外部client角度看,在任意给定时间,一个key只会存在于A或者B。MIGRATE
是一个通用命令,它也用于非cluster环境。MIGRATE
命令针对移动复杂key,比如很大的list,做过优化,以便能够尽可能快地在集群间移动key。尽管如此,如果使用redis的应用对延时由要求,对存在大key的集群进行重新配置仍然被认为是个不明智的举动。SETSLOT NODE
命令将会被发送到迁移涉及的两个节点,以便将他们的slots状态设置回普通状态。同样的命令通常也会被发送到集群中所有其他节点,以避免新配置在集群间自然传播的等待时间。CLUSTER SETSLOT
命令CLUSTER SETSLOT MIGRATING
ASK
重定向TRYAGAIN
错误提示等迁移完成后再试 (在笔者4.0本地测试环境中,只会产生ASK
错误,留待继续研究)CLUSTER SETSLOT IMPORTING
MOVED
重定向CLUSTER SETSLOT STABLE
MIGRATE
命令转移过的key无法恢复CLUSTER SETSLOT IMPORTING
CLUSTER SETSLOT MIGRATING
CLUSTER GETKEYSINSLOT
MIGRATE host port "" destination-db timeout [KEYS key [key ...]]
CLUSTER SETSLOT NODE
ASK
重定向。为何我们不能简单地使用 MOVED
重定向?因为使用 MOVED
意味着我们认为hash slot已经永久的服务于一个不同的节点,并且下一次查询应该尝试那个指定节点,而 AKS
意味着只是下一次查询需要发往指定的节点。IMPORTING
状态时才会接受改命令。ASKING
命令会为client设置一个单次标签(one-time flag),以允许该client可以访问一个IMPORTING
的slot一次。ASK
重定向的语义如下:
ASK
重定向命令,仅仅将这条查询重定向到某个节点的命令发送到指定的新节点,之后的命令还是继续发送给老的节点。ASKING
命令开始MOVED
消息,而client会永久更新hash slot 8的映射到新的IP:port。逐一,如果一个有bug的client提前修改了本地映射,这也不会成为问题,因为这个client不会在查询前带上ASKING
命令,这样B会通过MOVED
将client重定向回A节点。一个Redis Cluster client是可以不将slots配置(slot号到服务节点的地址的映射)记录在本地内存中的,这样它只需要随机找一个节点访问,并根据回复的重定向找到正确的服务节点,当然这样的client是非常没有效率的。
Redis Cluster client应该通过缓存slots配置而变得尽可能聪明。当然,这个配置并非必须总是最新的。因为跟错误的节点通信后会简单地获得一个重定向,并且这会出发一次客户端视图的更新。
Clients通常需要在下面两个场景中获取一次全量的slots/nodes映射信息:
MOVED
重定向信息时逐一client在接收到MOVED
重定向时,可以只更新单个slot,当然这通常不是很有效率,因为一般来说,每次配置变动通常会设计多个slots(比如发生了一次slave晋升,则所有该节点服务的slots都会被重新映射)。而在收到MOVED
重定向时重新获取所有slots的映射处理起来更为简单。
为了获取slots配置,Redis Cluster除了提供了CLUSTER NODES
命令外,还提供了一个新选择,这个新的命令只提供了client需要的信息,并且不需要client(对接收到的数据)进行解析。
这个新命令就是CLUSTER SLOTS
,它提供了一个slots范围数组,并关联了服务于对应范围的master和slave节点。
下面是CLUSTER SLOTS
输出的例子:
127.0.0.1:7000> cluster slots
1) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 7001
4) 1) "127.0.0.1"
2) (integer) 7004
2) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
4) 1) "127.0.0.1"
2) (integer) 7003
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
4) 1) "127.0.0.1"
2) (integer) 7005 d
更多对该命令的解释请参考 CLUSTER SLOTS
该命令(CLUSTER SLOTS
)不保证可以反悔16384 slots中的所有信息,如果slots配置缺失,clients应该将其初始化未NULL,并在用户尝试在这些未分配slots上执行命令式上报错误。
在返回一个错误给调用者之前,当一个slot被发现没有分配,client应该再次尝试获取slots配置以检查当前cluster已经正确地配置了。
MOVED
错误READONLY
命令,可以将slave节点设置为可读READWRITE
命令,可以清除该节点的只读flags值NODE_TIMEOUT
/2时间内没有发送过ping或从之接收过pong的节点。在NODE_TIMEOUT
耗尽之前,节点同样会尝试重新连接哪个节点,以确保这不是由于当前TCP连接问题造成的。NODE_TIMEOUT
被设置为一个较小的数字,同时节点数目又很大,从全局看,交换的消息数目会是很可观的,因为在NODE_TIMEOUT
一半的时间内,每个节点会尝试ping其他所有节点。currentEpoch
和configEpoch
字段——由Redis Cluster用来加载分布式算法(下一节中会详细描述)。对slave,configEpoch
就是它的master的configEpoch
。node flags
——用来表明节点是slave,还是master,以及其他一些由单bit表示的信息。PFAIL
和FAIL
。PFAIL
标签意味着 可能故障 ,是一个不需要确认的故障类型。FAIL
意味着一个节点已经失败,他必须在一段固定的时间内由多数master进行确认。NODE_TIMEOUT
时间后,就会将该节点标记为PFAIL
。无论master还是slave都可以姜其他节点标记为PFAIL
,而不管对方的类型。NODE_TIMEOUT
。为了让这一机制正确工作,NODE_TIMEOUT
必须大于一个网络正常往返的时间。为了增加可靠性,在NODE_TIMEOUT
时间过去一半时,如果节点还没有得到回复,它会尝试重新连接其他节点。这一机制确保了连接保持活跃,因此损坏的链接通常不会到之错误的在节点间报告失败。NODE_TIMEOUT
* FAIL_REPORT_VALIDITY_MULT
(在当前redis实现中,FAIL_REPORT_VALIDITY_MULTI
被设置为2且不可配置) 这段时间内将节点A标注为 PFAIL
或 FAIL
。FAIL
FAIL
消息给所有可达节点FAIL
消息将会强制所有接收到的节点将失联节点(节点B)标记为FAIL
,而不管当前自己是否已将其标记为PFAIL
FAIL
flag总是单向的,即一个节点可以从PFAIL
变为FAIL
,但是不能反向转变。FLAG
标签只有在以下情况下才会被清除:
FAIL
标签可以清除因为slave节点不会发生故障转移。FAIL
标签可以被清除,该master继续等待被配置后加入集群PFAIL
-> FAIL
转变的过程中,使用了弱一致机制(weak agreeement):
FAIL
条件并通过 FAIL
消息强制集群中其他节点接受该条件,还是无法保证消息可以倍所有节点接收到,因为此时可能因为分区问题导致某些节点不可达。FAIL
,或是一些少数派节点认定某个节点不在FAIL
状态。这两种状态下集群对某个节点最终一定会(在集群全局)有一个唯一的视图(view)。
FAIL
,所有其他节点将最终将会把这个master标记为FAIL
,因为在指定的时间窗内,集群里会有足够多的失败被报告。FAIL
,slave晋升将不会发生,所有节点将会根据上述的FAIL
状态清除规则清除该节点的FAIL
状态(例如通过"在N倍NODE_TIMEOUT内没有晋升动作"这一条规则)。FAIL
flag只是用来作为一个触发机制,它将触发执行slave晋升算法的安全部分,以便将slave晋升。理论上slave独立地运作并在发现它的master不可达后启动一次晋升,并在多数masters可以触达该master时等待其他master拒绝认可。然而,PFAIL
-> FAIL
状态变迁、弱一致、 在cluster可达节点间通过FAIL
消息强制状态的生成,这些额外的复杂度的引入,在实践上是有它的优势的。因为这些机制的引入,使得集群(可以意识到自己)在处于一个error状态下,所有节点可以拒绝写入操作。从从使用redis cluster的应用的角度看,这是一个必要的特性。同时这样也可以避免由于slave自己的问题导致无法连接master,进而导致错误的选举尝试。currentEpoch
是一个64bit无符号整数。currentEpoch
设置为0。currentEpoch
更新为发送方的epoch。configEpoch
的节点(提出的主张)。(【译注】current epoch是用来标识集群epoch的,集群epoch取自所有节点中configEpoch最大的那个节点的configEpoch)currentEpoch
仅仅被用在slave晋升。简单来说,epoch
是集群的一个本地时钟,拥有大epoch
的消息总是能赢得拥有相对小的epoch
的消息。configEpoch
,和其服务的hash slots的bitmap信息。configEpoch
被设置为0.configEpoch
将会在slave选举中被创建。在尝试替代失败的master时,slave会增加他的epoch并尝试得到多数masters的授权。当一个slave被选中,一个新的唯一的configEpoch
会被创建,同时该slave会使用这个新的configEpoch
并转变为一个master。configEpoch
时如何帮助解决冲突的。configEpoch
,此时的configEpoch
是他的master在上一次包交换中携带的configEpoch
。这允许其他实例检测到这个slave有一个老的配置并且需要更新(master节点将不会向一个拥有旧配置的slave授权选票)。configEpoch
发生变化,它就会被所有收到这条信息的节点永久地保存在各自的node.conf文件里。同理,currentEpoch
也会被保存。Redis保证会在执行下一个操作前保存这两个变量并同步到磁盘。configEpoch
的值在failover时使用一个简单的算法来保证产生一个新的、递增的、唯一的值。FAIL
状态,并且该slave已经要求晋升为master时,slave选举就会发生。FAIL
状态,那么它的所有slave都可以开始一个选举,但是只有一个slave会赢得最终的选举并晋升为master。FAIL
状态currentEpoch
计数,并向master实例请求选票。FAILOVER_AUTH_REQUEST
包请求选票。然后它会在不超过2倍NODE_TIMEOUT
时间内等待所有master的回复(一般至少等2秒)。FAILOVER_AUTH_ACK
,在时间窗 NODE_TIMEOUT * 2
以内,它就再也不能向该master的任何其他slave投票了。从安全性保证上来说,这一规则不是必须的,但是它有助于避免多个slave在几乎相同的时间内同时被选中(哪怕它们的configEpoch是不同的),而这显然不是期望得到的结果。NODE_TIMEOUT
时间窗(至少2s)内没有得到多数的回复,选举就会中止,而一次新的尝试将在 NODE_TIMEOUT
* 4 (至少4s)之后尝试。FAIL
状态,一个slave会在尝试选举前等待一小段时间。等待的时间按照如下公式计算:DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds
FAIL
状态扩散到整个集群,否则slave可能会在多数master不知道该FAIL
时请求选举并被拒绝投票。SLAVE_RANK
是该slave针对它从master获得的备份数据的总量的排序。在master失败之后,slave之间通过交换消息来创建一个(最大努力)排序:拥有最新备份offset的slave获得排序0,第二个更新为1,以此类推。这样最新的slave会尝试最先开始选举。configEPoch
,这个configEpoch
会比所有其他现存的master更大。它会在ping和pong包中作为master广播自己,同时提供自己的服务slot和比之前的master更大的configEpoch
。UPDATE
包之后。FAILOVER_AUTH_REQUEST
后就会开始一次选举。currentEpoch
小于该值的请求投票。一旦master对投票请求回复确认,lasterVoteEpoch就会同步更新并安全地保存到磁盘。FAIL
时,master才会投票给该slaveFAIL_AUTH_REQUEST
的cureentEpoch
的值小于master的currentEpoch
,那么该选举请求将被忽略。因此,master的回复总是与FAIL_AUTH_REQUEST
拥有相同的currentEpoch
。如果同样的slave再次请求选票,并增加了currentEpoch
,这可以保证针对旧请求的DELAY的投票不会在新投票请求中被接受。NODE_TIMEOUT
*2时间窗内,不会为该master的任意slave再次投票。这不是严格的需求,因为两个slave不可能在一个相同的epoch中同时获胜。然而,在实践中,它保证了当一个slave被选举后,它拥有足够的时间通知其他slave并避免其他slave赢得新一轮选举的可能性,否则这会造成有一次没有必要的failover。FAIL
状态,且master没有在当前的term(任期,代?)中投票过,那么它一定会将授予自己的投票。最好的slave总是更可能启动一次选举并在其他slave之前赢得选举,因为由于它拥有更高的排名,它总是会先于其他slave发起选举。configEpoch
小于master表中为slave宣称的slot服务的master的configEpoch
。记得之前提起过,slave发送的消息中使用它的master的configEpoch
,以及它的master服务的slots。这意味着请求选票的slave必须拥有它打算failover的master的slot配置,并且这个配置需要比授权选票的master更新或至少相等。UPDATE
消息会及时重新配置它,但我们假设所有的UPDATE
消息也丢失了)。这时,slave C尝试选图来替代B。接下来:
configEpoch
。UPDATE
消息。Redis Cluster中很重要的部分就是提供一种机制,用来传播集群中哪个节点服务于哪些hash slot信息。这无论是在集群启动还是在slave晋升时都非常重要。
同样的机制保证了节点在不限期遇到分区问题后能以一种明智的方式重新加入集群。
有两种生成hash slots配置的方式:
UPDATE
消息。因为每个heartbeat包都有发送方的configEpoch
和其服务的hash slots,如果接收方发现发送方的信息过期,它姜发送一个包含了新信息的包,强制过期节点更新自己的信息。心跳包消息或UPDATE
消息的接收方使用一种简单的规则来更新表映射中的hash slots到对应的节点。当一个新的Redis Cluster 节点被创建,它的本地hash slot表被初始化为NULL,这样每个hash slot就不会被绑定到任何节点。它看上去就像这样:
0 -> NULL
1 -> NULL
...
16383 -> NULL
配置传播的规则如下:
CLUSTER ADDSLOTS
,一般通过redis-trib
命令行工具,或其它类似的工具)所有的slots给它的master,这一信息会很快在整个集群中传播。configEpoch
比当前拥有该slot的master的configEpoch
更大,则重新绑定hash slot到新节点。configEpoch
最大的节点获得slot的拥有权达成一致。configuration epoch
将被增加以确保改动会被扩散到整个集群。configuration epoch
为3。所有更新了最新集群信息的接收者却看到相同的hash slots已经被关联到了节点B,并且节点B拥有更高的configuration epoch
。因此它们会发送一条UPDATE
消息给A,同时带上这些slots的心配置。A将会根据rule2更新自己的配置。备份迁移(replica migration)
的概念(特性),用来提升系统可用性。在一个由master-slave组成的集群中,如果slaves和masters之间的映射关系是固定的,那么集群的可用性随着时间的推移,姜会因为单个节点的失败而变得越来越差。config epoch
提供任何的一致性和/或版本化保证。相反,在master没有回归之前,它使用了一种避免slave块迁移(mass-migration)的算法。这种算法保证了最终(一旦集群配置变得稳定后)每个master至少可以拥有一个slave。FAIL
状态的slave节点。cluster-migration-barrier
的用户配置参数控制,该参数指定了在发生备份迁移之前,一个master必须拥有的好slave的数目。例如,如果该参数被设置为2,那么只有在某个master拥有2个可工作slaves时,其中一个slave才能尝试迁移(【译注】通过检查代码,应该是拥有2个或2个以上可工作slaves时,其中一个可以发生迁移)。configEpoch
值,并且这个值被保证是唯一的。configEpoch
值,它们将会仅仅把本地的currentEpoch
自增并寄希望于同一时间内不会有冲突。比如,系统管理员同时触发了两个events:
TAKEOVER
选项的CLUSTER FAILOVER
会手动晋升一个slave节点到master,并且不需要多数master的同意。这是个有用的操作,比如,在多数据中心创建时configuration epochs
而不需要其他节点的同意。configuration epoch
为集群中的最大值加1(除非此时B的configuration epoch
已经是集群中最大的),而不需要请求其他节点的同意。configuration epoch
而请求其他节点的同意,这将会很没有效率。而且,这回要求集群中的节点每次都通过fsync来保存这一新的配置(configuration epoch)。因为这些原因,我们采用如下行为处理reshardings时的configEpoch
:我们只需要在第一个hash slot迁移时生成一个新的configEpoch
,这在生产环境中会更加有效率。configuration epoch
的情况。一个resharding操作有管理员发起,同时一次failover恰巧发生,外加一点坏运气,如果各自的epoch没有在足够短的时间内扩散开,这将会会导致currentEpoch
冲突。configuration epoch
。configEpoch
时,这不会有什么问题。相对而言,更重要的是,当slave故障恢复一个master时,它需要拥有唯一的configuration epoch
。configEpoch
。configEpoch
结束某个操作时, 冲突解决算法 将会被用来处理这一场景。
configEpoch
currentEpoch
增加1,并用这一新值作为自己的configEpoch
。configEpoch
,那么除了拥有最大ID的节点,所有其他节点都将被前移,来保证最终每个节点都拥有一个唯一的configEpoch
。configEpoch
开始,尽管它们并没有被用到,因为redis-trib会保证使用CONFIG SET-CONFIG-EPOCH
为每一个节点设置不同的ID。configuration epoch
(因为有冲突解决算法的保证)。CLUSTER RESET
命令包含连个变种:
currentEpoch
,configEpoch
,和lastVoteEpoch
为0FLUSHALl
先清空数据,然后再reset。CLUSTER FORGET
命令来实现。