集群要实现的目的是要将不同的 key 分散放置到不同的 redis 节点,这里我们需要一个规则或者算法,通常的做法是获取 key 的哈希值,然后根据节点数来求模,但这种做法有其明显的弊端,当我们需要增加或减少一个节点时,会造成大量的 key 无法命中,这种比例是相当高的,所以就有人提出了一致性哈希的概念。
一致性哈希有四个重要特征:
均衡性:也有人把它定义为平衡性,是指哈希的结果能够尽可能分布到所有的节点中去,这样可以有效的利用每个节点上的资源。
单调性:对于单调性有很多翻译让我非常的不解,而我想要的是当节点数量变化时哈希的结果应尽可能的保护已分配的内容不会被重新分派到新的节点。
分散性和负载:这两个其实是差不多的意思,就是要求一致性哈希算法对 key 哈希应尽可能的避免重复。
但一致性哈希不是我们今天要介绍的重点,因为 Redis 引入另一种哈希槽(hash slot)的概念。
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
使用哈希槽的好处就在于可以方便的添加或移除节点。
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
内部机制,与我何干,对于我们来说,在新增或移除节点的时候不要让我们先停掉所有的 redis 服务我就谢天谢地了,这点它做到了。
下面我们就开始动手搭建一个 redis 集群来体验一下。
因为我们要启动多个 redis 实例,虽然我们可以直接通过命令行来启动,但始终是不怎么方便的,所以我们先来新建三个实例目录,分别是9001,9002,9003,目录名就是 redis 实例的端口号。
我这里已经建好了目录,然后我们把以前编译过和修改过的 redis-server、redis.conf这两个文件分别拷贝到这三个目录里面,拷贝完之后就像这样子了:
我们打开 redis.conf 文件,为了简单起见,我们只保留下面几个配置项:
daemonize yes
port 9001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
注意:port 要修改成对应目录的名字,也就是每个实例要有不同的端口。
下面我们分别启动这三个实例:
zhaoguihuadediannao:~ zhaogh$ cd applications/dev/redis-cluster
zhaoguihuadediannao:redis-cluster zhaogh$ cd 9001
zhaoguihuadediannao:9001 zhaogh$ ./redis-server ./redis.conf
zhaoguihuadediannao:9003 zhaogh$ cd ../9002
zhaoguihuadediannao:9002 zhaogh$ ./redis-server ./redis.conf
zhaoguihuadediannao:9002 zhaogh$ cd ../9003
zhaoguihuadediannao:9003 zhaogh$ ./redis-server ./redis.conf
zhaoguihuadediannao:9003 zhaogh$
接下来我们来创建集群,让三个实例互相通讯:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb create --replicas 0 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003
>>> Creating cluster
Connecting to node 127.0.0.1:9001: OK
Connecting to node 127.0.0.1:9002: OK
Connecting to node 127.0.0.1:9003: OK
>>> Performing hash slots allocation on 3 nodes...
Using 3 masters:
127.0.0.1:9001
127.0.0.1:9002
127.0.0.1:9003
M: 92c9912cb1ccf657c886ecd839dd32c66efd8762 127.0.0.1:9001
slots:0-5460 (5461 slots) master
M: b6d46fcb8b0e6ee373b09a4f2cbcec744d1a259b 127.0.0.1:9002
slots:5461-10922 (5462 slots) master
M: 44ab30c7c589ffb15b9b04dd827c72cfaeedacb2 127.0.0.1:9003
slots:10923-16383 (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join..
>>> Performing Cluster Check (using node 127.0.0.1:9001)
M: 92c9912cb1ccf657c886ecd839dd32c66efd8762 127.0.0.1:9001
slots:0-5460 (5461 slots) master
M: b6d46fcb8b0e6ee373b09a4f2cbcec744d1a259b 127.0.0.1:9002
slots:5461-10922 (5462 slots) master
M: 44ab30c7c589ffb15b9b04dd827c72cfaeedacb2 127.0.0.1:9003
slots:10923-16383 (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
zhaoguihuadediannao:src zhaogh$
需要注意的是执行 redis-trib.rb 命令需要 ruby 的支持,如果你没有安装可以先到 https://rubygems.org/gems/redis 下载,然后离线安装。
sudo gem install redis-3.0.7.gem --local
下面我们用 redis 自带的客户端测试一下:
zhaoguihuadediannao:src zhaogh$ ./redis-cli -c -p 9001
127.0.0.1:9001> get testkey001
-> Redirected to slot [12786] located at 127.0.0.1:9003
(nil)
127.0.0.1:9003> set testkey002 testvalue002
-> Redirected to slot [401] located at 127.0.0.1:9001
OK
127.0.0.1:9001> get testkey002
"testvalue002"
127.0.0.1:9001> set testkey003 testvalue003
OK
127.0.0.1:9001>
可以看到,虽然我们第一次连接的是9001端口,当我们去获取 testkey001 的时候,redis cluster 自动帮我们重定向到 9003 。
当我们在 9003 设置 testkey002 时,redis cluster 又重定向到 9001 。
如何增加节点:
首先我们创建一个叫做 9004 的目录:
zhaoguihuadediannao:redis-cluster zhaogh$ mkdir 9004
zhaoguihuadediannao:redis-cluster zhaogh$
接着我们将 9001 目录下的 redis-server 、 redis.conf 两个文件拷贝到 9004 目录:
zhaoguihuadediannao:redis-cluster zhaogh$ cp 9001/redis-server 9004
zhaoguihuadediannao:redis-cluster zhaogh$ cp 9001/redis.conf 9004
然后我们打开 redis.conf 文件修改里面的端口配置项,将其改为 9004 。
启动 9004 实例:
zhaoguihuadediannao:redis-cluster zhaogh$ cd 9004
zhaoguihuadediannao:9004 zhaogh$ ./redis-server ./redis.conf
zhaoguihuadediannao:9004 zhaogh$
想要把这个实例加入到集群,我们只需要执行 redis-trib.rb 命令:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb add-node 127.0.0.1:9004 127.0.0.1:9001
第一个参数是我们刚才启动的新实例,第二个参数是集群中已有的节点。
检查一下新节点是否已经加入:
zhaoguihuadediannao:src zhaogh$ ./redis-cli -c -p 9001
127.0.0.1:9001> cluster nodes
0e8f980bfe7a682e3d71b15523a41293535b8ccd :0 myself,master - 0 0 1 connected 0-5460
cbb01bdfdc265b190496956354d84aaae6e7d54d 127.0.0.1:9004 master - 0 1401952316346 0 connected
708e6e14474e3a99677b05ff89bd857375884437 127.0.0.1:9002 master - 0 1401952314325 2 connected 5461-10922
a7f9d3c64540cc3fc8cd3072e573bb8ab0bf1e6f 127.0.0.1:9003 master - 0 1401952315334 3 connected 10923-16383
127.0.0.1:9001>
我们可以发现 9004 并不包含任何哈希槽,因为它还没有数据。
我们还可以为集群中的主节点增加从节点用于只读查询。
如何增加从节点:
我们还是要创建目录,拷贝那两个文件,修改配置,然后启动实例:
zhaoguihuadediannao:redis-cluster zhaogh$ mkdir 9005
zhaoguihuadediannao:redis-cluster zhaogh$ cp 9001/redis-server 9005
zhaoguihuadediannao:redis-cluster zhaogh$ cp 9001/redis.conf 9005
修改 port 为 9005
zhaoguihuadediannao:redis-cluster zhaogh$ cd 9005
zhaoguihuadediannao:9005 zhaogh$ ./redis-server ./redis.conf
zhaoguihuadediannao:9005 zhaogh$
执行下面的命令,增加从节点:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb add-node --slave 127.0.0.1:9005 127.0.0.1:9001
第一个参数为从节点,第二个参数为主节点。
如何删除一个节点:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb del-node 127.0.0.1:9001 'cbb01bdfdc265b190496956354d84aaae6e7d54d'
这里要注意一下,第一个参数是集群中的任何一个主节点地址,而第二个参数是要删除节点的 ID,这个ID如果你不知道的话,可以通过 cluster nodes 命令查看。
还有一点就是要删除的节点必须是空的,也就是不能缓存任何数据,否则会删除不成功。对于非空节点,在删除之前需要重新分片,将缓存的数据转移到别的节点。
如何重新分片:
我们先给某个节点做点数据:
zhaoguihuadediannao:src zhaogh$ ./redis-cli -c -p 9001
127.0.0.1:9001> set testkey001 testvalue001
-> Redirected to slot [12786] located at 127.0.0.1:9003
OK
127.0.0.1:9003>
现在 9003 上已经有数据了,我们尝试删除一下:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb del-node 127.0.0.1:9001 '78ec1fd6647b79627d7c29bb2b22d04a4a6c43b3'
>>> Removing node 78ec1fd6647b79627d7c29bb2b22d04a4a6c43b3 from cluster 127.0.0.1:9001
Connecting to node 127.0.0.1:9001: OK
Connecting to node 127.0.0.1:9002: OK
Connecting to node 127.0.0.1:9003: OK
[ERR] Node 127.0.0.1:9003 is not empty! Reshard data away and try again.
zhaoguihuadediannao:src zhaogh$
没有删除成功,我们来重新分片,把 9003 上的数据转移:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb reshard 127.0.0.1:9003
然后输出了很多信息,很多数值和ID都可以从这段信息中找到。
How many slots do you want to move (from 1 to 16384)? 5461
会问你要移动多少个哈希槽,我们把 9003 上的所有哈希槽都移走,5461 这个数字可以从终端上看到,或许你的实际情况不是这个数字。
What is the receiving node ID? 4d2e0a8360795ce7ce8381c68746034aeba3c9b9
然后问你你要把这些哈希槽移到哪儿去,我指定了 9001 的 节点 ID。
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:78ec1fd6647b79627d7c29bb2b22d04a4a6c43b3
Source node #2:done
之后,redis 列出了重新分片计划,最后问你
Do you want to proceed with the proposed reshard plan (yes/no)? yes
执行完成后,我们看看 9003 上还有没有 key:
zhaoguihuadediannao:src zhaogh$ ./redis-cli -p 9003
127.0.0.1:9003> keys *
(empty list or set)
127.0.0.1:9003>
再看看 9001 上是不是有这个 key 了
zhaoguihuadediannao:src zhaogh$ ./redis-cli -p 9001
127.0.0.1:9001> keys *
1) "testkey001"
127.0.0.1:9001>
没错,果然转移过来了。
最后我们试试能不能把 9003 删除:
zhaoguihuadediannao:src zhaogh$ ./redis-trib.rb del-node 127.0.0.1:9001 '78ec1fd6647b79627d7c29bb2b22d04a4a6c43b3'
>>> Removing node 78ec1fd6647b79627d7c29bb2b22d04a4a6c43b3 from cluster 127.0.0.1:9001
Connecting to node 127.0.0.1:9001: OK
Connecting to node 127.0.0.1:9002: OK
Connecting to node 127.0.0.1:9003: OK
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
zhaoguihuadediannao:src zhaogh$ ./redis-cli -p 9003
Could not connect to Redis at 127.0.0.1:9003: Connection refused
not connected>