当前位置:首页 > 芯闻号 > 充电吧
[导读]配置集群只需要将每个数据库节点的 cluster-enabled打开即可,每个集群至少需要3个主数据库才能正常运行。cluster-enabled yes集群会将当前节点记录的集群状态持久化地存储在指

配置集群


只需要将每个数据库节点的 cluster-enabled打开即可,每个集群至少需要3个主数据库才能正常运行。


cluster-enabled yes



集群会将当前节点记录的集群状态持久化地存储在指定文件,每个节点对应的文件必须不同。


cluster-config-file nodes-6381.conf


每个节点启动后都会输出类似下面的内容: 


No cluster configuration found, I'm 4b7cbeb343a2ba14d5b1b14457600624c076c157


4b7cbeb343a2…表示该节点的运行ID,运行ID是节点在集群中的唯一标识;同一个运行ID,可能地址和端口是不同的。


启动后,可连接任意一个节点使用 INFO 命令来判断集群是否正常启用了:


127.0.0.1:6381> info cluster
# Cluster
cluster_enabled:1

##1表示集群正常启用



 


现在每个节点都是完全独立的,下面将它们加入同一个集群里。


Redis源代码中提供了一个辅助工具redis-trib.rb可以非常方便地完成这一任务。因为redis-trib.rb是用Ruby语言编写的,所以运行前需要在服务器上安装Ruby程序。redis-trib.rb 依赖于 gem 包 redis,可以执行 gem install redis来安装。

使用redis-trib.rb来初始化集群,只需要执行:

$ /path/to/redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385

其中 create参数表示要初始化集群,--replicas 1表示每个主数据库拥有的从数据库个数为1 。


执行完后,redis-trib.rb会输出如下内容:

 >>> Creating cluster 

Connecting to node 127.0.0.1:6380: OK 

Connecting to node 127.0.0.1:6381: OK 

Connecting to node 127.0.0.1:6382: OK

 Connecting to node 127.0.0.1:6383: OK

 Connecting to node 127.0.0.1:6384: OK 

Connecting to node 127.0.0.1:6385: OK 

>>> Performing hash slots allocation on 6 nodes... 

Using 3 masters: 

127.0.0.1:6380 

127.0.0.1:6381 

127.0.0.1:6382 

Adding replica 127.0.0.1:6383 to 127.0.0.1:6380 

Adding replica 127.0.0.1:6384 to 127.0.0.1:6381

 Adding replica 127.0.0.1:6385 to 127.0.0.1:6382

 M: 

d4f906940d68714db787a60837f57fa496de5d12 127.0.0.1:6380 slots:0-5460 (5461 slots) master 

.....

内容包括集群具体的分配方案,如果觉得没问题则输入yes来开始创建。

首先redis-trib.rb会以客户端的形式尝试连接所有的节点,并发送PING命令以确定节点能够正常服务。如果有任何节点无法连接,则创建失败。同时发送 INFO 命令获取每个节点的运行ID以及是否开启了集群功能(即cluster_enabled为1)。


准备就绪后集群会向每个节点发送 CLUSTER MEET命令,格式为 CLUSTER MEET ip port,这个命令用来告诉当前节点指定ip和port上在运行的节点也是集群的一部分,从而使得6个节点最终可以归入一个集群。

然后redis-trib.rb会分配主从数据库节点,分配的原则是尽量保证每个主数据库运行在不同的IP地址上,同时每个从数据库和主数据库均不运行在同一IP地址上,以保证系统的容灾能力。分配结果如下:

Using 3 masters: 

127.0.0.1:6380

 127.0.0.1:6381

 127.0.0.1:6382

Adding replica 127.0.0.1:6383 to 127.0.0.1:6380 

Adding replica 127.0.0.1:6384 to 127.0.0.1:6381

 Adding replica 127.0.0.1:6385 to 127.0.0.1:6382

其中主数据库是 6380、6381 和 6382 端口上的节点,6383是6380的从数据库,6384是6381的从数据库,6385是6382的从数据库。


分配完成后,会为每个主数据库分配插槽(分配哪些键归哪些节点负责)。

对每个要成为子数据库的节点发送 CLUSTER REPLICATE主数据库的运行 ID来将当前节点转换成从数据库并复制指定运行 ID 的节点(主数据库)。 

此时整个集群的过程即创建完成,使用 Redis 命令行客户端连接任意一个节点执行CLUSTER NODES可以获得集群中的所有节点信息,如在6380执行:

redis 6380> CLUSTER NODES

551e5094789035affc489db267c8519c3a29f35d 127.0.0.1:6385 slave 887fe91bf218f203194403807e0aee941e985286 0 1424677377448 6 connected 

.......

从上面的输出中可以看到所有节点的运行ID、地址和端口、角色、状态以及负责的插槽等信息.


节点的增加 

向新节点A发送如下命令即可:

 CLUSTER MEET ip port 

ip和port是集群中任意一个节点的地址和端口号,

A接收到客户端发来的命令后,会与该地址和端口号的节点B进行握手,使B将A认作当前集群中的一员。当B与A握手成功后,B会使用Gossip协议将节点A的信息通知给集群中的每一个节点。通过这一方式,即使集群中有多个节点,也只需要选择 MEET 其中任意一个节点,即可使新节点最终加入整个集群中。


插槽的分配 

新的节点加入集群后有两种选择,要么使用 CLUSTER REPLICATE命令复制每个主数据库来以从数据库的形式运行,要么向集群申请分配插槽(slot)来以主数据库的形式运行。 在一个集群中,所有的键会被分配给16384个插槽,而每个主数据库会负责处理其中的一部分插槽。


redis-trib.rb初始化集群时分配给每个节点的插槽都是连续的,但是实际上Redis并没有此限制,可以将任意的几个插槽分配给任意的节点负责。


键与插槽的对应关系

Redis 将每个键的键名的有效部分使用CRC16算法计算出散列值,然后取对16384的余数。这样使得每个键都可以分配到16384个插槽中,进而分配的指定的一个节点中处理。这里键名的有效部分是指:

(1)如果键名包含{符号,且在{符号后面存在}符号,并且{和}之间有至少一个字符,则有效部分是指{和}之间的内容; 

(2)如果不满足上一条规则,那么整个键名为有效部分。

如果命令涉及多个键(如MGET),只有当所有键都位于同一个节点时 Redis 才能正常支持。利用键的分配规则,可以将所有相关的键的有效部分设置成同样的值使得相关键都能分配到同一个节点以支持多键操作。


将插槽分配给指定节点

插槽的分配分为如下几种情况。 

(1)插槽之前没有被分配过,现在想分配给指定节点。

(2)插槽之前被分配过,现在想移动到指定节点。

其中第一种情况使用 CLUSTER ADD SLOT S命令来实现,redis-trib.rb 也是通过该命令在创建集群时为新节点分配插槽的。CLUSTER ADDSLOTS命令的用法为:

CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]

如想将 100 和 101 两个插槽分配给某个节点,只需要在该节点执行:

CLUSTER ADDSLOTS 100 101即可。

如果指定插槽已经分配过了,则会提示:

(error) ERR Slot 100 is already busy

可以通过命令 CLUSTER SLOTS来查看插槽的分配情况,如:

 redis 6380> CLUSTER SLOTS 

1) 1) (integer) 5461 

2) (integer) 10922 

3) 1) "127.0.0.1" 

2) (integer) 6381

 4) 1) "127.0.0.1" 

2) (integer) 6384

2) 1) (integer) 0 

2) (integer) 5460 

3) 1) "127.0.0.1" 

2) (integer) 6380 

4) 1) "127.0.0.1" 

2) (integer) 6383

一共3条记录,每条记录的前两个值表示插槽的开始号码和结束号码,

后面的值则为负责该插槽的节点,包括主数据库和所有的从数据库,主数据库始终在第一位。


使用redis-trib.rb将一个插槽从6380迁移到6381

$ /path/to/redis-trib.rb reshard 127.0.0.1:6380

其中reshard表示告诉redis-trib.rb要重新分片,127.0.0.1:6380是集群中的任意一个节点的地址和端口,redis-trib.rb会自动获取集群信息。接下来,redis-trib.rb将会询问具体如何进行重新分片,首先会询问想要迁移多少个插槽:

How many slots do you want to move (from 1 to 16384)?

我们只需要迁移一个,所以输入1后回车。接下来redis-trib.rb会询问要把插槽迁移到哪个节点:

What is the receiving node ID?

可以通过 CLUSTER NODES命令获取6381的运行ID,这里是 b547d05c9d0e188993befec 4ae5ccb430343fb4b,输入并回车。接着最后一步是询问从哪个节点移出插槽:

Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1:all

我们输入6380对应的运行ID按回车然后输入done再按回车确认即可。

接下来输入 yes来确认重新分片方案,重新分片即告成功。


如何不借助redis-trib.rb手工进行重新分片呢?

使用如下命令即可:

 CLUSTER SETSLOT 插槽号 NODE 新节点的运行 ID

然而这样迁移插槽的前提是插槽中并没有任何键,因为使用 CLUSTER SETSLOT命令迁移插槽时并不会连同相应的键一起迁移,这就造成了客户端在指定节点无法找到未迁移的键,造成这些键对客户端来说“丢失了”

手工获取某个插槽存在哪些键的方法是:

 CLUSTER GETKEYSINSLOT 插槽号要返回的键的数量

之后对每个键,使用MIGRATE命令将其迁移到目标节点: 

MIGRATE 目标节点地址目标节点端口键名数据库号码超时时间 [COPY] [REPLACE] 

其中COPY选项表示不将键从当前数据库中删除,而是复制一份副本。

REPLACE表示如果目标节点存在同名键,则覆盖。

因为集群模式只能使用0号数据库,所以数据库号码始终为0。

如要把键abc从当前节点(如6381)迁移到6380:

redis 6381> MIGRATE 127.0.0.1 6380 abc 0 15999 REPLACE


Redis提供了如下两个命令用来实现在集群不下线的情况下迁移数据: 

CLUSTER SETSLOT 插槽号 MIGRATING 新节点的运行 ID

CLUSTER SETSLOT 插槽号 IMPORTING 原节点的运行 ID


进行迁移时,假设要把0号插槽从A迁移到B,此时redis-trib.rb会依次执行如下操作

(1)在B执行 CLUSTER SETSLOT 0 IMPORTING A。

 (2)在A 执行 CLUSTER SETSLOT 0 MIGRATING B。 

(3)执行 CLUSTER GETKEYSINSLOT 0获取0号插槽的键列表。 

(4)对第3步获取的每个键执行MIGRATE命令,将其从A迁移到B。 (5)执行 CLUSTER SETSLOT 0 NODE B来完成迁移。

从上面的步骤来看 redis-trib.rb多了 1和 2两个步骤,这两个步骤就是为了解决迁移过程中键的临时“丢失”问题。首先执行完前两步后,当客户端向 A 请求插槽 0 中的键时,如果键存在(即尚未被迁移),则正常处理,如果不存在,则返回一个 ASK跳转请求,告诉客户端这个键在 B里,如图 8-6所示。客户端接收到 ASK跳转请求后,首先向 B发送 ASKING命令,然后再重新发送之前的命令。相反,当客户端向 B请求插槽 0 中的键时,如果前面执行了 ASKING 命令,则返回键值内容,否则返回 MOVED跳转请求(会在8.3.4节介绍),如图8-7所示。这样一来客户端只有能够处理ASK跳转,则可以在数据库迁移时自动从正确的节点获取到相应的键值,避免了键在迁移过程中临时“丢失”的问题。



获取与插槽对应的节点

当客户端向集群中的任意一个节点发送命令后,该节点会判断相应的键是否在当前节点中,如果键在该节点中,则会像单机实例一样正常处理该命令;如果键不在该节点中,就会返回一个 MOVE 重定向请求,告诉客户端这个键目前由哪个节点负责,然后客户端再将同样的请求向目标节点重新发送一次以获得结果。


键foo实际应该由6382节点负责,如果尝试在6380节点执行与键foo相关的命令,就会有如下输出:

redis 6380> SET foo bar 

(error) MOVED 12182 127.0.0.1:6382

返回的是一个MOVE重定向请求,12182表示foo所属的插槽号,127.0.0.1:6382则是负责该插槽的节点地址和端口,客户端收到重定向请求后,应该将命令重新向 6382节点发送一次:

redis 6382> SET foo bar OK

Redis命令行客户端提供了集群模式来支持自动重定向,使用-c参数来启用:

$ redis-cli -c -p 6380 reds 6380> SET foo bar -> Redirected to slot [12182] located at 127.0.0.1:6382 OK

可见加入了-c参数后,如果当前节点并不负责要处理的键,Redis命令行客户端会进行自动命令重定向。而这一过程正是每个支持集群的客户端应该实现的。


然而相比单机实例,集群的命令重定向也增加了命令的请求次数,原先只需要执行一次的命令现在有可能需要依次发向两个节点,算上往返时延,可以说请求重定向对性能的还是有些影响的。 为了解决这一问题,当发现新的重定向请求时,客户端应该在重新向正确节点发送命令的同时,缓存插槽的路由信息,即记录下当前插槽是由哪个节点负责的。这样每次发起命令时,客户端首先计算相关键是属于哪个插槽的,然后根据缓存的路由判断插槽由哪个节点负责。考虑到插槽总数相对较少(16384个),缓存所有插槽的路由信息后,每次命令将均只发向正确的节点,从而达到和单机实例同样的性能。


故障恢复

在一个集群中,每个节点都会定期向其他节点发送 PING 命令,并通过有没有收到回复来判断目标节点是否已经下线了。具体来说,集群中的每个节点每隔1秒钟就会随机选择5个节点,然后选择其中最久没有响应的节点发送PING命令。


 如果一定时间内目标节点没有响应回复,则发起 PING 命令的节点会认为目标节点疑似下线(PFAIL)。疑似下线可以与哨兵的主观下线类比,两者都表示某一节点从自身的角度认为目标节点是下线的状态。与哨兵的模式类似,如果要使在整个集群中的所有节点都认为某一节点已经下线,需要一定数量的节点都认为该节点疑似下线才可以,这一过程具体为: 

(1)一旦节点A认为节点B是疑似下线状态,就会在集群中传播该消息,所有其他节点收到消息后都会记录下这一信息; 

(2)当集群中的某一节点C收集到半数以上的节点认为B是疑似下线的状态时,就会将B标记为下线(FAIL),并且向集群中的其他节点传播该消息,从而使得B在整个集群中下线。 


<p style="clear:both;font-family:'Helvetica Neue', Hel

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

前言说到redis,可能大家的脑海中蹦出的关键词是:NoSQL、KV、高性能、缓存等。但今天的文章从另一个角度——微服务来展开。这篇文章的起因也是源自一次面试经历,在面试一位来自陌陌的候选人(就是那个交友的陌陌)时,他提...

关键字: redis

面试官:你们系统是怎么实现分布式锁的?我:我们使用了redis的分布式锁。具体做法是后端接收到请求后加入一个分布式锁,如果加锁成功,就执行业务,如果加锁失败就等待锁或者拒绝请求。业务执行完成后释放锁。面试官:能说一下具体...

关键字: 分布式 redis

进程请求分布式锁时一般包含三个阶段:1.进程请求获取锁;2.获取到锁的进程持有锁并执行业务逻辑;3.获取到锁的进程释放锁;下文会按照这个三个阶段进行分析。单机Redis获取锁从一开始的请求进程通过SETNX命令获取锁;1...

关键字: redis zookeeper 分布式锁

经过前面两篇文章《JSON Web Token - 在Web应用间安全地传递信息》《八幅漫画理解使用JSON Web Token设计单点登录系统》的科普,相信大家应该已经知道了 JWT 协议是什么了。

关键字: JWT redis session

我是Redis,一个叫Antirez的男人把我带到了这个世界上。 那天,Redis基友群里,许久未见的大白发来了一条消息……于是,大白拉了一个新的群 以后的日子中,咱们哥仨相互配合,日常工作中最多的就是数据同步了……

关键字: redis 嵌入式

作为一名服务端工程师,工作中你肯定和Redis打过交道。Redis为什么快,这点想必你也知道,至少为了面试也做过准备。很多人知道Redis快仅仅因为它是基于内存实现的,对于其它原因倒是模棱两可。那么,今天就和小莱一起看看...

关键字: redis 嵌入式

作为一名后端工程师,工作中你肯定和 Redis 打过交道。Redis 为什么快,这点想必你也知道,至少为了面试也做过准备。很多人知道 Redis 快仅仅因为它是基于内存实现的,对于其它原因倒是模棱两可。

关键字: redis 嵌入式

内存数据库Redis的相关知识,几乎是大厂的必考题,本文总结了设计Redis的11道面试题。

关键字: redis 嵌入式

最近,又重新学习了下Redis,深深被Redis的魅力所折服,Redis不仅能快还能慢,简直利器呀!

关键字: redis 嵌入式

本文介绍了Redis高可用相关的机制。

关键字: redis 架构
关闭
关闭