Redis- 集群:AKF拆分(y轴和z轴),CAP,主从复制,哨兵机制
容量问题
单机redis在使用的时候会碰到三个问题:单点故障、容量不足、访问压力。
因此我们可以对redis进行AKF拆分,站在x轴的角度上可以对redis进行 主从复制 + 哨兵机制。
主从复制:slave做master的全量备份保证数据的可靠性。master对外提供读写,slave对外提供读操作(读写分离),解决了 部分访问压力。
哨兵机制:对master做高可用,使用 哨兵集群 监控master健康状态,哨兵之间相互通信,如果发现master挂了会进行投票选举 其中一个个slave作为新的master。解决了单点故障的问题。
但是由于slave是master的全量数据,在容量这个维度上来说,redis依然是一个单实例的。对于臃肿的redis实例来说,还需要对其进行y轴个z轴的拆分。
什么是容量问题呢?
如果一个redis,它的内存是有限的,即便服务器有128G内存,也不可能让一个redis实例把128G内存全部使用,因为做持久化等等消耗时间成本很大。所以我们尽量控制一个redis实例只使用几G内存,这样这个实例的所有环节和动作都会很轻盈。
Y轴拆分
在数据可以分类,较比不是很多的情况下,我们可以在Client端把要写入redis数据 根据业务(商品、购物车、订单) 在AKF的Y轴上进行分类,把不同的分类写入不同的redis实例。
换个角度讲,就是每个redis实例自己是不知道拆分的,是Client中融入一部分 业务拆分逻辑 代码,使Client根据不同的业务访问不同的redis。
但是如果根据业务进行拆分后,某一个业务的数据数据量很大(比如:订单),应该如何继续拆分?
答案:在AKF的Z轴上继续拆分。
Z轴拆分
拆分逻辑在Client
Y轴拆分后某一业务的数据量还是很大,我们可以对Z轴方向上,在Client端根据 算法 继续拆分。(sharding 分片)
1. modula(hsah + 取模):
这种方式是在Client增加算法逻辑,把要存入数据的进行 % 运算,后边有几个redis实例,就和几进行 % 运算,根据取模的值,把数据存入到不同的redis实例。
优点: 简单,容易操作。
缺点: 取模 的值是固定的,影响分布式下的扩展性,如果添加新的redis实例会使 模数值 发生改变,再取数据的时候根据 模数 去取就无法查找到数据了。
2. random:
Client把数据随机放到不同的redis实例中:
一定会有很多人会问 ,随机把数据放入不同的redis,取出成本会不会很高?或者根本找不到数据?
但是为什么还会有这random模型呢? 如果存入的是一个list类型(lpush),key为ooxx,那么就会在两个redis中都生成ooxx的key,这个时候 消费者 并不是执行lpush的Client,而是执行rpop的另一个Client,都会从ooxx中取数据,无论从哪个redis取,都会取到key为ooxx的数据。这样就形成了一个类似于消息队列的功能。
说的简单一些,就是应用list类型 左进右出 或者 右进左出 的特性模拟队列,只不过在random模型中Client把list数据分发给不同的redis可以起到一个缓冲的效果,更适合高并发的场景。
3. ketama(一致性hash):
相较于 hash + 取模 的方式 弊端,有一种更好的解决方案,就是ketama的方式,其核心就是 一致性hash 算法。
先了解 一致性hash 算法:
在得到一个长度随机的字符串后(redis的key),经过算法得到一个等宽的其他的值(算法:hash、crc16、crc32、fnv、md5),这个值会和字符串做一个映射。和hash取模算法不同的是,一致性hash没有取模的过程(即:不会影响分布式扩展性),并且要求key和node都要参与计算。
然后会在内存中虚拟出一个环形(哈希环)。
假如这个哈希环由0 — 2^32个数字组成,每一个数字都是一个点。无论后面有多少个redis实例,或新增,或减少,都会给这些redis实例一个ID号(或者IP+Port之类的)唯一标识。
如果这个时候有一个 node1 节点,把node1的ID 经过 一个统一的hash算法就会得到一个值,这个值会映射到哈希环的某一个点上。这个时候新增node2节点,同样把node2节点的ID经过 统一的hash运算 得到一个值,也映射到这个哈希环上某个点。(node在哈希环上对应的点 称之为 物理点)
现在 在哈希环上有两个node映射的物理点,把这两个物理点放入带有排序功能的TreeMap集合里。这个时候如果Client要写入数据,可以把数据的key也参与hash运算后会到一个值,并且这个值同样映射到 哈希环 上点,然后用这个映射点和TreeMap里面的物理点进行对比,查找大过自己并且距离自己最近的点是哪个?最后把数据存入距离自己最近的物理点对应的node就可以了。
优点:
没有固定node数量的限制,不会造成全局洗牌。缺点:
新增节点后,造成一小部分数据无法命中。
如上图,新增一个节点 node3,把node3的ID进行hash运算得到一个数值,这个数值对应 哈希环 的点 恰好在之前数据映射的点和它存入node之间(data -> node2 变为 data -> node3 -> node2), 此时如果查询data,不会从node2查询,而是会从node3里面查找data,因此查询结果为null。
所以,这种方式只适合做缓存而不适合做数据库,缓存大多为期限,即使node2里面data数据永远不会被查询到,data也会随着时间的推移而被清除掉,或者开启缓存清理策略LRU、LFU。并且node3里面无法查询到数据,可以走数据库从新缓存到node3(缓存击穿),也可以修改为从比自己大的两个node中查找数据。
扩展:虚拟节点
如果node1和node2映射在 哈希环 的点分别在最左边 和 最右边,把哈希环切分成了 上半环 和 下半环,在添加数据的时候就可能出现 数据集中在某个半环上而导致 数据量 的倾斜。
我们可以每个node ID后面依次拼10个数字,让一个ID变成10个,用这些ID做hash运算映射到 哈希环 不同的点上,如果是两个设备在 哈希环 上面就会有20个点,这样做的目的是让一个物理设备出现在 多个 点上,就可以间接的解决数据倾斜的问题。
拆分逻辑在Proxy
拆分逻辑在Client弊端
在Client里实现 Z轴 拆分逻辑,虽然能解决容量问题,但是在实际场景中,一台redis可能要面对多个Client,换言之 每个Client都要和每个redis实例进行连接,况且每个Client不可能只和redis开一个连接,而是应该开一个连接池缓存10个或更多的连接。即便是没有连接池,每次TCP握手和分手都是一个损耗的事情。
结论:redis的连接成本很高。
如何解决?增加proxy代理。
Proxy拆分逻辑原理
可以在Client和redis之间增加一个代理层(proxy),类似于Nginx这种应用层 的反向代理,基于反向代理的基础上 负载均衡服务器。
Client只需要和proxy建立握手,proxy再单独和多个redis实例建立握手,这样就可以减少对redis的socket连接的压力。这个时候我们更关注的是proxy的性能。
基于proxy的理论,可以把在Client的拆分逻辑 迁移 到 代理层,所以在代理层可以对modula、random、ketama进行逻辑实现。
注意:
无论拆分逻辑在Client还是Proxy,都没有脱离modula、random、ketama三种模式,并且都只适合作为缓存,不适合做数据库。这个问题如何解决?redis自带的拆分:cluster。
常用的代理:
twemproxy
、predixy
。
拓展 -> proxy是不需要记录Client状态,本身不存在数据库存储,所以proxy是无状态的 ,很容易的就可以把proxy一变多:
如果在高并发场景下,一个proxy不够用了怎么办:
那就使用两个proxy,让固定Client组连接固定的proxy,为了提高可用性,可以搭建LVS四层 负载均衡 服务器 对两个proxy做负载均衡。
但是LVS也是一个单点,并且不能对后端的proxy集群做健康检查,所以可以使用keepalived建立VIP对LVS做主备HA高可用。
同时建立LVS的VIP也有一个好处:无论企业后端技术多复杂,对用户都是透明的。用户只需要访问VIP就OK!
redis自带的拆分:cluster
redis作为数据库的情况下,可以使用redis自带的拆分cluster。规避了ketama新增redis实例时截断数据的情况,那么cluster是如何做到的呢?
引导:
假如现在有两台redis服务器,分别是redis1和redis2,以后还会出现第三台服务器redis3。
在加入新节点到集群的话,无论拆分逻辑是在Client还是在Proxy,对算法都是一个极大的挑战。
要解决这个问题,可以在 拆分逻辑中 进行 预分区:
现在有两个redis节点,未来就可能有10个节点。那么在两个节点的时候,就可以直接取模10个节点,取模的结果为 0 ~ 9 ,两个节点可以分别对0~9个节点做一个mapping(10个槽位),比如:
redis1映射0,1,2,3,4
redis2映射5,6,7,8,9
(各领取5个槽位)。
这个时候如果redis3加入集群,会让redis1和redis2让出几个槽位,比如从redis1中拿到了3和4,在redis2中拿到了8和9(如上图)。移动数据的时候,不需要把redis全部rehash,只需要把3,4,8,9槽位的时点数据找到直接传输给redis3,redis3接收完数据后,会根据 时点数据 跟redis1和redis2传输期间内的数据做一个追平更新,追平的一刹那,再往3,4,8,9槽位存数据就会直接存入redis3。
Client只需要知道映射关系,就可以根据槽位正确的读取数据了。
cluster:
有3台redis服务器,分别是redis1,redis2,redis3。
Client想要存入k1,会随机访问redis1和redis2。
和之前不同的是每一个redis中都存在hash算法,并且保存了集群内其他redis实例的槽位映射关系(知道别人有什么),满足了这两个条件后,就可以让每个redis实例都能当家做主。
这样k1随机进入一个redis实例后(假设这个实例是redis2),redis2会先拿k1进行hash取模算出槽位,用计算的槽位和自己的mapping映射的槽位做一个匹配,如果和自己匹配就直接存入redis2,如果不匹配再和redis2内保存的其他实例的槽位映射关系进行匹配,就能找到k1需要存的redis实例是哪个,把k1返回给Client 并 重定向到正确的redis实例中(假设匹配结果为redis3)。redis3拿到k1后同样要进行hash运算并且 把结果和自身映射槽位进行匹配,匹配成功直接存入自己这个实例当中。
摘要redis中文网:
“实际redis中,Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽。”
这就是redis自带的cluster,是一种无主模型。
注意:
只要把数据拆分,无论逻辑在Client、Proxy、还是redis自身,都会有一个问题 : 聚合操作很难实现。
- 一个操作需要几个key,但是这些key都不在一个节点上。
- 事务
- set集合的交集,并集,差集运算。两个set分别在不同的redis中,如果redis自身实现就会涉及数据移动过程,redis在设计的思想是 计算 向 数据移动,而不是移动数据。redis的特点就是快,所以作者做了一个取舍。
解决方案:问题的起点在于 数据被分开。换言之,数据不会被分开就能够实现 聚合操作。
操作的key都在相同的节点,可以用 hash tag 。
比如:把key设置成为{oo}k1,{oo}k2,{oo}k3这种带有{}内部有固定标识的 格式,这样在存入key的时候会使用 oo 进行取模,而不会使用k1,k2,k3进行取模。进而存入同一个redis节点。
twemproxy操作演示
GitHub地址:https://github.com/twitter/twemproxy
安装:
cd /soft/twemproxy //新建存放twemproxy的目录
git clone https://github.com/twitter/twemproxy.git //从git上clone下该项目
yum install automake libtool -y //安装automake和libtool
autoreconf -fvi //执行完之后目录文件多出configure可执行文件
./configure //执行configure文件
make //进行编译
cd src/ //进入src目录,会看到nutcracker可执行程序
cd ../ //会看见有scripts脚本目录
cd scripts //进入scripts目录,会看见nutcracker.init文件
################################### 把twemproxy变为服务 ##################################
cp nutcracker.init /etc/init.d/twemproxy //把nutcracker.init拷贝到/etc/init.d/目录
cd /etc/init.d/ //发现cp后的twemproxy还没有变绿,所以要赋予权限
chmod 777 twemproxy //赋予权限,使其变绿
vi twemproxy //发现里面OPTIONS="-d -c /etc/nutcracker/nutcracker.yml" 需要在指定目录有nutcracker.yml文件。
mkdir /etc/nutcracker //创建目录
cd /soft/twemproxy/twemproxy/conf //回到twemproxy安装目录下的conf目录,里面有nutcracker.yml文件
cp ./* /etc/nutcracker/ //拷贝当前目录下所有文件到 /etc/nutcracker/ 目录
cd /soft/twemproxy/twemproxy/src //进入src目录下
cp nutcracker /usr/bin/ //把之前编译的可执行程序复制到 /usr/bin/ 目录
cd /etc/nutcracker/ //进入之前移动配置文件的目录
cp nutcracker.yml nutcracker.yml.bak //把nutcracker.yml文件进行备份(好习惯)
vi nutcracker.yml //修改配置文件,下面有详细介绍
nutcracker.yml 详解
alpha:
listen: 127.0.0.1:22121 #代理众多redis实例对外暴露的统一访问接口
hash: fnv1a_64 #使用的hash算法
distribution: ketama #分布式使用的模型
auto_eject_hosts: true
redis: true #代理的是redis
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- 127.0.0.1:6379:1
- 127.0.0.1:6380:1 #计量代理的两个redis实例
演示
先启动两个redis:
cd
mkdir data
cd data
mkdir 6379
mkdir 6380
cd 6379
redis-server --port 6379
############# 切入到新窗口
cd data/6380
redis-server --port 6380
启动twemproxy :
[root@z8524210 6379]# service twemproxy start
Reloading systemd: [ 确定 ]
Starting twemproxy (via systemctl): [ 确定 ]
[root@z8524210 6379]#
启动后,连接twemproxy提供的统一接口,并添加元素:
[root@z8524210 6379]# redis-cli -p 22121
127.0.0.1:22121> set k1 aaa
OK
127.0.0.1:22121> set k2 asd
OK //在统一接口里添加4个元素 k1,k2,k3和 1
127.0.0.1:22121> set k3 asd
OK //这些数据都存到哪些实例了呢?
127.0.0.1:22121> set 1 zhangsan
OK
127.0.0.1:22121>
然后连接redis6379和redis6380接口,并查看各自实例的元素:
####################################### 6379 ####################################
[root@z8524210 ~]# redis-cli -p 6379
127.0.0.1:6379> KEYS *
1) "1" //6379里面有一个 1 元素
127.0.0.1:6379>
####################################### 6380 ####################################
[root@z8524210 ~]# redis-cli -p 6380
127.0.0.1:6380> KEYS *
1) "k1"
2) "k3" //6380里面有 k1,k2,k3三个元素
3) "k2"
127.0.0.1:6380>
但是因为数据分治,所以不支持聚合操作:
127.0.0.1:22121> KEYS *
Error: Server closed the connection //聚合操作会报错
127.0.0.1:22121> WATCH k1
Error: Server closed the connection
127.0.0.1:22121> MULTI
Error: Server closed the connection
127.0.0.1:22121>
predixy操作演示
Github地址:https://github.com/joyieldInc/predixy
Predixy可以在所有主流平台下编译,推荐在linux下使用,需要支持C++11的编译器。所以直接下载别人编译好的程序。
[root@z8524210 ~]# mkdir predixy
[root@z8524210 ~]# cd predixy/
[root@z8524210 ~]# wget https://github.com/joyieldInc/predixy/releases/download/1.0.5/predixy-1.0.5-bin-amd64-linux.tar.gz
[root@z8524210 predixy]# cd predixy/ //下载完之后进入predixy目录
下载完之后进入predixy目录:
[root@z8524210 predixy]# ll
总用量 36
drwxr-xr-x. 2 root root 21 6月 11 17:47 bin
drwxr-xr-x. 2 root root 178 6月 11 18:03 conf
-rw-r--r--. 1 root root 26 6月 11 17:42 _config.yml
drwxr-xr-x. 3 root root 112 6月 11 17:42 doc
-rw-r--r--. 1 root root 1537 6月 11 17:42 LICENSE
-rw-r--r--. 1 root root 274 6月 11 17:42 Makefile
-rw-r--r--. 1 root root 5680 6月 11 17:42 README_CN.md
-rw-r--r--. 1 root root 4200 6月 11 17:42 README.md
drwxr-xr-x. 2 root root 4096 6月 11 17:43 src
drwxr-xr-x. 2 root root 39 6月 11 17:42 test
/**
* 可以看到有可执行程序的bin目录,里面只有一个predixy可执行程序。
* 还有conf配置文件目录,里面是很多配置文件
*/
进入到conf目录:
[root@z8524210 predixy]# cd conf/
[root@z8524210 conf]# ll
总用量 36
-rw-r--r--. 1 root root 2395 6月 11 17:42 auth.conf
-rw-r--r--. 1 root root 1041 6月 11 17:42 cluster.conf //cluster配置文件
-rw-r--r--. 1 root root 3426 6月 11 17:42 command.conf
-rw-r--r--. 1 root root 781 6月 11 17:42 dc.conf
-rw-r--r--. 1 root root 2121 6月 11 17:42 latency.conf
-rw-r--r--. 1 root root 2544 6月 11 17:56 predixy.conf //主配置文件
-rw-r--r--. 1 root root 1849 6月 11 18:03 sentinel.conf //哨兵配置文件
-rw-r--r--. 1 root root 2016 6月 11 17:42 standalone.conf
-rw-r--r--. 1 root root 98 6月 11 17:42 try.conf
[root@z8524210 conf]#
vi predixy.conf,需要设置的地方主要有两个:
GENERAL模块:Bind 127.0.0.1:7617 //设置对外暴露的统一接口
SERVERS模块:Include sentinel.conf //引入哨兵配置文件
设置好predixy.conf文件后,继续修改哨兵的配置文件vi sentinel.conf:
SentinelServerPool {
Databases 16
Hash crc16
HashTag "{}"
Distribution modula
MasterReadPriority 60
StaticSlaveReadPriority 50
DynamicSlaveReadPriority 50
RefreshInterval 1
ServerTimeout 1
ServerFailureLimit 10
ServerRetryTimeout 1
KeepAlive 120
Sentinels {
+ 127.0.0.1:26379 //三个哨兵的 IP+端口
+ 127.0.0.1:26380
+ 127.0.0.1:26381
}
Group ooxx { //一套哨兵可以监控多套主从,一个Group为一套主从。
} //Client写入的数据会被打散分别存入ooxx主从的master和xxoo主从的master。
Group xxoo {
}
}
编写三个哨兵的配置文件:
[root@z8524210 test]# vi 26379.conf
port 26379
sentinel monitor ooxx 127.0.0.1 36379 2 //第一套主从ooxx的master ID+ Port,组为ooxx
sentinel monitor xxoo 127.0.0.1 46379 2 //第二套主从xxoo的master ID+ Port 组为xxoo
[root@z8524210 test]# vi 26379.conf
port 26380
sentinel monitor ooxx 127.0.0.1 36379 2
sentinel monitor xxoo 127.0.0.1 46379 2
[root@z8524210 test]# vi 26379.conf
port 26381
sentinel monitor ooxx 127.0.0.1 36379 2
sentinel monitor xxoo 127.0.0.1 46379 2
在三个窗口分别启动哨兵:
[root@z8524210 test]# redis-server 26379.conf --sentinel
[root@z8524210 test]# redis-server 26380.conf --sentinel
[root@z8524210 test]# redis-server 26381.conf --sentinel
建立四个个文件夹36379、36380、46379、46380,分别进去启动对应的redis
//开启两个主机
[root@z8524210 36379]# redis-server --port 36379
[root@z8524210 46379]# redis-server --port 46379
//开启两个从机,追随对应的主机
[root@z8524210 36380]# redis-server --port 36380 --replicaof 127.0.0.1 36379
[root@z8524210 46380]# redis-server --port 46380 --replicaof 127.0.0.1 46379
启动predixy代理:
[root@z8524210 bin]# ./predixy ../conf/predixy.conf
2020-06-11 19:38:06.154417 N Proxy.cpp:112 predixy listen in 127.0.0.1:7617
2020-06-11 19:38:06.154595 N Proxy.cpp:143 predixy running with Name:PredixyExample Workers:1
2020-06-11 19:38:06.154953 N Handler.cpp:456 h 0 create connection pool for server 127.0.0.1:26380
2020-06-11 19:38:06.155035 N ConnectConnectionPool.cpp:42 h 0 create server connection 127.0.0.1:26380 5
2020-06-11 19:38:06.155150 N Handler.cpp:456 h 0 create connection pool for server 127.0.0.1:26381
2020-06-11 19:38:06.155172 N ConnectConnectionPool.cpp:42 h 0 create server connection 127.0.0.1:26381 6
2020-06-11 19:38:06.155212 N Handler.cpp:456 h 0 create connection pool for server 127.0.0.1:26379
2020-06-11 19:38:06.155225 N ConnectConnectionPool.cpp:42 h 0 create server connection 127.0.0.1:26379 7
2020-06-11 19:38:06.156206 N StandaloneServerPool.cpp:422 sentinel server pool group ooxx create master server 127.0.0.1:36379
2020-06-11 19:38:06.156221 N StandaloneServerPool.cpp:472 sentinel server pool group ooxx create slave server 127.0.0.1:36380
2020-06-11 19:38:06.156226 N StandaloneServerPool.cpp:422 sentinel server pool group xxoo create master server 127.0.0.1:46379
2020-06-11 19:38:06.156228 N StandaloneServerPool.cpp:472 sentinel server pool group xxoo create slave server 127.0.0.1:46380
/**
* 可以从日志中看到,对外暴露了7617的统一访问接口。
* 以及3个哨兵的 IP + Port
* 还有两队主从集群
*/
往7617统一接口中存入key,然后验证存入的key具体存入哪个实例。
/**
* 往7617统一接口中,存入k1和k2和hash tag 的key
*/
[root@z8524210 ~]# redis-cli -p 7617
127.0.0.1:7617> set k1 sdasd
OK
127.0.0.1:7617> get k1
"sdasd"
127.0.0.1:7617> set k2 aaaa
OK
127.0.0.1:7617> set {xx}k1 dasdas
OK
127.0.0.1:7617> set {xx}k2 dasdas
OK
127.0.0.1:7617> set {xx}k3 dasdas
OK
/**
* 连接36379 mastr ,查看数据
*/
[root@z8524210 ~]# redis-cli -p 36379
127.0.0.1:36379> KEYS *
1) "k1"
127.0.0.1:36379>
//能够发现k1和k2分别存入了36379和46379两个master中
/**
* 连接46379 mastr ,查看数据,发现hash tag 都存到了这个主机里
*/
[root@z8524210 ~]# redis-cli -p 46379
127.0.0.1:46379> KEYS *
1) "{xx}k2"
2) "k2"
3) "{xx}k3"
4) "{xx}k1"
127.0.0.1:46379>
但是因为分治的原因,不能实现聚合操作,除非配置文件中只有一套主从(单group):
127.0.0.1:7617> WATCH {xx}k1
(error) ERR forbid transaction in current server pool
127.0.0.1:7617>
当ooxx的36379 master挂了后,哨兵会发现,然后36380会成为新的master:
127.0.0.1:7617> set k5 dsd
OK
127.0.0.1:7617>
[root@z8524210 ~]# redis-cli -p 36380
127.0.0.1:36380> KEYS *
1) "k5"
2) "k1"
127.0.0.1:36380>
代理就是解耦 后端的复杂度,使用的时候比较舒适!
cluster操作演示
先去使用测试脚本感受一下:
[root@z8524210 ~]# cd /soft/redis-5.0.4/utils/create-cluster
[root@z8524210 create-cluster]# vi create-cluster //查看脚本
create-cluster脚本(部分):
#!/bin/bash
# Settings
PORT=30000
TIMEOUT=2000
NODES=6 //表示一共有6个节点
REPLICAS=1 //有三个slave节点
启动6个节点:
[root@z8524210 create-cluster]# ./create-cluster start
Starting 30001
Starting 30002
Starting 30003
Starting 30004
Starting 30005
Starting 30006
[root@z8524210 create-cluster]#
创建cluster集群:
[root@z8524210 create-cluster]# ./create-cluster create
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460 //这里显示有三个主,第一个master有0-5460个槽位
Master[1] -> Slots 5461 - 10922 // 第二个master有5461-10922个槽位
Master[2] -> Slots 10923 - 16383 // 第三个master有10923-16383个槽位
Adding replica 127.0.0.1:30005 to 127.0.0.1:30001 //123位master,456为slave
Adding replica 127.0.0.1:30006 to 127.0.0.1:30002
Adding replica 127.0.0.1:30004 to 127.0.0.1:30003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 604529304a6ff642659ca98c0772ae97df3ca57e 127.0.0.1:30001
slots:[0-5460] (5461 slots) master
M: daf084af642bb6369eb0adb72463eb6c2fdcd240 127.0.0.1:30002
slots:[5461-10922] (5462 slots) master
M: e55845c60c3039d776ae7fb4a8c5cb73cf0117da 127.0.0.1:30003
slots:[10923-16383] (5461 slots) master
S: c647248988b60acaddd535a57c6e6ddaa4e37728 127.0.0.1:30004
replicates 604529304a6ff642659ca98c0772ae97df3ca57e
S: 34e7bc777d6ccf28e5133ff138217ad1282a6db7 127.0.0.1:30005
replicates daf084af642bb6369eb0adb72463eb6c2fdcd240
S: bd8d511ce888966443720b1ad453b8c4edc1abc3 127.0.0.1:30006
replicates e55845c60c3039d776ae7fb4a8c5cb73cf0117da
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:30001)
M: 604529304a6ff642659ca98c0772ae97df3ca57e 127.0.0.1:30001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: bd8d511ce888966443720b1ad453b8c4edc1abc3 127.0.0.1:30006
slots: (0 slots) slave
replicates e55845c60c3039d776ae7fb4a8c5cb73cf0117da
S: 34e7bc777d6ccf28e5133ff138217ad1282a6db7 127.0.0.1:30005
slots: (0 slots) slave
replicates daf084af642bb6369eb0adb72463eb6c2fdcd240
S: c647248988b60acaddd535a57c6e6ddaa4e37728 127.0.0.1:30004
slots: (0 slots) slave
replicates 604529304a6ff642659ca98c0772ae97df3ca57e
M: daf084af642bb6369eb0adb72463eb6c2fdcd240 127.0.0.1:30002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: e55845c60c3039d776ae7fb4a8c5cb73cf0117da 127.0.0.1:30003
slots:[10923-16383] (5461 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.
[root@z8524210 create-cluster]#
以普通Client存入数据会报错:
[root@z8524210 create-cluster]# redis-cli -p 30001
127.0.0.1:30001> KEYS *
(empty list or set)
127.0.0.1:30001> set k1 dasd
(error) MOVED 12706 127.0.0.1:30003 //报错,提示要移动到30003上才能创建,应该用cluster的方式进入客户端
127.0.0.1:30001>
用cluster的方式进入Client:
[root@z8524210 create-cluster]# redis-cli -c -p 30001
127.0.0.1:30001> set k1 aaaa
-> Redirected to slot [12706] located at 127.0.0.1:30003 //存入k1的时候会跳转到30003客户端
OK
127.0.0.1:30003> set k2 cccc
-> Redirected to slot [449] located at 127.0.0.1:30001 //存入k2的时候会跳转到30001客户端
OK
127.0.0.1:30001> set k3 cccc //k3计算后,就在30001客户端,所以没有跳转
OK
127.0.0.1:30001> set {xx}k1 asds
-> Redirected to slot [15983] located at 127.0.0.1:30003 //hash tag的方式可以让key存入同一个节点
OK
127.0.0.1:30003> set {xx}k2 asds
OK
127.0.0.1:30003> set {xx}k3 asds
OK
127.0.0.1:30003> set {xx}k4 asds
OK
127.0.0.1:30003> WATCH k2 //非hash tag聚合操作会报错
-> Redirected to slot [449] located at 127.0.0.1:30001
OK
127.0.0.1:30001> MULTI
OK
127.0.0.1:30001> get k1
-> Redirected to slot [12706] located at 127.0.0.1:30003
"dasd"
127.0.0.1:30003> set k5 dasd
OK
127.0.0.1:30003> EXEC
(error) ERR EXEC without MULTI //报错了
127.0.0.1:30003> WATCH {xx}k1 //使用hash tag就能实现聚合操作,因为数据都在一个实例里
OK
127.0.0.1:30003> MULTI
OK
127.0.0.1:30003> get {xx}k2
QUEUED
127.0.0.1:30003> get {xx}k3
QUEUED
127.0.0.1:30003> set {xx}k4 dasda
QUEUED
127.0.0.1:30003> exec
1) "asds"
2) "asds"
3) OK
127.0.0.1:30003>
测试脚本的命令:
[root@z8524210 create-cluster]# ./create-cluster satrt //启动节点
[root@z8524210 create-cluster]# ./create-cluster create //创建集群
[root@z8524210 create-cluster]# ./create-cluster stop //停止集群和节点
[root@z8524210 create-cluster]# ./create-cluster clean //清理数据文件
/**
* 在真实环境中,可以把satrt的步骤 换成 依次启动我们自己的集群节点。
* create启动集群的步骤,可以使用客户端命令启动
*/
真实使用:
先查看帮助命令
[root@z8524210 create-cluster]# redis-cli --cluster help
Cluster Manager Commands:
create host1:port1 ... hostN:portN
--cluster-replicas <arg>
check host:port
--cluster-search-multiple-owners
info host:port
fix host:port
--cluster-search-multiple-owners
reshard host:port
--cluster-from <arg>
--cluster-to <arg>
--cluster-slots <arg>
--cluster-yes
--cluster-timeout <arg>
--cluster-pipeline <arg>
--cluster-replace
rebalance host:port
--cluster-weight <node1=w1...nodeN=wN>
--cluster-use-empty-masters
--cluster-timeout <arg>
--cluster-simulate
--cluster-pipeline <arg>
--cluster-threshold <arg>
--cluster-replace
add-node new_host:new_port existing_host:existing_port
--cluster-slave
--cluster-master-id <arg>
del-node host:port node_id
call host:port command arg arg .. arg
set-timeout host:port milliseconds
import host:port
--cluster-from <arg>
--cluster-copy
--cluster-replace
help
For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
[root@z8524210 create-cluster]#
模拟启动6个节点:
[root@z8524210 create-cluster]# ./create-cluster start
Starting 30001
Starting 30002
Starting 30003
Starting 30004
Starting 30005
Starting 30006
/**
* 手工启动的redis实例需要在配置文件中开启 cluster-enabled yes
*/
使用客户端手工创建集群:
[root@z8524210 create-cluster]# redis-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas 1
移动槽位:
[root@z8524210 create-cluster]# redis-cli --cluster reshard 127.0.0.1:30001
>>> Performing Cluster Check (using node 127.0.0.1:30001)
M: 07a92516271c6c748d89f2b8adaace50f3385d46 127.0.0.1:30001 //30001的ID
slots:[2000-5460] (3461 slots) master
1 additional replica(s)
S: b1c430bce3b79b4d8959b9b4f575a723f2fce84c 127.0.0.1:30006
slots: (0 slots) slave
replicates 2089c5c063fda989cfbbc83171654bcde79a98d9
S: 774dd32475129b76c06c512cd2d1316e507970a3 127.0.0.1:30005
slots: (0 slots) slave
replicates 76be6e16cbdd1e489672a3d63de0e4914347ec9d
S: 20c688e9a5151d149c1c052ad801a8fff971ef34 127.0.0.1:30004
slots: (0 slots) slave
replicates 07a92516271c6c748d89f2b8adaace50f3385d46
M: 76be6e16cbdd1e489672a3d63de0e4914347ec9d 127.0.0.1:30002 //30002的ID
slots:[0-1999],[5461-10922] (7462 slots) master
1 additional replica(s)
M: 2089c5c063fda989cfbbc83171654bcde79a98d9 127.0.0.1:30003 //30003的ID
slots:[10923-16383] (5461 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)? 2000
What is the receiving node ID? 07a92516271c6c748d89f2b8adaace50f3385d46 //这里添加新节点的ID
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots. // all表示从所有节点中抽取
Type 'done' once you entered all the source nodes IDs. //done 表示结束
Source node #1: 76be6e16cbdd1e489672a3d63de0e4914347ec9d // 输入30002的ID,表示从30002中移动槽位
Source node #2: done //结束,按回车,开始移动槽位
....
Do you want to proceed with the proposed reshard plan (yes/no)? yes //输入yes表示赞同,回车,等待移动完成
查看槽位:
[root@z8524210 create-cluster]# redis-cli --cluster info 127.0.0.1:30001 //随便选一个节点的ip端口
127.0.0.1:30001 (07a92516...) -> 0 keys | 5461 slots | 1 slaves. //之前30001有7000多槽位,现在变为5000了
127.0.0.1:30002 (76be6e16...) -> 0 keys | 5462 slots | 1 slaves. //之前30002有3000多槽位,现在变为5000了
127.0.0.1:30003 (2089c5c0...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
[root@z8524210 create-cluster]#
查看节点的详情:
[root@z8524210 create-cluster]# redis-cli --cluster check 127.0.0.1:30001 //随便选一个集群中节点的ip和端口
戏入人生
整理完毕!