Redis Cluster集群知识学习总结

 

Redis集群解决方案有两个: 1)  Twemproxy: 这是Twitter推出的解决方案,简单的说就是上层加个代理负责分发,属于client端集群方案,目前不少应用者都在采用的解决方案。Twemproxy会用到LVS、Twemproxy、Keepalived、Redis主从模式,有点麻烦,并且没有在线扩容节点能力,须要一开始就预留出足够的节点,以前的文章也详细介绍了Twemproxy这种集群方式及其部署过程; 2) Redis Cluster: 这是Redis3.0以后,官方推出的server端集群方案。html

Redis 3.0以后支持了Cluster,大大加强了Redis水平扩展的能力。Redis Cluster是Redis官方的集群实现方案,在此以前已经有第三方Redis集群解决方案,如Twenproxy、Codis,与其不一样的是:Redis Cluster并不是使用Porxy模式来链接集群节点,而是使用无中心节点的模式来组建集群。在Cluster出现以前,只有Sentinel保证了Redis的高可用性node

Redis Cluster实如今多个节点之间进行数据共享,即便部分节点失效或者没法进行通信时,Cluster仍然能够继续处理请求。若每一个主节点都有一个从节点支持,在主节点下线或者没法与集群的大多数节点进行通信的状况下, 从节点提高为主节点,并提供服务,保证Cluster正常运行,Redis Cluster的节点分片是经过哈希槽(hash slot)实现的,每一个键都属于这 16384(0~16383) 个哈希槽的其中一个,每一个节点负责处理一部分哈希槽。git

前面已经介绍了Redis Cluster集群及其部署过程,下面再补充下有关Redis Cluster应用原理部份内容,以便更加深入透彻地理解Redis Cluster。github

1、Redis Cluster集群最核心的三个目标redis

  • 性能:这是Redis赖以生存的看家本领,增长集群功能后固然不能对性能产生太大影响,因此Redis采起了P2P而非Proxy方式、异步复制、客户端重定向等设计,而牺牲了部分的一致性、使用性。
  • 水平扩展:集群的最重要能力固然是扩展,文档中称能够线性扩展到1000结点。
  • 可用性:在Cluster推出以前,可用性要靠Sentinel保证。有了集群以后也自动具备了Sentinel的监控和自动Failover能力。

2、Redis架构变化与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理论要求的,三者只能取其二。数据库

=============Redis Cluster的概念特色============
-  去中心、去中间件,各节点平等,保存各自数据和集群状态,节点间活跃互连。
-  传统用一致性哈希分配数据,集群用哈希槽(hash slot)分配。 算法为CRC16。
-  默认分配16384个slot, 用CRC16算法取模{ CRC16(key)%16384 }计算所属slot。
-  最少3个主节点数组

==========简单来讲, Redis Cluster集群的优势=======
- 官方解决方案
- 能够在线水平扩展(Twemproxy的一大弊端就是不支持在线扩容节点)
- 客户端直连,系统瓶颈更少
- 无中心架构
- 支持数据分片ruby

3、Redis Cluster集群部署bash

1)集群部署和配置

这个以前已经介绍过了,部署过程参考:http://www.cnblogs.com/kevingrace/p/7846324.html
要想开启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

配置好后,根据集群规模,拷贝出来几份一样的配置文件,惟一不一样的就是监听端口,能够依次改成700一、7002… 由于Redis Cluster若是数据冗余是1的话,至少要3个Master和3个Slave,因此能够拷贝出6个实例的配置文件。为了不相互影响,为6个实例的配置文件创建独立的文件夹。

2)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

3)集群创建
首先,启动配置好的6个Redis实例。

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

此时6个实例尚未造成集群,如今用redis-trb.rb管理脚本创建起集群。能够看到,redis-trib默认用前3个实例做为Master,后3个做为Slave。由于Redis基于Master-Slave作数据备份,而非像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.

至此,Redis Cluster集群就已经创建成功了!“贴心”的Redis还在utils/create-cluster下提供了一个create-cluster脚本,可以建立出一个集群,相似上面创建起的3主3从的集群。

4)Redis Cluster集群简单测试
链接到集群中的任意一个结点,启动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"

仔细观察可以注意到,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

5)Redis Cluster 集群重启
目前redis-trib的功能还比较弱,须要重启集群的话,须要先手动kill掉各个进程,而后从新启动就能够了。这确实有点太傻X, 网上有人反馈说重启有问题,不过本人暂时还没遇到问题。

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

6)Redis Cluster集群数据迁移
这就须要体验一下Redis集群的Resharding功能了~~

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"

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 - 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. 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 迁移完成后,查看以前保存的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"

7)Redis Cluster集群故障转移

在高可用性方面,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

尝试查询以前保存在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"

经过上面能够知道,用Redis提供的redis-trib或create-cluster脚本能几步甚至一步就创建起一个Redis集群。本篇为了深刻了解Redis Cluster的用户,因此要暂时抛开这些方便的工具,彻底手动创建一遍上面的3主3从集群。

8)Redis Cluster集群发现: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

9)Redis Cluster集群的角色设置(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

10)Redis Cluster的槽指派(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.

这样就经过手动执行命令获得了与以前同样的集群。

11)Redis Cluster集群的数据迁移(MIGRATE)
真正开始Resharding以前,redis-trib会先在源结点和目的结点上执行cluster setslot <slot> importing和cluster setslot <slot> migrating命令,将要迁移的槽分别标记为迁出中和导入中的状态。而后,执行cluster getkeysinslot得到Slot中的全部Key。最后就能够对每一个Key执行migrate命令进行迁移了。槽迁移完成后,执行cluster setslot命令通知整个集群槽的指派已经发生变化。
关于迁移过程当中的数据访问,客户端访问源结点时,若是Key还在源结点上就直接操做。若是已经不在源结点了,就向客户端返回一个ASK错误,将客户端重定向到目的结点。

12)Redis Cluster集群内部数据结构
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 也被放到了这个结构里面。

ypedef 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;

13)Redis Cluster集群的处理流程全梳理
在单机模式下,Redis对请求的处理很简单。Key存在的话,就执行请求中的操做;Key不存在的话,就告诉客户端Key不存在。然而在集群模式下,由于涉及到请求重定向和Slot迁移,因此对请求的处理变得很复杂,流程以下:

  • 检查Key所在Slot是否属于当前Node?
  • 计算crc16(key) % 16384获得Slot
  • 查询clusterState.slots负责Slot的结点指针
  • 与myself指针比较
  • 若不属于,则响应MOVED错误重定向客户端
  • 若属于且Key存在,则直接操做,返回结果给客户端
  • 若Key不存在,检查该Slot是否迁出中?(clusterState.migrating_slots_to)
  • 若Slot迁出中,返回ASK错误重定向客户端到迁移的目的服务器上
  • 若Slot未迁出,检查Slot是否导入中?(clusterState.importing_slots_from)
  • 若Slot导入中且有ASKING标记,则直接操做
  • 不然响应MOVED错误重定向客户端

14)Redis Cluster集群现实存在的问题
尽管属于无中心化架构一类的分布式系统,但不一样产品的细节实现和代码质量仍是有很多差别的,就好比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功能,“脑裂”期间的更新可能丢失
============================================redis cluster install  ========================================
3主 3从, 从库交叉存放在 主库上 

主:ip 192.168.1.101 6381   从ip 192.168.1.102  6383
主:IP 192.168.1.102  6382  从ip 192.168.1.103   6381
主:ip 192.168.1.103  6383  从 ip 192.168.1.101  6382 

Redis cluster 集群配置文件

建立集群目录
mkdir /data/redis_data/{conf,data,logs,temp}

101 节点的配置文件
/data/redis_data/conf/redis-6381.conf
/data/redis_data/conf/redis-6383.conf

102 节点配置文件
/data/redis_data/conf/redis-6382.conf
/data/redis_data/conf/redis-6381.conf

103 节点配置文件
/data/redis_data/conf/redis-6383.conf
/data/redis_data/conf/redis-6382.conf

集群配置文件内容 范例
================================================================================
################################## NETWORK #####################################
bind 0.0.0.0
protected-mode yes
port 6382
tcp-backlog 511
unixsocket /data/redis_data/temp/redis.sock
unixsocketperm 700
timeout 0
tcp-keepalive 300
################################# GENERAL #####################################
daemonize yes
supervised no
pidfile /data/redis_data/temp/redis_6382.pid
# debug # verbose# notice # warning 
loglevel notice
logfile "/data/redis_data/logs/redis_6382.log"
syslog-enabled no
# Specify the syslog identity.
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
databases 1
################################ SNAPSHOTTING  ################################
#   save <seconds> <changes>
#save 900 1
#save 300 10
#save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression no
rdbchecksum yes
dbfilename dump_6382.rdb
dir /data/redis_data/data
################################# REPLICATION #################################
# slaveof 192.168.1.101  6379 
# masterauth <master-password>
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
# repl-ping-slave-period 10
repl-timeout 60
repl-disable-tcp-nodelay no
repl-backlog-size 1mb
repl-backlog-ttl 3600
# By default the priority is 100.
slave-priority 100
# min-slaves-to-write 3
# min-slaves-max-lag 10
# slave-announce-ip 5.5.5.5
# slave-announce-port 1234
################################## SECURITY ###################################
# requirepass foobared
# rename-command CONFIG ""
################################### LIMITS ####################################
maxclients 10000
maxmemory 4294967296
# maxmemory-policy noeviction
# maxmemory-samples 5
############################## APPEND ONLY MODE ###############################
appendonly yes
appendfilename "appendonly_6382.aof"
# appendfsync always
appendfsync everysec
# appendfsync no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
################################ LUA SCRIPTING  ###############################
lua-time-limit 5000
################################ REDIS CLUSTER  ###############################
cluster-enabled yes
cluster-config-file nodes-6382.conf
cluster-node-timeout 15000
# cluster-migration-barrier 1
# cluster-require-full-coverage yes
################################## SLOW LOG ###################################
slowlog-log-slower-than 10000
slowlog-max-len 128
################################ LATENCY MONITOR ##############################
latency-monitor-threshold 0
############################# EVENT NOTIFICATION ##############################
# PUBLISH __keyspace@0__:foo del
# PUBLISH __keyevent@0__:del foo
#  notify-keyspace-events Elg
#  notify-keyspace-events Ex
notify-keyspace-events ""
############################### ADVANCED CONFIG ###############################
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
====================================================================================
启动节点

redis-server /data/redis_data/conf/redis-6381.conf 
redis-server /data/redis_data/conf/redis-6383.conf 

redis-server /data/redis_data/conf/redis-6382.conf
redis-server /data/redis_data/conf/redis-6381.conf

redis-server /data/redis_data/conf/redis-6383.conf
redis-server /data/redis_data/conf/redis-6382.conf
==============================安装 redis-trib.rb====================================
安装ruby 2.4.1.tgz
./configure --prefix=/usr/local/ruby
make
make install
cd /usr/local/ruby
cp ruby /usr/local/bin/
cp gem /usr/local/bin

wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
gem list --check redis gem
cp /data/software/redis-3.2.9/src/redis-trib.rb  /usr/local/bin
redis-trib.rb
=============================================集群模式配置=============================================

如下这种方式貌似不能按照本身的思路添加主从
redis-trib.rb create --replicas 1 192.168.1.101:6381 192.168.1.102:6382   192.168.1.103:6383 192.168.1.102:6381 192.168.1.103:6382   192.168.1.101:6383

思路改成先加主库 再加从库
添加主库
redis-trib.rb create  192.168.1.101:6381 192.168.1.102:6382  192.168.1.103:6383 

添加从库
把 102的6381 做为从库加入 101的6381
redis-trib.rb add-node --slave 192.168.1.102:6381   192.168.1.101:6381

redis-trib.rb add-node --slave 192.168.1.103:6382   192.168.1.102:6382
redis-trib.rb add-node --slave 192.168.1.101:6383   192.168.1.103:6383

检测
redis-trib.rb check 192.168.1.101:6381
redis-trib.rb check 192.168.1.102:6382
redis-trib.rb check 192.168.1.103:6383

随便连接一个就好了,

4、Redis Cluster容错机制failover总结

failover是redis cluster的容错机制,是redis cluster最核心功能之一;它容许在某些节点失效状况下,集群还能正常提供服务。

redis cluster采用主从架构,任什么时候候只有主节点提供服务,从节点进行热备份,故其容错机制是主从切换机制,即主节点失效后,选取一个从节点做为新的主节点。在实现上也复用了旧版本的主从同步机制。

从纵向看,redis cluster是一层架构,节点分为主节点和从节点。从节点挂掉或失效,不须要进行failover,redis cluster能正常提供服务;主节点挂掉或失效须要进行failover。另外,redis cluster还支持manual failover,即人工进行failover,将从节点变为主节点,即便主节点还活着。下面将介绍这两种类型的failover。

1)主节点失效产生的failover
a)(主)节点失效检测
通常地,集群中的节点会向其余节点发送PING数据包,同时也老是应答(accept)来自集群链接端口的链接请求,并对接收到的PING数据包进行回复。当一个节点向另外一个节点发PING命令,可是目标节点未能在给定的时限(node timeout)内回复时,那么发送命令的节点会将目标节点标记为PFAIL(possible failure)。

因为节点间的交互老是伴随着信息传播的功能,此时每次当节点对其余节点发送 PING 命令的时候,就会告知目标节点此时集群中已经被标记为PFAIL或者FAIL标记的节点。相应的,当节点接收到其余节点发来的信息时, 它会记下那些被其余节点标记为失效的节点。 这称为失效报告(failure report)。

若是节点已经将某个节点标记为PFAIL,而且根据节点所收到的失效报告显式,集群中的大部分其余主节点(n/2+1)也认为那个节点进入了失效状态,那么节点会将那个PFAIL节点的状态标记为FAIL。

一旦某个节点被标记为FAIL,关于这个节点已失效的信息就会被广播到整个集群,全部接收到这条信息的节点都会将失效节点标记为FAIL。

b)选举主节点
一旦某个主节点进入 FAIL 状态, 集群变为FAIL状态,同时会触发failover。failover的目的是从从节点中选举出新的主节点,使得集群恢复正常继续提供服务。
整个主节点选举的过程可分为申请、受权、升级、同步四个阶段:
(1)申请
新的主节点由原已失效的主节点属下的全部从节点中自行选举产生,从节点的选举遵循如下条件:
a、这个节点是已下线主节点的从节点;
b、已下线主节点负责处理的哈希槽数量非空;
c、主从节点之间的复制链接的断线时长有限,不超过 ( (node-timeout * slave-validity-factor) + repl-ping-slave-period )。

若是一个从节点知足了以上的全部条件,那么这个从节点将向集群中的其余主节点发送受权请求,询问它们是否容许本身升级为新的主节点。
从节点发送受权请求的时机会根据各从节点与主节点的数据误差来进行排序,让误差小的从节点优先发起受权请求。
(2)受权
其余主节点会遵信如下三点标准来进行判断:
a、 发送受权请求的是从节点,并且它所属的主节点处于FAIL状态 ;
b、 从节点的currentEpoch〉自身的currentEpoch,从节点的configEpoch>=自身保存的该从节点的configEpoch;
c、 这个从节点处于正常的运行状态,没有被标记为FAIL或PFAIL状态;

若是发送受权请求的从节点知足以上标准,那么主节点将赞成从节点的升级要求,向从节点返回CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK受权。
(3)升级
一旦某个从节点在给定的时限内获得大部分主节点(n/2+1)的受权,它就会接管全部由已下线主节点负责处理的哈希槽,并主动向其余节点发送一个PONG数据包,包含如下内容:
a、 告知其余节点本身如今是主节点了
b、 告知其余节点本身是一个ROMOTED SLAVE,即已升级的从节点;
c、告知其余节点都根据本身新的节点属性信息对配置进行相应的更新
(4)同步
其余节点在接收到ROMOTED SLAVE的告知后,会根据新的主节点对配置进行相应的更新。特别地,其余从节点会将新的主节点设为本身的主节点,从而与新的主节点进行数据同步。
至此,failover结束,集群恢复正常状态。

此时,若是原主节点恢复正常,但因为其的configEpoch小于其余节点保存的configEpoch(failover了产生较大的configEpoch),故其配置会被更新为最新配置,并将本身设新主节点的从节点。

另外,在failover过程当中,若是原主节点恢复正常,failover停止,不会产生新的主节点。

2)Manual Failover
Manual Failover是一种运维功能,容许手动设置从节点为新的主节点,即便主节点还活着。
Manual Failover与上面介绍的Failover流程大都相同,除了下面两点不一样:
a)触发机制不一样,Manual Failover是经过客户端发送cluster failover触发,并且发送对象只能是从节点;
b)申请条件不一样,Manual Failover不须要主节点失效,failover有效时长固定为5秒,并且只有收到命令的从节点才会发起申请。

另外,Manual Failover分force和非force,区别在于:非force须要等从节点彻底同步完主节点的数据后才进行failover,保证不丢失数据,在这过程当中,原主节点中止写操做;而force不进行进行数据完整同步,直接进行failover。

3)集群状态检测集群有OK和FAIL两种状态,能够经过CLUSTER INFO命令查看。当集群发生配置变化时, 集群中的每一个节点都会对它所知道的节点进行扫描,只要集群中至少有一个哈希槽不可用(即负责该哈希槽的主节点失效),集群就会进入FAIL状态,中止处理任何命令。 另外,当大部分主节点都进入PFAIL状态时,集群也会进入FAIL状态。这是由于要将一个节点从PFAIL状态改变为FAIL状态,必需要有大部分主节点(n/2+1)承认,当集群中的大部分主节点都进入PFAIL时,单凭少数节点是没有办法将一个节点标记为FAIL状态的。 然而集群中的大部分主节点(n/2+1)进入了下线状态,让集群变为FAIL,是为了防止少数存着主节点继续处理用户请求,这解决了出现网络分区时,一个可能被两个主节点负责的哈希槽,同时被用户进行读写操做(经过禁掉其中少数派读写操做,证保只有一个读写操做),形成数据丢失数据问题。说明:上面n/2+1的n是指集群里有负责哈希槽的主节点个数。

相关文章
相关标签/搜索