系数剖析Redis Cluster原理和使用

健全剖析Redis Cluster原理和利用


1.Redis Cluster总览

1.1 设计规范和初衷

法定文档Cluster
Spec
中,作者详细介绍了Redis集群为何要统筹成现在的规范。最基本的靶子有四个:

  1. 性能:这是Redis赖以生存的看家本领,增添集群效应后自然无法对性能发生太大影响,所以Redis采纳了P2P而非Proxy格局、异步复制、客户端重定向等规划,而献身了有些的一致性、使用性。
  2. 水平增加:集群的最关键力量自然是扩充,文档中称能够线性增添到1000结点。
  3. 可用性:在Cluster推出此前,可用性要靠Sentinel保证。有了集群之后也自行具有了Sentinel的监控和自动Failover能力。

1.2 架构变化与CAP理论

Redis
Cluster集群效用推出已经有一段时间了。在单机版的Redis中,每个Master之间是不曾此外通信的,所以咱们一般在Jedis客户端如故Codis这样的代办中做Pre-sharding。遵照CAP理论来说,单机版的Redis属于担保CP(Consistency
&
Partition-Tolerancy)而牺牲A(Availability)
,也就说Redis可以确保所有用户看到同一的数额(一致性,因为Redis不自动冗余数据)和网络通信出问题时,暂时隔离开的子系统能连续运行(分区容忍性,因为Master之间尚未一向关联,不需要通信),不过不保险某些结点故障时,所有请求都能被响应(可用性,某个Master结点挂了的话,那么它上边分片的数码就不能访问了)。

有了Cluster功能后,Redis从一个单单的NoSQL内存数据库成为了分布式NoSQL数据库,CAP模型也从CP变成了AP。也就是说,通过活动分片和冗余数据,Redis具有了真正的分布式能力,某个结点挂了的话,因为数量在此外结点上有备份,所以任何结点顶上来就足以继承提供服务,保证了Availability。可是,也正因为这点,Redis不可能保证曾经的强一致性了。那也是CAP理论要求的,三者只可以取其二。

有关CAP理论的易懂讲解,请参考我的译文《可能是CAP理论的最好解释
。简单解析了Redis在架构上的变化后,大家就一路来体会一下Redis
Cluster效率吧!


2.Redis集群初探

Redis的设置很粗略,从前曾经介绍过,就不详细说了。关于Redis
Cluster的基础知识往日也有过收拾,请参考《Redis集群效用预览》。假若需要宏观的刺探,这必然要看合法文档Cluster
Tutorial
,只看这个就够了!

2.1 集群配置

要想打开Redis
Cluster形式,有几项配置是必须的。其余为了方便使用和继续的测试,我还额外做了一些布置:

  • 绑定地址:bind
    192.168.XXX.XXX。不可能绑定到127.0.0.1或localhost,否则指点客户端重定向时会报”Connection
    refused”的失实。
  • 开启Cluster:cluster-enabled yes
  • 集群配置文件:cluster-config-file
    nodes-7000.conf。这些布局文件不是要我们去配的,而是Redis运行时保留配置的文书,所以我们也不可以修改这些文件。
  • 集群超时时间:cluster-node-timeout
    15000。结点超时多久则认为它宕机了。
  • 槽是否全覆盖:cluster-require-full-coverage
    no。默认是yes,假定有结点宕机导致16384个槽没全被掩盖,整个集群就整个停下服务,所以一定要改为no
  • 后台运行:daemonize yes
  • 输出日志:logfile “./redis.log”
  • 监听端口:port 7000

安排好后,按照我们的集群规模,拷贝出来几份同样的布局文件,唯一不同的就是监听端口,可以依次改为7001、7002…
因为Redis
Cluster假使数据冗余是1的话,至少要3个Master和3个Slave,所以大家拷贝出6个实例的安排文件。为了避免相互影响,为6个实例的部署文件建立独立的公文夹。

[root@8gVm redis-3.0.4]# pwd
/root/Software/redis-3.0.4
[root@8gVm redis-3.0.4]# tree -I "*log|nodes*" cfg-cluster/
cfg-cluster/
├── 7000
│   └── redis.conf.7000
├── 7001
│   └── redis.conf.7001
├── 7002
│   └── redis.conf.7002
├── 7003
│   └── redis.conf.7003
├── 7004
│   └── redis.conf.7004
└── 7005
    └── redis.conf.7005

6 directories, 6 files
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.2 redis-trib管理器

Redis作者应该是个Ruby爱好者,Ruby客户端就是她支付的。这一次集群的管住功能尚未放手到Redis代码中,于是作者又随手写了个名叫redis-trib的田间管理脚本。redis-trib依赖Ruby和RubyGems,以及redis扩张。可以先用which命令查看是否已安装ruby和rubygems,用gem
list –local查看本地是否已设置redis扩充。

最简便的点子就是用apt或yum包管理器安装RubyGems后执行gem install
redis。倘诺网络或环境受限的话,可以手动安装RubyGems和redis扩大(外国链接或者无法下载,可以从CSDN下载):

[root@8gVm Software]# wget https://github.com/rubygems/rubygems/releases/download/v2.2.3/rubygems-2.2.3.tgz
[root@8gVm Software]# tar xzvf rubygems-2.2.3.tgz 
[root@8gVm Software]# cd rubygems-2.2.3
[root@8gVm rubygems-2.2.3]# ruby setup.rb --no-rdoc --no-ri

[root@8gVm Software]# wget https://rubygems.org/downloads/redis-3.2.1.gem
[root@8gVm Software]# gem install redis-3.2.1.gem --local --no-rdoc --no-ri
Successfully installed redis-3.2.1
1 gem installed
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.3 集群建立

先是,启动大家安排好的6个Redis实例。

[root@8gVm redis-3.0.4]# for ((i=0; i<6; ++i))
> do
> cd cfg-cluster/700$i && ../../src/redis-server redis.conf.700$i && cd -
> done
  • 1
  • 2
  • 3
  • 4

  • 1
  • 2
  • 3
  • 4

这时6个实例还不曾变异集群,现在用redis-trb.rb管理脚本建立起集群。可以观望,redis-trib默认用前3个实例作为Master,后3个作为Slave。因为Redis基于Master-Slave做数据备份,而非像卡桑德拉(Sandra)(Cassandra)或Hazelcast一样不区分结点角色,自动复制并分配Slot的地点到各种结点

[root@8gVm redis-3.0.4]# src/redis-trib.rb create --replicas 1 192.168.1.100:7000 192.168.1.100:7001 192.168.1.100:7002 192.168.1.100:7003 192.168.1.100:7004 192.168.1.100:7005
>>> Creating cluster
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
Connecting to node 192.168.1.100:7005: OK
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.1.100:7000
192.168.1.100:7001
192.168.1.100:7002
Adding replica 192.168.1.100:7003 to 192.168.1.100:7000
Adding replica 192.168.1.100:7004 to 192.168.1.100:7001
Adding replica 192.168.1.100:7005 to 192.168.1.100:7002
    ...
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 192.168.1.100:7000)
    ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

至此,集群就早已确立成功了!“贴心”的Redis还在utils/create-cluster下提供了一个create-cluster脚本,可以创造出一个集群,类似我们地点建立起的3主3从的集群。

2.4 简单测试

俺们连接受集群中的任意一个结点,启动redis-cli时要加-c选项,存取两个Key-Value感受一下Redis久违的集群效益。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000
192.168.1.100:7000> set foo bar
-> Redirected to slot [12182] located at 192.168.1.100:7002
OK
192.168.1.100:7002> set hello world
-> Redirected to slot [866] located at 192.168.1.100:7000
OK
192.168.1.100:7000> get foo
-> Redirected to slot [12182] located at 192.168.1.100:7002
"bar"
192.168.1.100:7002> get hello
-> Redirected to slot [866] located at 192.168.1.100:7000
"world"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

细心察看可知专注到,redis-cli遵照指令,不断在7000和7002结点此前重定向跳转。假设启动时不加-c选项的话,就能观望以错误形式显示出的MOVED重定向信息。

[root@8gVm redis-3.0.4]# src/redis-cli -h 192.168.1.100 -p 7000
192.168.1.100:7000> get foo
(error) MOVED 12182 192.168.1.100:7002
  • 1
  • 2
  • 3

  • 1
  • 2
  • 3

2.5 集群重启

眼下redis-trib的效劳还比较弱,需要重启集群的话先手动kill掉各类进程,然后重新启航就可以了。这也有点太…
网上有人重启后会遇到问题,我还相比幸运,这种“土鳖”的办法重启试了五回还没发现问题。

[root@8gVm redis-3.0.4]# ps -ef | grep redis | awk '{print $2}' | xargs kill
  • 1

  • 1

3.尖端功效尝鲜

身为“高级效率”,其实在另外分布式系统中早就都有落实了,只可是在Redis世界里是相比较异常的。本有的紧要考查眨眼之间间Redis
Cluster中的数据迁移(Resharding)和故障转移职能。

3.1 数据迁移

本小节大家体会一下Redis集群的Resharding成效!

3.1.1 创设测试数据

率先保存foo1~10共10个Key-Value作为测试数据。

[root@8gVm redis-3.0.4]# for ((i=0; i<10; ++i))
> do
> src/redis-cli -c -h 192.168.1.100 -p 7000 set foo$i bar
> done

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000
192.168.1.100:7000> keys *
1) "foo6"
2) "foo7"
3) "foo3"
4) "foo2"
192.168.1.100:7000> get foo4
-> Redirected to slot [9426] located at 192.168.1.100:7001
"bar"
192.168.1.100:7001> keys *
1) "foo4"
2) "foo8"
192.168.1.100:7001> get foo5
-> Redirected to slot [13555] located at 192.168.1.100:7002
"bar"
192.168.1.100:7002> keys *
1) "foo5"
2) "foo1"
3) "foo10"
4) "foo9"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3.1.2 启动新结点

参考此前的不二法门新拷贝出两份redis.conf配置文件redis.conf.7010和7011,与前面结点的安排文件做一下区分。启动新的两个Redis实例之后,通过redis-trib.rb脚本添加新的Master和Slave到集群中。

[root@8gVm redis-3.0.4]# cd cfg-cluster/7010 && ../../src/redis-server redis.conf.7010 && cd -
[root@8gVm redis-3.0.4]# cd cfg-cluster/7011 && ../../src/redis-server redis.conf.7011 && cd -
  • 1
  • 2

  • 1
  • 2

3.1.3 添加到集群

使用redis-trib.rb add-node独家将两个新结点添加到集群中,一个当做Master,一个当做其Slave。

[root@8gVm redis-3.0.4]# src/redis-trib.rb add-node 192.168.1.100:7010 192.168.1.100:7000
>>> Adding node 192.168.1.100:7010 to cluster 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7005: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
>>> Performing Cluster Check (using node 192.168.1.100:7000)
    ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Connecting to node 192.168.1.100:7010: OK
>>> Send CLUSTER MEET to node 192.168.1.100:7010 to make it join the cluster.
[OK] New node added correctly.

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master - 0 1442452249525 0 connected
    ...

[root@8gVm redis-3.0.4]# src/redis-trib.rb add-node --slave --master-id 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7011 192.168.1.100:7000
>>> Adding node 192.168.1.100:7011 to cluster 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7010: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7005: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
>>> Performing Cluster Check (using node 192.168.1.100:7000)
    ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Connecting to node 192.168.1.100:7011: OK
>>> Send CLUSTER MEET to node 192.168.1.100:7011 to make it join the cluster.
Waiting for the cluster to join.
>>> Configure node as replica of 192.168.1.100:7010.
[OK] New node added correctly.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

3.1.4 Resharding

通过redis-trib.rb reshard可以交互式地迁移Slot。下面的事例将5000个Slot从7000~7002迁移到7010上。也足以因此./redis-trib.rb reshard <host>:<port> --from <node-id> --to <node-id> --slots --yes在程序中自动完成搬迁。

[root@8gVm redis-3.0.4]# src/redis-trib.rb reshard 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7010: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7005: OK
Connecting to node 192.168.1.100:7011: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
>>> Performing Cluster Check (using node 192.168.1.100:7000)
M: b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000
   slots:0-5460 (4128 slots) master
   1 additional replica(s)
M: 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010
   slots:0 (4000 slots) master
   1 additional replica(s)
   ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 5000
What is the receiving node ID? 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9
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

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master - 0 1442455872019 7 connected 0-1332 5461-6794 10923-12255
b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 myself,master - 0 0 1 connected 1333-5460
b5ab302f5c2395e3c8194c354a85d02f89bace62 192.168.1.100:7001 master - 0 1442455875022 2 connected 6795-10922
0c565e207ce3118470fd5ed3c806eb78f1fdfc01 192.168.1.100:7002 master - 0 1442455874521 3 connected 12256-16383
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

搬迁完成后,查看以前封存的foo1~10的遍布情状,可以看看部分Key已经搬迁到了新的结点7010上。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 keys "*"
1) "foo3"
2) "foo7"
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7001 keys "*"
1) "foo4"
2) "foo8"
3) "foo0"
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7002 keys "*"
1) "foo1"
2) "foo9"
3) "foo5"
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7010 keys "*"
1) "foo6"
2) "foo2"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.2 故障转移

在高可用性方面,Redis可到底可以”Auto”一把了!Redis
Cluster重用了Sentinel的代码逻辑,不需要独自启动一个Sentinel集群,Redis
Cluster本身就能半自动举行Master选举和Failover切换

下边我们有意kill掉7010结点,之后能够看到结点状态变为了fail,而Slave
7011被推举为新的Master。

[root@8gVm redis-3.0.4]# kill 43637

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master,fail - 1442456829380 1442456825674 7 disconnected
b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 myself,master - 0 0 1 connected 1333-5460
b5ab302f5c2395e3c8194c354a85d02f89bace62 192.168.1.100:7001 master - 0 1442456848722 2 connected 6795-10922
0c565e207ce3118470fd5ed3c806eb78f1fdfc01 192.168.1.100:7002 master - 0 1442456846717 3 connected 12256-16383
5a3c67248b1df554fbf2c93112ba429f31b1d3d1 192.168.1.100:7005 slave 0c565e207ce3118470fd5ed3c806eb78f1fdfc01 0 1442456847720 6 connected
99bff22b97119cf158d225c2b450732a1c0d3c44 192.168.1.100:7011 master - 0 1442456849725 8 connected 0-1332 5461-6794 10923-12255
cd305d509c34842a8047e19239b64df94c13cb96 192.168.1.100:7003 slave b2036adda128b2eeffa36c3a2056444d23b548a8 0 1442456848220 4 connected
64b544cdd75c1ce395fb9d0af024b7f2b77213a3 192.168.1.100:7004 slave b5ab302f5c2395e3c8194c354a85d02f89bace62 0 1442456845715 5 connected
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

品尝查询在此之前封存在7010上的Key,可以见到7011顶替上来继续提供劳务,整个集群没有遭到震慑。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 get foo6
"bar"
[root@8gVm redis-3.0.4]# 
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 get foo2
"bar"
  • 1
  • 2
  • 3
  • 4
  • 5

  • 1
  • 2
  • 3
  • 4
  • 5

4.中间原理分析

前边我们已经学习过,用Redis提供的redis-trib或create-cluster脚本能几步仍然一步就建立起一个Redis集群。这一部分我们为了深远学习,所以要暂时丢掉这些方便的工具,完全手动建立一回上边的3主3从集群。

4.1 集群发现:MEET

最初阶时,每个Redis实例自己是一个集群,我们透过cluster meet让各种结点相互“握手”。这也是Redis
Cluster最近的一个欠缺之处:贫乏结点的电动发现效果

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c :7000 myself,master - 0 0 0 connected

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster meet 192.168.1.100 7001
OK
    ...
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster meet 192.168.1.100 7005
OK

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
7b953ec26bbdbf67179e5d37e3cf91626774e96f 192.168.1.100:7003 master - 0 1442466369259 4 connected
5d9f14cec1f731b6477c1e1055cecd6eff3812d4 192.168.1.100:7005 master - 0 1442466368659 4 connected
33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 192.168.1.100:7000 myself,master - 0 0 1 connected
63162ed000db9d5309e622ec319a1dcb29a3304e 192.168.1.100:7001 master - 0 1442466371262 3 connected
45baa2cb45435398ba5d559cdb574cfae4083893 192.168.1.100:7002 master - 0 1442466372264 2 connected
cdd5b3a244761023f653e08cb14721f70c399b82 192.168.1.100:7004 master - 0 1442466370261 0 connecte
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.2 角色设置:REPLICATE

结点全部“握手”成功后,就可以用cluster replicate一声令下为结点指定角色了,默认每个结点都是Master。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7003 cluster replicate 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c
OK
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7004 cluster replicate 63162ed000db9d5309e622ec319a1dcb29a3304e
OK
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7005 cluster replicate 45baa2cb45435398ba5d559cdb574cfae4083893
OK

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
7b953ec26bbdbf67179e5d37e3cf91626774e96f 192.168.1.100:7003 slave 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 0 1442466812984 4 connected
5d9f14cec1f731b6477c1e1055cecd6eff3812d4 192.168.1.100:7005 slave 45baa2cb45435398ba5d559cdb574cfae4083893 0 1442466813986 5 connected
33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 192.168.1.100:7000 myself,master - 0 0 1 connected
63162ed000db9d5309e622ec319a1dcb29a3304e 192.168.1.100:7001 master - 0 1442466814987 3 connected
45baa2cb45435398ba5d559cdb574cfae4083893 192.168.1.100:7002 master - 0 1442466811982 2 connected
cdd5b3a244761023f653e08cb14721f70c399b82 192.168.1.100:7004 slave 63162ed000db9d5309e622ec319a1dcb29a3304e 0 1442466812483 3 connected
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

4.3 槽指派:ADDSLOTS

安装好主从涉嫌之后,就足以用cluster addslots指令指派16384个槽的地方了。有点恶心的是,ADDSLOTS命令需要在参数中一个个指明槽的ID,而无法指定范围。这里用Bash
3.0的特性简化了,不然就得用Bash的巡回来形成了:

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster addslots {0..5000}
OK
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7001 cluster addslots {5001..10000}
OK
[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7001 cluster addslots {10001..16383}
OK

[root@8gVm redis-3.0.4]# src/redis-trib.rb check 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
  ...
>>> Performing Cluster Check (using node 192.168.1.100:7000)
  ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这么大家就经过手动执行命令获得了与事先同一的集群。

4.4 数据迁移:MIGRATE

的确先导Resharding在此之前,redis-trib会先在源结点和目标结点上执行cluster setslot <slot> importingcluster setslot <slot> migrating一声令下,将要迁移的槽分别标记为迁出中和导入中的状态。然后,执行cluster getkeysinslot取得Slot中的所有Key。最后就足以对每个Key执行migrate一声令下举办搬迁了。槽迁移完成后,执行cluster setslot一声令下通告任何集群槽的派出已经发生变化。

至于迁移过程中的数据访问,客户端访问源结点时,假设Key还在源结点上就径直操作。倘使已经不在源结点了,就向客户端再次来到一个ASK错误,将客户端重定向到目标结点

4.5 内部数据结构

Redis
Cluster功效涉及六个着力的数据结构clusterState、clusterNode、clusterLink都在cluster.h中定义。这两个数据结构中最重大的特性就是:clusterState.slots、clusterState.slots_to_keys和clusterNode.slots了,它们保存了两种炫耀关系

  • clusterState:集群状态 
    • nodes:所有结点
    • migrating_slots_to:迁出中的槽
    • importing_slots_from:导入中的槽
    • slots_to_keys:槽中隐含的富有Key,用于迁移Slot时获得其含有的Key
    • slots:Slot所属的结点,用于拍卖请求时判断Key所在Slot是否和谐承担
  • clusterNode:结点信息 
    • slots:结点负责的所有Slot,用于发送Gossip消息通告任何结点自己背负的Slot。通过位图模式保留节省空间,16384/8刚好是2048字节,所以槽总数16384不是随意定的
  • clusterLink:与此外结点通信的总是

// 集群状态,每个节点都保存着一个这样的状态,记录了它们眼中的集群的样子。
// 另外,虽然这个结构主要用于记录集群的属性,但是为了节约资源,
// 有些与节点有关的属性,比如 slots_to_keys 、 failover_auth_count 
// 也被放到了这个结构里面。
typedef struct clusterState {
    ...
    // 指向当前节点的指针
    clusterNode *myself;  /* This node */

    // 集群当前的状态:是在线还是下线
    int state;            /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */

    // 集群节点名单(包括 myself 节点)
    // 字典的键为节点的名字,字典的值为 clusterNode 结构
    dict *nodes;          /* Hash table of name -> clusterNode structures */

    // 记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点
    // migrating_slots_to[i] = NULL 表示槽 i 未被迁移
    // migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A
    clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];

    // 记录要从源节点迁移到本节点的槽,以及进行迁移的源节点
    // importing_slots_from[i] = NULL 表示槽 i 未进行导入
    // importing_slots_from[i] = clusterNode_A 表示正从节点 A 中导入槽 i
    clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];

    // 负责处理各个槽的节点
    // 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理
    clusterNode *slots[REDIS_CLUSTER_SLOTS];

    // 跳跃表,表中以槽作为分值,键作为成员,对槽进行有序排序
    // 当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供方便
    // 具体操作定义在 db.c 里面
    zskiplist *slots_to_keys;
    ...
} clusterState;

// 节点状态
struct clusterNode {
    ...
    // 节点标识
    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    // 以及节点目前所处的状态(比如在线或者下线)。
    int flags;      /* REDIS_NODE_... */

    // 由这个节点负责处理的槽
    // 一共有 REDIS_CLUSTER_SLOTS / 8 个字节长
    // 每个字节的每个位记录了一个槽的保存状态
    // 位的值为 1 表示槽正由本节点处理,值为 0 则表示槽并非本节点处理
    // 比如 slots[0] 的第一个位保存了槽 0 的保存情况
    // slots[0] 的第二个位保存了槽 1 的保存情况,以此类推
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */

    // 指针数组,指向各个从节点
    struct clusterNode **slaves; /* pointers to slave nodes */

    // 如果这是一个从节点,那么指向主节点
    struct clusterNode *slaveof; /* pointer to the master node */
    ...
};

/* clusterLink encapsulates everything needed to talk with a remote node. */
// clusterLink 包含了与其他节点进行通讯所需的全部信息
typedef struct clusterLink {
    ...
    // TCP 套接字描述符
    int fd;                     /* TCP socket file descriptor */

    // 与这个连接相关联的节点,如果没有的话就为 NULL
    struct clusterNode *node;   /* Node related to this link if any, or NULL */
    ...
} clusterLink;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

4.6 处理流程全梳理

在单机形式下,Redis对请求的处理很简单。Key存在的话,就进行请求中的操作;Key不存在的话,就报告客户端Key不设有。不过在集群模式下,因为涉嫌到请求重定向和Slot迁移,所以对请求的处理变得很复杂,流程如下:

  1. 检查Key所在Slot是否属于当前Node? 
    2.1 计算crc16(key) % 16384得到Slot 
    2.2 查询clusterState.slots负责Slot的结点指针 
    2.3 与myself指针相比
  2. 若不属于,则响应MOVED错误重定向客户端
  3. 若属于且Key存在,则直接操作,重临结果给客户端
  4. 若Key不设有,检查该Slot是否迁出中?(clusterState.migrating_slots_to)
  5. 若Slot迁出中,再次回到ASK错误重定向客户端到搬迁的目标服务器上
  6. 若Slot未迁出,检查Slot是否导入中?(clusterState.importing_slots_from)
  7. 若Slot导入中且有ASKING标记,则平素操作
  8. 要不然响应MOVED错误重定向客户端

5.用到案例收集

5.1 有道:Redis Cluster使用经验

详情请参见原文,关键内容摘录如下:

5.1.1 多少个缺陷

“redis
cluster的统筹在这块有点奇葩,跟集群相关的操作需要一个外表的ruby脚本来帮忙(当然或许是为着让主程序的代码丰富简洁?),然后十分剧本还只援助填实例的ip不扶助host,还不告知您不襄助让您用host之后各类莫名其妙。”

“第一个缺陷就是严酷信赖客户端driver的成熟度。假如把redis
cluster设计成类似卡桑德拉(Sandra)(Cassandra),请求集群中任何一个节点都可以承担转发呼吁,client会好写一些。”

“第二个毛病完全是计划性问题了,就是一个redis进程既肩负读写多少又担负集群交互,即便设计者已经竭尽简化了代码和逻辑,但要么让redis从一个内存NoSQL变成了一个分布式NoSQL。分布式系统很容易有坑,一旦有坑必须升级redis。”

5.1.2 去主旨化 vs. Proxy

“关于redis
cluster的统筹,Gossip/P2P的去中央化架构本身不是题材,但只要有了骨干节点,能做的事体就多了,比如sharding不均匀是很容易自行rebalance的,而无核心的只好靠外界来搞。然后redis
cluster又是slot的款式而非C*式的一致性哈希,新节点分slot又不自行,倚重外界(ruby脚本)来分配显得不便民更不雅观和谐。而且因为是master-slave的系列而非W+R>N的那种,master挂掉之后不久发现是相比较根本的,gossip对于节点挂掉的觉察终究没有基本节点/zookeeper方便高效。”

“基于proxy做转账表示屏蔽了下层存储,完全可以遵照前缀/tag/冷热程度,来把部分甚至大多数多少放在磁盘从而省去成本又确保一致性,这都是有主题节点所带动的便宜。”

5.2 奇虎360:Redis Cluster浅析和Bada对比

详情请参见原文,关键内容摘录如下:

5.2.1 负载均衡问题

“redis
cluster的主备是以节点为单位,而bada则是以partition为单位,这样,同样是3个节点,1024个partition的情景下,redis
cluster的主节点负责整个1024个partition的劳务,而七个从节点则只承担异步备份,导致集群负载不均,再看bada,将1024个partition的主均分到3个节点中,每个节点各有主备,主对外提供劳动,这样均分了拜访压力,有效的应用了资源。”

5.2.2 一致性的担保

redis
cluster与bada一样,最后一致性
,读写都只请求主节点,当一条写请求在相应的主节点写成功后,会即时回到给客户端成功,然后主节点通过异步的法子将新的数量同步到相应的从节点,这样的办法减弱了客户端两个节点写成功等待的日子,不过在好几意况下会造成写丢失:

1)当主节点接受一条写请求,写入并重临给客户端成功后不幸宕掉,此时刚刚的写还未共同给其对应的从节点,而从节点在发现主节点挂掉并再一次选主后,新的主节点则永久丢失了事先老的主节点向用户确认的写

2)当网络发出割裂,将集群分裂成少数派与大部分派,这样在客户端不知情的场馆下,会将写继续写入到个别派中的某些主节点中,而当割裂超越一定时长后,集群感知到充足,此时个别派中的所有主节点会截止响应所有的写请求,多数派的其对应的从节点则会发起选举成为新的主节点,假使过了一会后割裂恢复生机,老的主节点发现有改进的主存在,自动变成其从节点,而新的主节点中则会永远丢失掉网络割裂至集群感知异常举办切主这个阶段老主节点肯定的有所写

周旋于redis cluster的不可磨灭丢失,bada通过binlog
merge有效的解决了这一题材
。所有partition的主节点在响应客户端的写请求时,都会在本地记录binlog,binlog实质就是富含时间戳的KV对。当老主以从节点的地位重新出席集群时,会触发binlog
merge操作,新主会相比较并且统一二者的binlog,这样就足以将事先丢失掉得写再补回来。”

5.2.3 请求重定向问题

“bada服务端节点在接收本不该由友好担当的Partition请求后,不会向客户端重回重定向音讯,而是通过代办的方法,直接在集群内部向科学节点转发客户端的央浼,并将结果同meta信息再转发回客户端。”

“再看multi key操作,redis cluster为了追求高性能,帮助multi
key的前提是拥有的key必须在同一个节点中
,
然而如此的拍卖需要提交用户,对需要举行multi
key操作的持有key,在写入前人为的丰盛hash tags。当redis
cluster举办resharding的时候,也就是将一些slot从一个节点迁移到另一个节点时,此时的multi
key操作可能会失利,因为在搬迁的slot中的key此时设有于六个节点。

bada怎么办吗?用户一旦对multi key操作性能很在乎时,可以应用与redis
cluster同样的办法,给这多少个key加上hash
tags来让它们落在同一个节点,要是得以承受性能的多少损耗而解放用户的拍卖逻辑,则可以像single
key操作一样,请求任一bada节点,它会代理所有的key请求并将结果回到给用户。并且在multi
key操作在其它时候都足以,尽管在拓展partition的动迁
,bada也会提早开展切主,保证服务的正常提供。”

5.3 芒果电视:Redis服务解决方案

详情请参见原文,关键内容摘录如下:

芒果电视机在Redis Cluster基础上展开支付,紧要扩大了两个零部件:

  • 监督管理:以Python为首要开发框架的Web应用程序Redis-ctl
  • 恳请代理:以C++11为开销语言的轻量数据代理程序cerberus。其听从和优点为: 
    • 集群代理程序的自发性请求分发/重试机制使得应用不必修改自身代码或更新Redis库
    • 代理节点为富有Redis节点加上统一保管和气象监测, 可以查看历史数据,
      或在发生其他问题之后很快响应修复
    • 代办过程的无状态性使之可在故障后很快回升,
      不影响后端集群数据完整性

这六个零件都已开源到GitHub上,我们可以关注一下!


6.Pros & Cons总结

至于Redis
Cluster带来的各种优势就隐瞒了,在此处最首假设“鸡蛋里挑骨头”,总括一下脚下集群效应的不足之处和可能的“坑”。

6.1 无中央化架构

6.1.1 Gossip消息

Gossip信息的网络支付和时延是控制Redis
Cluster可以线性扩充的要素之一。关于这多少个问题,在《redis
cluster百万QPS的挑战》
一文中有着提及。

6.1.2 结点粒度备份

除此以外,Redis
Cluster也许是为着简化设计使用了Master-Slave复制的数据备份方案,并没有应用如卡桑德拉(Sandra)(Cassandra)(Cassandra)或IMDG等对等分布式系统中普遍的Slot粒度(或叫Partition/Bucket等)的机关冗余和派出。

这种规划尽管制止相比较复杂的分布式技术,但也带来了一部分题材:

  • Slave完全闲置:即使是读请求也不会被重定向到Slave结点上,Slave属于“冷备”
  • 写压力不可能分摊:Slave闲置导致的另一个题目就是写压力也都在Master上

6.2 客户端的搦战

出于Redis Cluster的宏图,客户端要担负起一部分权责:

  • Cluster协议协助:不管Dummy依旧Smart模式,都要持有解析Cluster协议的力量
  • 网络支付:Dummy客户端不断重定向的网络开发
  • 老是维护:Smart客户端对连接到集群中每个结点Socket的掩护
  • 缓存路由表:Smart客户端Slot路由表的缓存和更新
  • 内存消耗:Smart客户端上述维护的信息都是有内存消耗的
  • MultiOp有限帮忙:对于MultiOp,由客户端通过KeyTag保证所有Key都在同一Slot。而尽管如此,迁移时也会促成MultiOp失利。同理,对Pipeline和Transaction的支撑也受限于必须操作同一Slot内的Key。

6.3 Redis实现问题

即使属于无中央化架构一类的分布式系统,但不同出品的底细实现和代码质料依然有为数不少区其它,就比如Redis
Cluster有些地点的规划看起来就有一对“奇葩”和简陋:

  • 不可以自动发现:无Auto
    Discovery功效。集群建即刻以及运行中新增结点时,都要通过手动执行MEET命令或redis-trib.rb脚本添加到集群中
  • 不可能自动Resharding:不仅不自动,连Resharding算法都尚未,要团结总结从怎么样结点上迁移多少Slot,然后依旧得经过redis-trib.rb操作
  • 沉痛倚重外部redis-trib:如上所述,像集群健康情形检查、结点参加、Resharding等等功效全都抽离到一个Ruby脚本中了。还不清楚下边提到的缺失效用未来是要继承加到这一个剧本里如故会晤并到集群结点中?redis-trib也许要变成Codis中Dashboard的角色
  • 无监控管理UI:尽管将来加了UI,像迁移进度这种音讯在无主题化设计中很难取得
  • 只保证最后一致性:写Master成功后随即回去,如需强一致性,自行通过WAIT命令实现。但对此“脑裂”问题,近日Redis没提供网络復苏后的Merge功用,“脑裂”期间的换代可能有失

6.4 性能损耗

鉴于事先手头没有空闲的物理机资源,所以只在虚拟机上做了大概的单机测试,在单身的一台压力机使用YCSB测试框架向虚拟机暴发读写负载。虚拟机的配置为8核AMDXeon CPU
X5650@2.67GHz,16GB内存,分别搭建了4结点的单机版Redis和集群版Redis,测试一下Redis
Cluster的特性损耗。由于不是近年来做的测试,所以Jedis用的2.6.2本子。注:当然Redis
Cluster可以透过多机部署得到水平扩大带来的性质提高,这里只是出于条件有限所以做的简要单机测试。

鉴于YCSB本身仅扶助Redis单机版,所以需要我们团结一心扩充扩张插件,具体方法请参见《YCSB性能测试工具使用》。通过YCSB爆发2000w随机数据,Value大约100Byte左右。然后通过YCSB测试Read-Mostly(90%
Read)和Read-Write-Mixed(50% Read)二种情状:

  • 多少加载:吞吐量上有约18%的狂跌。
  • Read-Mostly:吞吐量上有约3.5%~7.9%的下降。
  • Read-Write-Mixed:吞吐量上有约3.3%~5.5%下降。
  • 内存占用:Jedis客户端多占用380MB内存。

6.5 最后的下结论

从当下看来,相相比较Sentinel或Codis等方案,Redis
Cluster的优势还真是简单,个人觉得最大的独到之处有六个:

  1. 法定提供的Slot实现而不用像Codis这样去改源码了;
  2. 不用额外的Sentinel集群或类似的代码实现了。

同另外分布式系统,如Cassandra(Cassandra),或内存型的IMDG如Hazelcast和GridGain,除了性能方面外,从效果上Redis
Cluster简直被爆端庄无完肤…
看看自己从前总计过的GridGain介绍《开源IMDG之GridGain》

  • 结点自动发现和Rebalance
  • 分区粒度的备份
  • 故障时分区角色自动调整
  • 结果聚合(不会重定向客户端)
  • “脑裂”恢复生机后的Merge(Hazelcast协理多种统一策略)
  • 多Primary分区写操作(见Replicated形式)

这个都是Redis
Cluster没有或者要手动完成的。当然这也熟视无睹,因为这与Redis的计划性初衷有关
,毕竟作者都早就说了,最中央的统筹目的就是性质、水平伸缩和可用性。

从Redis
Cluster的条件搭建使用到高级功效和里面原理分析,再到利用案例收集和优缺点的解析罗列,讲了这样多,关于Redis集群到底怎么着,相信我们遵照自己亲身和花色的具体情形一定有了自己的下结论。不管是评估测试也好,二次开发也好,仍旧一贯上线使用可以,相信随着官方的持续迭代革新和豪门的力量,Redis
Cluster一定会逐步全面成熟的!

网站地图xml地图