Redis 集群规范.docx
- 文档编号:11352789
- 上传时间:2023-02-28
- 格式:DOCX
- 页数:31
- 大小:40.20KB
Redis 集群规范.docx
《Redis 集群规范.docx》由会员分享,可在线阅读,更多相关《Redis 集群规范.docx(31页珍藏版)》请在冰豆网上搜索。
Redis集群规范
Redis集群规范(仍在开发中)
Redis集群的目标
Redis集群是Redis的一个分布式实现,主要是为了实现以下这些目标(按在设计中的重要性排序):
∙在1000个节点的时候仍能表现得很好并且可扩展性(scalability)是线性的。
∙没有合并操作,这样在Redis的数据模型中最典型的大数据值中也能有很好的表现。
∙写入安全(Writesafety):
那些与大多数节点相连的客户端所做的写入操作,系统尝试全部都保存下来。
不过公认的,还是会有小部分(smallwindows?
)写入会丢失。
∙可用性(Availability):
在绝大多数的主节点(masternode)是可达的,并且对于每一个不可达的主节点都至少有一个它的从节点(slave)可达的情况下,Redis集群仍能进行分区(partitions)操作。
这篇文档要讲的是,在Redis仓库(放在Github上)中的 unstable 分支中实现的功能。
实现的功能子集
Redis集群实现了所有在非分布式Redis版本中出现的处理单一键值(key)的命令。
那些使用多个键值的复杂操作,比如set里的并集(unions)和交集(intersections)操作,就没有实现。
通常来说,那些处理命令的节点获取不到键值的所有操作都不会被实现。
在将来,用户或许可以通过使用 MIGRATECOPY 命令,在集群上用 计算节点(ComputationNodes) 来执行多键值的只读操作,但Redis集群本身不会执行复杂的多键值操作来把键值在节点间移来移去。
Redis集群不像单机版本的Redis那样支持多个数据库,集群只有数据库0,而且也不支持 SELECT 命令。
Redis集群协议中的客户端和服务器端
在Redis集群中,节点负责存储数据、记录集群的状态(包括键值到正确节点的映射)。
集群节点同样能自动发现其他节点,检测出没正常工作的节点,并且在需要的时候在从节点中推选出主节点。
为了执行这些任务,所有的集群节点都通过TCP连接(TCPbus?
)和一个二进制协议(集群连接,clusterbus)建立通信。
每一个节点都通过集群连接(clusterbus)与集群上的其余每个节点连接起来。
节点们使用一个gossip协议来传播集群的信息,这样可以:
发现新的节点、发送ping包(用来确保所有节点都在正常工作中)、在特定情况发生时发送集群消息。
集群连接也用于在集群中发布或订阅消息。
由于集群节点不能代理(proxy)请求,所以客户端在接收到重定向错误(redirectionserrors) -MOVED 和 -ASK 的时候,将命令重定向到其他节点。
理论上来说,客户端是可以自由地向集群中的所有节点发送请求,在需要的时候把请求重定向到其他节点,所以客户端是不需要保存集群状态。
不过客户端可以缓存键值和节点之间的映射关系,这样能明显提高命令执行的效率。
安全写入
Redis集群节点间使用异步冗余备份(asynchronousreplication),所以在分区过程中总是存在一些时间段(windows?
),在这些时间段里容易丢失写入数据。
但是一个连接到绝大部分主节点的客户端的时间段,与一个连接到极小部分主节点的客户端的时间段是相当不同的。
Redis集群会努力尝试保存所有与大多数主节点连接的客户端执行的写入,但以下两种情况除外:
1)一个写入操作能到达一个主节点,但当主节点要回复客户端的时候,这个写入有可能没有通过主从节点间的异步冗余备份传播到从节点那里。
如果在某个写入操作没有到达从节点的时候主节点已经宕机了,那么该写入会永远地丢失掉,以防主节点长时间不可达而它的一个从节点已经被提升为主节点。
2)另一个理论上可能会丢失写入操作的模式是:
∙因为分区使一个主节点变得不可达。
∙故障转移(failover)到主节点的一个从节点。
(即从节点被提升为主节点)
∙过一段时间之后主节点再次变得可达。
∙一个没有更新路由表(routingtable)的客户端或许会在集群把这个主节点变成一个从节点(新主节点的从节点)之前对它进行写入操作。
实际上这是极小概率事件,这是因为,那些由于长时间无法被大多数主节点访问到的节点会被故障转移掉,不再接受任何写入操作,当其分区修复好以后仍然会在一小段时间内拒绝写入操作好让其他节点有时间被告知配置信息的变更。
通常所有节点都会尝试通过非阻塞连接尝试(non-blockingconnectionattempt)尽快去访问一个再次加入到集群里的节点,一旦跟该节点建立一个新的连接就会发送一个ping包过去(这足够升级节点配置信息)。
这就使得一个节点很难在恢复可写入状态之前没被告知配置信息更改。
Redis集群在拥有少数主节点和至少一个客户端的分区上容易丢失为数不少的写入操作,这是因为如果主节点被故障转移到集群中多数节点那边的节点上,那么所有发送到这些主节点的写入操作都会永久性丢失。
一个主节点要被故障转移,必须是大多数主节点在至少 NODE_TIMEOUT 这么长时间里无法访问该节点,所以如果分区在这段时间之前修复好了,就没有写入操作会丢失。
当分区故障持续超过 NODE_TIMEOUT,集群的多数节点这边会在一超过 NODE_TIMEOUT 这个时间段后开始拒绝往受损分区进行写入,所以在少数节点这边(指分区)变得不再可用后,会有一个写入操作最大损失范围(因为在指定时间段后将不会再有写入操作被接收或丢失)。
可用性
Redis集群在分区的少数节点那边不可用。
集群假设在分区的多数节点这边至少有大多数可达的主节点,并且对于每个不可达主节点都至少有一个从节点可达,在经过了差不多 NODE_TIMEOUT 这么长时间后,有个从节点被推选出来并故障转移掉它的主节点,这时集群又再恢复可用。
这意味着Redis集群的设计是能容忍集群中少数节点的出错,但对于要求大量网络分块(largenetsplits)的可用性的应用来说,这并不是一个合适的解决方案。
举个例子,一个由N个主节点组成的集群,每个主节点都只有一个从节点。
当有一个节点(因为故障)被分割出去后,集群的多数节点这边仍然是可访问的。
当有两个节点(因故障)被分割出去后集群仍可用的概率是 1-(1/(N*2-1))(在第一个节点故障出错后总共剩下 N*2-1 个节点,那么失去冗余备份(即失去从节点)的那个主节点也故障出错的概率是 1/(N*2-1)))。
比如一个拥有5个节点的集群,每个节点都只有一个从节点,那么在两个节点从多数节点这边分割出去后集群不再可用的概率是1/(5*2-1)=0.1111,即有大约11%的概率。
表现
在Redis集群中节点并不是把命令转发到管理所给出的键值的正确节点上,而是把客户端重定向到服务一定范围内的键值的节点上。
最终客户端获得一份最新的集群表示,里面有写着哪些节点服务哪些键值子集,所以在正常操作中客户端是直接联系到对应的节点并把给定的命令发过去。
由于使用了异步冗余备份,节点不会等待其他节点对写入操作的承认。
(目前正在开发可选同步冗余备份,极有可能会添加入将来的代码发布中)
同样,由于一些命令不支持操作多个键值,如果不是碎片重整(resharding),那么数据是永远不会在节点间移动的。
所以普通操作是可以被处理得跟在单一Redis上一样的。
这意味着,在一个拥有N个主节点的Redis集群中,由于Redis的设计是支持线性扩展的,所以你可以认为同样的操作在集群上的表现会跟在单一Redis上的表现乘以N一样。
同时,询问(query)通常在一次循环中被执行,客户端会保持跟节点持续不断的连接,所以延迟数据跟在单一Reids上是一样的。
为什么要避免使用合并操作
Redis集群的设计是避免在多个节点中存在同个键值对的冲突版本,这是因为Redis数据模型并不提倡这么做:
Redis中的值通常都是比较大的,经常可以看到列表或者排序好的集合中有数以百万计的元素。
数据类型也是语义复杂的。
传输和合并这样的值将会变成一个主要的性能瓶颈。
键分布模型
键空间被分割为16384槽(slot),事实上集群的最大节点数量是16384个。
(然而建议最大节点数量设置在1000这个数量级上)
所有的主节点都负责16384个哈希槽中的一部分。
当集群处于稳定状态时,集群中没有在执行重配置(reconfiguration)操作,每个哈希槽都只由一个节点进行处理(不过主节点可以有一个或多个从节点,可以在网络断线或节点失效时替换掉主节点)。
以下是用来把键映射到哈希槽的算法(下一段哈希标签例外就是按照这个规则):
HASH_SLOT=CRC16(key)mod16384
其中,CRC16的定义如下:
∙名称:
XMODEM(也可以称为ZMODEM或CRC-16/ACORN)
∙输出长度:
16bit
∙多项数(poly):
1021(即是x16 +x12 +x5 +1)
∙初始化:
0000
∙反射输入字节(ReflectInputbyte):
False
∙反射输入CRC(ReflectOutputCRC):
False
∙用于输出CRC的异或常量(XorconstanttooutputCRC):
0000
∙该算法对于输入"123456789"的输出:
31C3
CRC16的16位输出中的14位会被使用(这也是为什么上面的式子中有一个对16384取余的操作)。
在我们的测试中,CRC16能相当好地把不同的键均匀地分配到16384个槽中。
注意:
在本文档的附录A中有CRC16算法的实现。
键哈希标签(Keyshashtags)
计算哈希槽可以实现哈希标签(hashtags),但这有一个例外。
哈希标签是确保两个键都在同一个哈希槽里的一种方式。
将来也许会使用到哈希标签,例如为了在集群稳定的情况下(没有在做碎片重组操作)允许某些多键操作。
为了实现哈希标签,哈希槽是用另一种不同的方式计算的。
基本来说,如果一个键包含一个"{...}"这样的模式,只有 { 和 }之间的字符串会被用来做哈希以获取哈希槽。
但是由于可能出现多个 { 或 },计算的算法如下:
∙如果键包含一个 { 字符。
∙那么在 { 的右边就会有一个 }。
∙在 { 和 } 之间会有一个或多个字符,第一个 } 一定是出现在第一个 { 之后。
然后不是直接计算键的哈希,只有在第一个 { 和它右边第一个 } 之间的内容会被用来计算哈希值。
例子:
∙比如这两个键 {user1000}.following 和 {user1000}.followers 会被哈希到同一个哈希槽里,因为只有 user1000 这个子串会被用来计算哈希值。
∙对于 foo{}{bar} 这个键,整个键都会被用来计算哈希值,因为第一个出现的 { 和它右边第一个出现的 } 之间没有任何字符。
∙对于 foo{{bar}}zap 这个键,用来计算哈希值的是 {bar 这个子串,因为它是第一个 { 及其右边第一个 } 之间的内容。
∙对于 foo{bar}{zap} 这个键,用来计算哈希值的是 bar 这个子串,因为算法会在第一次有效或无效(比如中间没有任何字节)地匹配到 { 和 } 的时候停止。
∙按照这个算法,如果一个键是以 {} 开头的话,那么就当作整个键会被用来计算哈希值。
当使用二进制数据做为键名称的时候,这是非常有用的。
下面是用Ruby和C语言实现的 HASH_SLOT 函数,有加上哈希标签例外。
Ruby样例代码:
defHASH_SLOT(key)
s=key.index"{"
ifs
e=key.index"}",s+1
ife&&e!
=s+1
key=key[s+1..e-1]
end
end
crc16(key)%16384
end
C样例代码:
unsignedintHASH_SLOT(char*key,intkeylen){
ints,e;/*start-endindexesof{and}*/
/*Searchthefirstoccurrenceof'{'.*/
for(s=0;s if(key[s]=='{')break; /*No'{'? Hashthewholekey.Thisisthebasecase.*/ if(s==keylen)returncrc16(key,keylen)&16383; /*'{'found? Checkifwehavethecorresponding'}'.*/ for(e=s+1;e if(key[e]=='}')break; /*No'}'ornothingbetween{}? Hashthewholekey.*/ if(e==keylen||e==s+1)returncrc16(key,keylen)&16383; /*Ifweareherethereisbotha{anda}onitsright.Hash *whatisinthemiddlebetween{and}.*/ returncrc16(key+s+1,e-s-1)&16383; } 集群节点属性 在集群中,每个节点都有一个唯一的名字。 节点名字是一个十六进制表示的160bit随机数,这个随机数是节点第一次启动时获得的(通常是用/dev/urandom)。 节点会把它的ID保存在配置文件里,以后永远使用这个ID,只要这个节点配置文件没有被系统管理员删除掉。 节点ID是用于在整个集群中标识每个节点。 一个给定的节点可以在不改变节点ID的情况下改变IP和地址。 集群能检测到IP或端口的变化,然后使用在集群连接(clusterbus)上的gossip协议来发布广播消息,通知配置变更。 每个节点都有其他相关信息是所有节点都知道的: ∙节点的IP地址和TCP端口号。 ∙各种标识。 ∙节点使用的哈希槽。 ∙最近一次用集群连接发送ping包的时间。 ∙最近一次在回复中收到一个pong包的时间。 ∙最近一次标识节点失效的时间。 ∙该节点的从节点个数。 ∙如果该节点是从节点,会有主节点ID信息。 (如果它是个主节点则该信息置为0000000...) 使用 CLUSTERNODES 命令可以获得以上的一些信息,这个命令可以发送到集群中的所有节点,无论主节点还是从节点。 下面的例子是在一个只有三个节点的小集群中发送 CLUSTERNODES 命令到一个主节点得到的输出。 $redis-cliclusternodes d1861060fe6a534d42d8a19aeb36600e18785e04: 0myself-01318428930connected0-1364 3886e65cc906bfd9b1f7e7bde468726a052d1dae127.0.0.1: 6380master-13184289301318428931connected1365-2729 d289c575dcbc4bdd2931585fd4339089e461a27d127.0.0.1: 6381master-13184289311318428931connected2730-4095 在上面罗列出来的信息中,各个域依次表示的是: 节点ID,IP地址: 端口号,标识,上一次发送ping包的时间,上一次收到pong包的时间,连接状态,节点使用的哈希槽。 集群拓扑结构 Redis集群是一个网状结构,每个节点都通过TCP连接跟其他每个节点连接。 在一个有N个节点的集群中,每个节点都有N-1个流出的TCP连接,和N-1个流入的连接。 这些TCP连接会永久保持,并不是按需创建的。 节点握手 节点总是在集群连接端口接受连接,甚至会回复接收到的ping包,即使发送ping包的节点是不可信的。 然而如果某个节点不被认为是在集群中,那么所有它发出的数据包都会被丢弃掉。 只有在两种方式下,一个节点才会认为另一个节点是集群中的一部分: ∙当一个节点使用 MEET 消息介绍自己。 一个meet消息跟一个 PING 消息完全一样,但它会强制让接收者接受发送者为集群中的一部分。 只有在系统管理员使用以下命令要求的时候,节点才会发送 MEET 消息给其他节点: CLUSTERMEETipport ∙一个已被信任的节点能通过传播gossip消息让另一个节点被注册为集群中的一部分。 也就是说,如果A知道B,B知道C,那么B会向A发送C的gossip消息。 A收到后就会把C当作是网络中的一部分,并且尝试连接C。 这意味着,只要我们往任何连接图中加入节点,它们最终会自动形成一个完全连接图。 从根本上来说,这表示集群能自动发现其他节点,但前提是有一个由系统管理员强制创建的信任关系。 这个机制能防止不同的Redis集群因为IP地址变更或者其他网络事件而意外混合起来,从而使集群更具健壮性。 当节点的网络连接断掉时,它会积极尝试连接所有其他已知节点。 MOVED重定向 一个Redis客户端可以自由地向集群中的任意节点(包括从节点)发送查询。 接收的节点会分析查询,如果这个命令是集群可以执行的(就是查询中只涉及一个键),那么节点会找这个键所属的哈希槽对应的节点。 如果刚好这个节点就是对应这个哈希槽,那么这个查询就直接被节点处理掉。 否则这个节点会查看它内部的哈希槽->节点ID映射,然后给客户端返回一个MOVED错误。 一个MOVED错误如下: GETx -MOVED3999127.0.0.1: 6381 这个错误包括键(3999)的哈希槽和能处理这个查询的节点的ip: 端口号(127.0.0.1: 6381)。 客户端需要重新发送查询到给定ip地址和端口号的节点。 注意,即使客户端在重发查询之前等待了很长一段时间,与此同时集群的配置信息发生改变,如果哈希槽3999现在是为其他节点服务,那么目标节点会再向客户端回复一个MOVED错误。 从集群的角度看,节点是以ID来标识的。 我们尝试简化接口,所以只向客户端暴露哈希槽和用“ip: 端口号”标识的Redis节点之间的映射。 虽然并没有要求,但是客户端应该尝试记住哈希槽3999是服务于127.0.0.1: 6381。 这样的话一旦有一个新的命令需要发送,它能计算出目标键的哈希槽,提高找到正确节点的机率。 注意,当集群是稳定的时候,所有客户端最终都会得到一份哈希槽->节点的映射表,这样能使得集群效率非常高: 客户端直接定位目标节点,不用重定向、或代理或发生其他单点故障(singlepointoffailureentities)。 一个客户端也应该能处理本文后面将提到的-ASK重定向错误。 集群在线重配置(livereconfiguration) Redis集群支持在集群运行过程中添加或移除节点。 实际上,添加或移除节点都被抽象为同一个操作,那就是把哈希槽从一个节点移到另一个节点。 ∙向集群添加一个新节点,就是把一个空节点加入到集群中并把某些哈希槽从已存在的节点移到新节点上。 ∙从集群中移除一个节点,就是把该节点上的哈希槽移到其他已存在的节点上。 所以实现这个的核心是能把哈希槽移来移去。 从实际角度看,哈希槽就只是一堆键,所以Redis集群在重组碎片(reshard)时做的就是把键从一个节点移到另一个节点。 为了理解这是怎么工作的,我们需要介绍 CLUSTER 的子命令,这些命令是用来操作Redis集群节点上的哈希槽转换表(slotstranslationtable)。 以下是可用的子命令: ∙CLUSTERADDSLOTSslot1[slot2]...[slotN] ∙CLUSTERDELSLOTSslot1[slot2]...[slotN] ∙CLUSTERSETSLOTslotNODEnode ∙CLUSTERSETSLOTslotMIGRATINGnode ∙CLUSTERSETSLOTslotIMPORTINGnode 头两个命令,ADDSLOTS 和 DELSLOTS,就是简单地用来给一个Redis节点指派(assign)或移除哈希槽。 在哈希槽被指派后,节点会将这个消息通过gossip协议向整个集群传播。 ADDSLOTS 命令通常是用于在一个集群刚建立的时候快速给所有节点指派哈希槽。 当 SETSLOT 子命令使用 NODE 形式的时候,用来给指定ID的节点指派哈希槽。 除此之外哈希槽能通过两个特殊的状态来设定,MIGRATING 和 IMPORTING: ∙当一个槽被设置为MIGRATING,原来持有该哈希槽的节点仍会接受所有跟这个哈希槽有关的请求,但只有当查询的键还存在原节点时,原节点会处理该请求,否则这个查询会通过一个 -ASK 重定向(-ASKredirection)转发到迁移的目标节点。 ∙当一个槽被设置为IMPORTING,只有在接受到ASKING命令之后节点才会接受所有查询这个哈希槽的请求。 如果客户端一直没有发送ASKING命令,那么查询都会通过 -MOVED 重定向错误转发到真正处理这个哈希槽的节点那里。 这么讲可能显得有点奇怪,现在我们用实例让它更清晰些。 假设我们有两个Redis节点,称为A和B。 我们想要把哈希槽8从节点A移到节点B,所以我们发送了这样的命令: ∙我们向节点B发送: CLUSTERSETSLOT8IMPORTINGA ∙我们向节点A发送: CLUSTERSETSLOT8MIGRATINGB 其他所有节点在每次被询问到的一个键是属于哈希槽8的时候,都会把客户端引向节点"A"。 具体如下: ∙所有关于已存在的键的查询都由节点"A"处理。 ∙所有关于不存在于节点A的键都由节点"B"处理。 这种方式让我们可以不用在节点A中创建新的键。 同时,一个叫做 redis-trib 的特殊客户端,它也是Redis集群的配置程序(configurationutility),会确保把已存在的键从节点A移到节点B。 这通过以下命令实现: CLUSTERGETKEYSINSLOTslotcount 上面这个命令会返回指定的哈希槽中 count 个键。 对于每个返回的键,redis-trib向节点A发送一个 MIGRATE 命令,这样会以原子性的方式(在移动键的过程中两个节点都被锁住,以免出现竞争状况)把指定的键从节点A移到节点B。 以下是 MIGRATE的工作原理: MIGRATEtarget_hosttarget_portkeytarget_databaseidtimeout 执行 MIGRATE 命令的节点会连接到目标节点
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Redis 集群规范 集群 规范