1、集群原理简介1.1、什么是集群?什么是分区?
集群简单的说就是将同一个服务部署在不同的机器上,从而提高服务的横向扩展能力。
分区就是将数据分布在多个实例(服务器)上,让每一个实例都只存储一部分数据,从而达到增大总的存储数据量的效果。
1.2、为什么要实用集群?
为什么要实用集群呢?主从+哨兵模式不是已经很好了吗,已经高可用了吗?
但是主从+哨兵虽然解决了高可用问题,但是没有解决数据分区存储问题。因为我们存储的数据量大小取决于主服务器的存储容量。那么集群模式将数据分区存储就是为了实现数据存储量可以横向扩展。
1.3、数据分区的优点与缺点?
优点
缺点
一般按照分区键(ID)进行分区,分区方式一般有范围分区和Hash分区方式。
1.4.1、范围分区
根据ID数字额范围分区,比如:1-10000,100001-20000...90001-100000。每个范围分到不同得Redis实例中。
优点
实现简单,方便迁移和扩展
缺点
数据分布不均匀,可以少数节点占据了大多数的数据。性能损失比较严重。
非数字型的key,比如UUID就无法使用,当然,可以使用雪花算法代替ID的生成。雪花算法是数值且能够排序。
1.4.2、Hash分区
利用简单的hash算法就可以,比如根据key求hash值,对Redis实例的总数执行取模操作,从而计算落在哪个Redis实例上。
优点
支持所有key类型, key分布均匀, 性能比较好
缺点
迁移复杂,需要重新计算,扩展较差(但是可以利用一致性hash环拓展)。 而Hash算法在客户端链接服务端时,被广泛使用(比如:JedisPool)。
1.5、客户端分区
对于给定一个key, 客户端可以直接选择正确的节点进行读写。许多客户端都实现了客户端分区(比如:RedisPool),当然,也可以自己编码是现实。
客户端有很多种计算key的落地到那个Redis实例的算法,这个时最普通的hash算法。
1.5.1、普通hash算法
计算公式:hash(key)%N;hash:可以采用hash算法,比如CRC32、CRC16等;N:是Redis主机个数。
优点
数据分布均匀,实现简单
缺点
节点数量固定,扩展的话需要重新计算hash。
查询必须采用分片的key来查询,一旦key变化了,数据就查询不出来了,所以不要轻易改变key的分区。
1.5.1、一致性hash算法1.5.1.1、算法简介
普通hash是对主机数量取模,而一致性hash是对2^32(4 294 967 296)取模(一个足够大的hash表)。
我们把2^32想象成一个圆,就像钟表一样,钟表的圆可以理解成由60个点组成的圆,而此处我们把这个圆想象成由2^32个点组成的圆,示意图如下:
圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1 。我们把这个由2的32次方个点组成的圆环称为hash环。
假设我们有3台缓存服务器(A/B/C),使用它们各自的IP地址进行哈希计算,使用哈希后的结果对2^32取模,可以使用如下公式:
hash("服务器的IP地址") % 2^32
通过上述公式算出的结果一定是一个0到2^32-1之间的一个整数,我们就用算出的这个整数,分别代表服务器(A/B/C).
既然这个整数肯定处于0到2^32-1之间,那么,上图中的hash环上必定有一个点与这个整数对应,也就是服务器A/B/C就可以映射到这个环上,如下图:
假设,我们需要使用Redis缓存数据,那么我们使用如下公式可以将数据映射到上图中的hash环上
hash(key) % 2^32
现在服务器与数据都被映射到了hash环上,上图中的数据将会被缓存到服务器A上。
因为从数据的开始位置,沿顺时针方向遇到的第一个服务器就是A服务器,所以,上图中的数据将会被缓存到服务器A上。
1.5.1.2、一致性hash算法有缺点
优点
添加或移除节点时,数据只需要做部分的迁移,比如上图中把C服务器移除,则数据4迁移到服务器A中,而其他的数据保持不变。
缺点
数据分布不均匀,可能出现所有的key都被hash到同一个节点上了,折中现象叫做hash环偏移。
理论上我们可以增加服务器数量来减少便宜,但是这样成本太高了。所以通过增加虚拟节点来处理。
1.5.1.3、一致性hash虚拟节点
"虚拟节点"是"实际节点"(实际的物理服务器)在hash环上的复制品,一个实际节点可以对应多个虚拟节点。
从上图可以看出,A、B、C三台服务器分别虚拟出了一个虚拟节点,当然,如果你需要,也可以虚拟出更多的虚拟节点。
引入虚拟节点的概念后,缓存的分布就均衡多了,上图中,1和3号数据被缓存在服务器A中,4和5号数据被缓存在服务器B中,2和6号数据被缓存在服务器C中.
如果你还不放心,可以虚拟出更多的虚拟节点,以便减小hash环偏斜所带来的影响,虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大。
一般来说,一致性hash算法+虚拟节点就是一个很好的方案了。
1.5.1.4、一致性hash算法实现
一致性hash算法(无虚拟节点)
public class HashDemo { public static void mAIn(String[] args) { //step1 初始化:把服务器节点IP的哈希值对应到哈希环上 // 定义服务器ip String[] servers = new String[]{"192.168.222.101", "192.168.222.102", "192.168.222.103"}; // 创建一个排序的hashMap,key存储hash值,value存储服务器IP地址,并按照Hash值排序 SortedMap hashServerMap = new TreeMap<>(); for (String redisServer : servers) { // 求出每⼀个ip的hash值,对应到hash环上,存储hash值与ip的对应关系 int serverHash = Math.abs(redisServer.hashCode()); // 存储hash值与ip的对应关系 hashServerMap.put(serverHash, redisServer); } //step2 针对客户端IP求出hash值 // 定义客户端传递过来的RedisKey String[] redisKeys = new String[]{"user:001:name", "user:001:age", "user:001:sex"}; for (String redisKey : redisKeys) { // 计算redisKey的hash值 int redisKeyHash = Math.abs(redisKey.hashCode()); //step3 针对客户端,找到能够处理当前RedisKey的服务器(哈希环上顺时针最近) // 根据redisKey的哈希值去找出哪⼀个服务器节点能够处理 // tailMap返回此映射的键大于或等于fromKey的部分,也就是比redisKey的hash值大的排序列表,取第一个就是最近的服务器节点 SortedMap filteredSortedMap = hashServerMap.tailMap(redisKeyHash); // 获取key落到那台服务器,filteredSortrdMap为空,直接取服务器列表hashServerMap第一个,不为空,则取出最近一个filteredSortrdMap Integer hashKey = filteredSortedMap.isEmpty() ? hashServerMap.firstKey() : filteredSortedMap.firstKey(); System.out.println("==========>>>>RedisKey:" + redisKey + " 被路由到服务器:" + hashServerMap.get(hashKey)); } } }
运行结果
==========>>>>RedisKey:user:001:name 被路由到服务器:192.168.222.101 ==========>>>>RedisKey:user:001:age 被路由到服务器:192.168.222.101 ==========>>>>RedisKey:user:001:sex 被路由到服务器:192.168.222.101
可以看出,Redis的key被路由到同一个节点了,我们使用增加虚拟节点来避免折中情况。
一致性hash算法(有虚拟节点)
public class HashDemo2 { public static void main(String[] args) { //step1 初始化:把服务器节点IP的哈希值对应到哈希环上 // 定义服务器ip String[] servers = new String[]{"192.167.222.101", "192.168.222.103", "191.169.222.123"}; // 创建一个排序的hashMap,key存储hash值,value存储服务器IP地址,并按照Hash值排序 SortedMap hashServerMap = new TreeMap<>(); // 定义针对每个真实服务器虚拟出来⼏个节点 int virtualCount = 3; for (String redisServer : servers) { // 求出每⼀个ip的hash值,对应到hash环上,存储hash值与ip的对应关系 int serverHash = Math.abs(redisServer.hashCode()); // 存储hash值与ip的对应关系 hashServerMap.put(serverHash, redisServer); // 处理虚拟节点 for(int i = 0; i < virtualCount; i++) { int virtualHash = Math.abs((redisServer + "#" + i).hashCode()); hashServerMap.put(virtualHash,"----由虚拟节点"+ i + "映射过来的请求:"+ redisServer); } } //step2 针对客户端IP求出hash值 // 定义客户端传递过来的RedisKey String[] redisKeys = new String[]{ "user:001:name", "order:001:name", "product:001:name", "user:002:name", "order:002:name", "product:002:name", }; for (String redisKey : redisKeys) { // 计算redisKey的hash值 int redisKeyHash = Math.abs(redisKey.hashCode()); //step3 针对客户端,找到能够处理当前RedisKey的服务器(哈希环上顺时针最近) // 根据redisKey的哈希值去找出哪⼀个服务器节点能够处理 // tailMap返回此映射的键大于或等于fromKey的部分,也就是比redisKey的hash值大的排序列表,取第一个就是最近的服务器节点 SortedMap filteredSortedMap = hashServerMap.tailMap(redisKeyHash); // 获取key落到那台服务器,filteredSortrdMap为空,直接取服务器列表hashServerMap第一个,不为空,则取出最近一个filteredSortrdMap Integer hashKey = filteredSortedMap.isEmpty() ? hashServerMap.firstKey() : filteredSortedMap.firstKey(); System.out.println("==========>>>>RedisKey:" + redisKey + " 被路由到服务器:" + hashServerMap.get(hashKey)); } } }
运行结果
==========>>>>RedisKey:user:001:name 被路由到服务器:192.167.222.101 ==========>>>>RedisKey:order:001:name 被路由到服务器:191.169.222.123 ==========>>>>RedisKey:product:001:name 被路由到服务器:191.169.222.123 ==========>>>>RedisKey:user:002:name 被路由到服务器:----由虚拟节点2映射过来的请求:191.169.222.123 ==========>>>>RedisKey:order:002:name 被路由到服务器:191.169.222.123 ==========>>>>RedisKey:product:002:name 被路由到服务器:191.169.222.123
可以看出,虚拟节点确实生效了。
2、Redis集群
Redis3.0之后,Redis官方提供了完整的集群解决方案,称为Rediscluster。
Redis集群方案采用去中心化的方式,包括:sharding(分区)、replication(复制)、failover(故障转移)。
Redis5.0前采用redis-trib进行集群的创建和管理,需要ruby支持。Redis5.0可以直接使用Redis-cli进行集群的创建和管理。
2.1、Redis集群部署架构
Redis的部署架构时没有中心的,每个节点都是主节点,是一个P2P(点对点)的去中心化集群架构,依靠gossip协议用于集群间传播。
2.2、Gossip协议
Gossip协议是一个通信协议,一种传播消息的方式,它起源于病毒传播。
Gossip协议基本思想
一个节点周期性(每秒)随机选择一些节点,并把信息传递给这些节点。
这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。
信息会周期性的传递给N个目标节点。这个N被称为fanout(扇出)。
gossip协议包含多种消息,包括meet、ping、pong、fail、publish等等。
通过gossip协议,cluster可以提供集群间状态同步更新、自动选举,故障转移(failover)等重要的集群功能.
2.3、Redis的hash槽
redis-cluster把所有的物理节点映射到[0-16383]个slot上,基本上采用平均分配和连续分配的方式。
比如部署架构图中有5个主节点,这样在RedisCluster创建时,slot槽可按如下分配:
Redis1 0-3270, Redis2 3271-6542, Redis3 6543-9814, Redis4 9815-13087, Redis5 13088-16383
cluster负责维护节点和slot槽的对应关系 value------>slot-------->节点
当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。
2.5、RedisCluster的优点
生产环境中的Redis服务器最少三台主服务器,三台从服务器。这里由于条件有限,在同一台机器上处理,也就是实现伪分布式集群。
下载Redis
下载地址:https://download.redis.io/releases/redis-5.0.0.tar.gz
安在Redis单机版
请查看<> 中,地址为:https://www.toutiao.com/article/7140648630402204168
准备工作
# 创建redis集群文件夹 mkdir -p /opt/redis/redis_cluster # 复制一份编译好的Redis命令 cp -r /opt/redis/baseredis /opt/redis/redis_cluster/redis1 # 复制配置文件 cp /opt/redis/redis-5.0.0/redis.conf /opt/redis/redis_cluster/redis1/redis.conf # 复制六份,分别修改配置文件 cp -r redis1 redis2 ; cp -r redis1 redis3 ; cp -r redis1 redis4 ; cp -r redis1 redis5 ; cp -r redis1 redis6
redis.conf配置文件修改
##################################.NETWORK ##################################### # 注释掉bind 127.0.0.1,不然ip地址只能使用127.0.0.1访问 # bind 127.0.0.1 # 端口号(需要修改) port 7001 tcp-backlog 511 timeout 0 tcp-keepalive 300 ################################# GENERAL ##################################### # 开启后台启动(修改为yes) daemonize yes supervised no # 进程ID(修改为对应端口号的pid) pidfile /var/run/redis_7001.pid loglevel notice logfile "" databases 16 always-show-logo yes ################################ SNAPSHOTTING ################################ save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbchecksum yes dbfilename dump.rdb dir ./ ################################# REPLICATION ################################# # 主从复制 # replicaof # 连接主服务器认证密码(修改为集群的密码,每台机器都一样) masterauth abcAbc123. replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-disable-tcp-nodelay no # By default the priority is 100. replica-priority 100 ################################## SECURITY ################################### # 认证密码(修改为集群的密码,每台机器都一样) requirepass abcAbc123. ############################# LAZY FREEING #################################### lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no ############################## AppEND ONLY MODE ############################### # 是否开启AOF(修改为yes, 开启aof) appendonly yes # 开启aof后的文件名 appendfilename "appendonly.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 aof-use-rdb-preamble yes ################################ LUA SCRIPTING ############################### # Set it to 0 or a negative value for unlimited execution without warnings. lua-time-limit 5000 ################################ REDIS CLUSTER ############################### # 是否开启集群(修改为yes,开启集群功能) cluster-enabled yes # 集群节点的配置文件(修改为对应端口号的集群端口号对用节点) cluster-config-file nodes-7001.conf # 节点间通信超时时间 cluster-node-timeout 15000 ################################## SLOW LOG ################################### slowlog-max-len 128 ################################ LATENCY MONITOR ############################## latency-monitor-threshold 0 ############################# EVENT NOTIFICATION ############################## 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 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes
同样的配置复制6份,修改为端口号相关的东西,比如端口号,进程ID文件,集群节点名称等。可以使用以上配置批量替换
vi编辑器中执行
# 将7001替换成7002 /g表示全部替换 :%s/7001/7002/g
启动Redis
cd /opt/redis/redis_cluster/redis1/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis2/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis3/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis4/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis5/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis6/ && ./bin/redis-server redis.conf
查看redis启动情况
[root@VM-0-5-centos redis6]# ps -ef |grep redis root 1485 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7001 [cluster] root 1604 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7002 [cluster] root 1606 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7003 [cluster] root 1611 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7004 [cluster] root 1617 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7005 [cluster] root 1630 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7006 [cluster]
开启防火墙
firewall-cmd --zone=public --add-port=7001/tcp --permanent firewall-cmd --zone=public --add-port=7002/tcp --permanent firewall-cmd --zone=public --add-port=7003/tcp --permanent firewall-cmd --zone=public --add-port=7004/tcp --permanent firewall-cmd --zone=public --add-port=7005/tcp --permanent firewall-cmd --zone=public --add-port=7006/tcp --permanent firewall-cmd --zone=public --add-port=17001/tcp --permanent firewall-cmd --zone=public --add-port=17002/tcp --permanent firewall-cmd --zone=public --add-port=17003/tcp --permanent firewall-cmd --zone=public --add-port=17004/tcp --permanent firewall-cmd --zone=public --add-port=17005/tcp --permanent firewall-cmd --zone=public --add-port=17006/tcp --permanent systemctl restart firewalld.service
放行云服务器的防火墙端口(7001-7006,17001-17006)于本机公网IP
开启集群(公网IP,指定密码)
/opt/redis/redis_cluster/redis1/bin/redis-cli --cluster create 162.14.74.11:7001 162.14.74.11:7002 162.14.74.11:7003 162.14.74.11:7004 162.14.74.11:7005 162.14.74.11:7006 --cluster-replicas 1 -a abcAbc123.
加入集群结果
[root@VM-0-5-centos redis6]# /opt/redis/redis_cluster/redis1/bin/redis-cli --cluster create > 162.14.74.11:7001 > 162.14.74.11:7002 > 162.14.74.11:7003 > 162.14.74.11:7004 > 162.14.74.11:7005 > 162.14.74.11:7006 > --cluster-replicas 1 -a abcAbc123. Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 162.14.74.11:7004 to 162.14.74.11:7001 Adding replica 162.14.74.11:7005 to 162.14.74.11:7002 Adding replica 162.14.74.11:7006 to 162.14.74.11:7003 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 95bb2b273537ec44879178d80fec968a4a02d151 162.14.74.11:7001 slots:[0-5460] (5461 slots) master M: 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 162.14.74.11:7002 slots:[5461-10922] (5462 slots) master M: 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 162.14.74.11:7003 slots:[10923-16383] (5461 slots) master S: b7cefab8fbce66cf7784b52a18c1d07fbc485e8d 162.14.74.11:7004 replicates 95bb2b273537ec44879178d80fec968a4a02d151 S: 9d6cc857350689586336dfd6d82f0fbb41dd8450 162.14.74.11:7005 replicates 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 S: 830c13e63211ef4c4dc2668581895f78b45ff06c 162.14.74.11:7006 replicates 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 Can I set the above configuration? (type 'yes' to accept): yes # 这里输入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 162.14.74.11:7001) M: 95bb2b273537ec44879178d80fec968a4a02d151 162.14.74.11:7001 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 830c13e63211ef4c4dc2668581895f78b45ff06c 162.14.74.11:7006 slots: (0 slots) slave replicates 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 M: 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 162.14.74.11:7003 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 162.14.74.11:7002 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: b7cefab8fbce66cf7784b52a18c1d07fbc485e8d 162.14.74.11:7004 slots: (0 slots) slave replicates 95bb2b273537ec44879178d80fec968a4a02d151 S: 9d6cc857350689586336dfd6d82f0fbb41dd8450 162.14.74.11:7005 slots: (0 slots) slave replicates 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 [OK] All nodes agree about slots configuration. # 加入集群成功 >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
连接Redis进行测试
# 使用集群方式连接Redis /opt/redis/redis_cluster/redis1/bin/redis-cli -h 162.14.74.11 -p 7001 -c # 使用密码认证 162.14.74.11:7001> auth abcAbc123. OK # 使用cluster nodes查询集群节点 162.14.74.11:7001> cluster nodes 830c13e63211ef4c4dc2668581895f78b45ff06c 162.14.74.11:7006@17006 slave 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 0 1662994023610 6 connected 95bb2b273537ec44879178d80fec968a4a02d151 10.0.0.5:7001@17001 myself,master - 0 1662994021000 1 connected 0-5460 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 162.14.74.11:7003@17003 master - 0 1662994022610 3 connected 10923-16383 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 162.14.74.11:7002@17002 master - 0 1662994024612 2 connected 5461-10922 b7cefab8fbce66cf7784b52a18c1d07fbc485e8d 162.14.74.11:7004@17004 slave 95bb2b273537ec44879178d80fec968a4a02d151 0 1662994022000 4 connected 9d6cc857350689586336dfd6d82f0fbb41dd8450 162.14.74.11:7005@17005 slave 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 0 1662994023000 5 connected
Redis集群搭建(Docker方式)
使用Docker集群也是一样的道理。我们使用host模式(直接使用本机IP),实现集群。
准备工作
# 创建docker方式redis集群文件夹 mkdir -p /opt/redis/docker_redis_cluster # 拷贝准备好的文件到该目录 # 如果没有安装tree 先安装 yum install tree -y
使用tree查看目录结构, 只需要创建配置文件目录进行挂载修改,data和logs会自动挂载。
. ├── docker-compose.yml ├── redis1 │ └── conf │ └── redis.conf ├── redis2 │ └── conf │ └── redis.conf ├── redis3 │ └── conf │ └── redis.conf ├── redis4 │ └── conf │ └── redis.conf ├── redis5 │ └── conf │ └── redis.conf └── redis6 └── conf └── redis.conf
再使用ls -al查询隐藏文件
[root@VM-0-5-centos docker_redis_cluster]# ls -al total 40 drwxr-xr-x 8 root root 4096 Sep 14 21:29 . drwxr-xr-x 8 root root 4096 Sep 12 22:49 .. -rw-r--r-- 1 root root 2953 Sep 14 21:29 docker-compose.yml -rw-r--r-- 1 root root 24 Sep 13 21:23 .env drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis1 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis2 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis3 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis4 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis5 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis6
可以看到我们准备了docker-compose.yml, .env, redis.conf三种文件。只需要修改.env的变量,就可以通过docker-compose实现一键集群了。
.env
# 服务器的IP地址 公网IP 注意开启防火墙端口 和 公网的安全组 SERVICE_IP=162.14.74.11 # 根目录 BASE_DIR=/opt/redis/docker_redis_cluster
编写docker-compose.yml
version: '2' services: redis_cluster: image: redis hostname: redis_cluster container_name: redis_cluster command: 'redis-cli --cluster create ${SERVER_IP}:7001 ${SERVER_IP}:7002 ${SERVER_IP}:7003 ${SERVER_IP}:7004 ${SERVER_IP}:7005 ${SERVER_IP}:7006 --cluster-yes --cluster-replicas 1 -a abcAbc123.' depends_on: - redis1 - redis2 - redis3 - redis4 - redis5 - redis6 privileged: true environment: TZ: Asia/Shanghai network_mode: host redis1: # 镜像名 image: redis # 重启策略 失败后总是重启 restart: always # 主机名 hostname: redis1 # 容器名 container_name: redis1 # 是否有权限 true privileged: true # 网络模式 host表示与宿主机使用相同的IP,docker中的IP发生变化 network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis1/data:/data - ${BASE_DIR}/redis1/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis1/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis2: image: redis restart: always hostname: redis2 container_name: redis2 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis2/data:/data - ${BASE_DIR}/redis2/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis2/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis3: image: redis restart: always hostname: redis3 container_name: redis3 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis3/data:/data - ${BASE_DIR}/redis3/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis3/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis4: image: redis restart: always hostname: redis4 container_name: redis4 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis4/data:/data - ${BASE_DIR}/redis4/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis4/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis5: image: redis restart: always hostname: redis5 container_name: redis5 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis5/data:/data - ${BASE_DIR}/redis5/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis5/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis6: image: redis restart: always hostname: redis6 container_name: redis6 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis6/data:/data - ${BASE_DIR}/redis6/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis6/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ]
redis.conf配置文件
protected-mode yes port 7001 tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize no supervised no pidfile /var/run/redis_7001.pid loglevel verbose databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir ./ masterauth abcAbc123. replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no replica-priority 100 requirepass abcAbc123. lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yes lua-time-limit 5000 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 cluster-replica-validity-factor 10 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" 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 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes
上面时原始的redis.conf去除了注释后,并修改了部分属性,修改部分如下:
# 端口号 port 7001 # 进程ID保存文件 pidfile /var/run/redis_7001.pid # 向主服务器认证的密码 masterauth abcAbc123. # 服务器自己的密码 与向主服务器认证密码保持一致 requirepass abcAbc123. # 开启appendaof模式 appendonly yes # 开启集群 cluster-enabled yes # 集群节点配置文件名称 cluster-config-file nodes-7001.conf
启动集群
cd /opt/redis/docker_redis_cluster && docker-compose up -d
测试集群
# 进入容器 docker exec -it redis1 /bin/bash # 使用集群方式连接Redis redis-cli -h 162.14.74.11 -p 7001 -c # 使用密码认证 162.14.74.11:7001> auth abcAbc123. OK # 使用cluster nodes查询集群节点 162.14.74.11:7001> cluster nodes ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663164275000 1 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663164276864 2 connected 5461-10922 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663164274000 1 connected 0-5460 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663164275861 2 connected 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663164275000 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663164273856 3 connected 10923-16383
使用Docker方式实现集群,其实跟手动实现集群是一样的,只是使用docker-compose一键实现了该操作而已。
2.3、Redis客户端分片与重定向
不同节点分组服务于相互无交集的分片(sharding),Redis Cluster不存在单独的proxy或配置服务器,所以需要将客户端路由到目标的分片。
Redis Cluster的客户端相比单机Redis需要具备路由语义的识别能力,且具备一定的路由缓存能力。
2.3.1、moved重定向
我们直到,Redis集群数据时存储在各个分片上的,如果我们连接某一个几点,但是数据没有在该节点上,将会被重定向到其他节点获取数据。流程图如下:
流程图
流程说明
命令实现
# 在7001上面设置获取key root@redis1:/data# redis-cli -h 162.14.74.11 -p 7001 -c 162.14.74.11:7001> get name # 会经过认证 (error) NOAUTH Authentication required. 162.14.74.11:7001> auth abcAbc123. OK # 获取 162.14.74.11:7001> get name # 获取时发现在7002上,重定向到7002, 但是开启了认证 所有没有获取到 -> Redirected to slot [5798] located at 162.14.74.11:7002 (error) NOAUTH Authentication required. # 进行认证 162.14.74.11:7002> auth abcAbc123. OK # 重新获取则成功 162.14.74.11:7002> get name "zhangsan"
2.3.2、ask重定向
在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移
当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息
如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制。
流程图
流程说明
Redis时可以实现动态扩缩容的,Redis扩容就是向Redis中添加节点
防火墙开通端口
firewall-cmd --zone=public --add-port=7007/tcp --permanent firewall-cmd --zone=public --add-port=17007/tcp --permanent systemctl restart firewalld.service
处理这个,还需要开启云服务器的安全组规则。能够telnet通才可以。
[root@VM-0-5-centos docker_redis_cluster]# telnet 162.14.74.11 7007 Trying 162.14.74.11... Connected to 162.14.74.11. Escape character is '^]'.
编写增加节点的docker-compose.yml
version: '2' services: redis7: image: redis restart: always hostname: redis7 container_name: redis7 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis7/data:/data - ${BASE_DIR}/redis7/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis7/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ]
执行文件
docker-compose -f addNode.yml up -d
加入集群
在任意一台机器执行如下命令
# 注意 如果设置了密码 加入节点时使用-a指定密码 redis-cli --cluster add-node 162.14.74.11:7007 162.14.74.11:7001 -a abcAbc123.
执行结果
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Adding node 162.14.74.11:7007 to cluster 162.14.74.11:7001 >>> Performing Cluster Check (using node 162.14.74.11:7001) M: d77367d7830b4503980a14e379cb06a271906787 162.14.74.11:7001 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005 slots: (0 slots) slave replicates d77367d7830b4503980a14e379cb06a271906787 M: 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006 slots: (0 slots) slave replicates 5a92ba294e35f1d61e8091216c59c2fa44adfb9d S: 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004 slots: (0 slots) slave replicates 851c1b20f0616b60f172837e7e4bce792397d8ac M: 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003 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.
同理,使用cluster nodes查看节点信息
ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663166631000 1 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663166631175 2 connected 5461-10922 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663166628000 1 connected 0-5460 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663166630173 2 connected d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007@17007 master - 0 1663166632178 0 connected 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663166631000 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663166630000 3 connected 10923-16383
可以看到,节点已经加入成功了,但是发现不像其他节点有一个connected 0-5460。这是因为还没有分配hash操作,节点上没有数据。
2.4.1、Redis数据迁移+重新分配hash槽
添加完主节点需要对主节点进行hash槽分配,这样该主节才可以存储数据。
Redis数据迁移原理
# 指定rehash重新分槽的命令 redis-cli --cluster reshard 162.14.74.11:7007 -a abcAbc123. # 输入hash槽数量,表示要分配hash槽给目标节点 How many slots do you want to move (from 1 to 16384)? 输入3000 # 输入接收槽的结点id 也就是7007节点的id 可以在前面的cluster nodes中获取 d5004e076572b8fab64c8cc4473c298396b78823 What is the receiving node ID? d5004e076572b8fab64c8cc4473c298396b78823 # 输入源结点id,这里输入all表示从其他所有节点中都分一点槽出来 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 # 输入yes开始移动槽到目标结点id Do you want to proceed with the proposed reshard plan (yes/no)? yes # 这些就是迁移日志 还有很多 只复制了两行 Moving slot 195 from 162.14.74.11:7001 to 162.14.74.11:7007: Moving slot 196 from 162.14.74.11:7001 to 162.14.74.11:7007:
使用cluster nodes查看
162.14.74.11:7001> cluster nodes ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663167613059 7 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663167610000 2 connected 7687-10922 # 7001有两端槽 因为之前迁移错误 迁移到7001去了 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663167612000 7 connected 1550-6961 10923-12421 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663167611053 2 connected # 这里7007有i狼三段槽,从7001-7003都迁移了部分过来 d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007@17007 master - 0 1663167611000 8 connected 0-1549 6962-7686 12422-13146 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663167612055 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663167608044 3 connected 13147-16383
2.4.2、Redis添加从节点
添加7008从结点,将7008作为7007的从结点
放行防火墙和云服务器安全组
firewall-cmd --zone=public --add-port=7008/tcp --permanent firewall-cmd --zone=public --add-port=17008/tcp --permanent systemctl restart firewalld.service
安全组略,需要修改腾讯云上的端口规则。
添加从节点
# redis-cli --cluster add-node 新节点的ip和端口 旧节点ip和端口 --cluster-slave --cluster-master-id 主节点id -a 密码 redis-cli --cluster add-node 162.14.74.11:7008 162.14.74.11:7007 --cluster-slave --cluster-master-id d5004e076572b8fab64c8cc4473c298396b78823 -a abcAbc123.
执行结果
root@redis1:/data# redis-cli --cluster add-node 162.14.74.11:7008 162.14.74.11:7007 --cluster-slave --cluster-master-id d5004e076572b8fab64c8cc4473c298396b78823 -a abcAbc123. Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Adding node 162.14.74.11:7008 to cluster 162.14.74.11:7007 >>> Performing Cluster Check (using node 162.14.74.11:7007) M: d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007 slots:[0-1549],[6962-7686],[12422-13146] (3000 slots) master S: ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005 slots: (0 slots) slave replicates d77367d7830b4503980a14e379cb06a271906787 S: 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004 slots: (0 slots) slave replicates 851c1b20f0616b60f172837e7e4bce792397d8ac M: 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002 slots:[7687-10922] (3236 slots) master 1 additional replica(s) M: 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003 slots:[13147-16383] (3237 slots) master 1 additional replica(s) S: 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006 slots: (0 slots) slave replicates 5a92ba294e35f1d61e8091216c59c2fa44adfb9d M: d77367d7830b4503980a14e379cb06a271906787 162.14.74.11:7001 slots:[1550-6961],[10923-12421] (6911 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. >>> Send CLUSTER MEET to node 162.14.74.11:7008 to make it join the cluster. Waiting for the cluster to join >>> Configure node as replica of 162.14.74.11:7007. [OK] New node added correctly.
使用cluster nodes查看
64b8c5798f880927b81ca09d8fc21ab2402767a5 162.14.74.11:7008@17008 slave d5004e076572b8fab64c8cc4473c298396b78823 0 1663168579000 8 connected ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663168577000 7 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663168579327 2 connected 7687-10922 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663168578000 7 connected 1550-6961 10923-12421 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663168580330 2 connected d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007@17007 master - 0 1663168579000 8 connected 0-1549 6962-7686 12422-13146 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663168579000 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663168578000 3 connected 13147-16383
2.4.3、Redis缩容
原理与扩容一样
redis-cli --cluster del-node 162.14.74.11:7008 64b8c5798f880927b81ca09d8fc21ab2402767a5 -a abcAbc123.
2.5、Redis故障转移(failover)
故障检测
集群中的每个节点都会定期地(每秒)向集群中的其他节点发送PING消息
如果在一定时间内(cluster-node-timeout),发送ping的节点A没有收到某节点B的pong回应,则A将B标识为pfail。
A在后续发送ping时,会带上B的pfail信息, 通知给其他节点。
如果B被标记为pfail的个数大于集群主节点个数的一半(N/2 + 1)时,B会被标记为fail,A向整个集群广播,该节点已经下线。其他节点收到广播,标记B为fail。
从节点选举
使用raft算法,每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。
slave通过向其他master发送FAILVOER_AUTH_REQUEST消息发起竞选,master收到后回复FAILOVER_AUTH_ACK消息告知是否同意。
slave发送FAILOVER_AUTH_REQUEST前会将currentEpoch自增,并将最新的Epoch带入到FAILOVER_AUTH_REQUEST消息中,如果自己未投过票,则回复同意,否则回复拒绝。
所有的Master开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。
RedisCluster失效的判定:
变更通知
当slave收到过半的master同意时,会成为新的master。此时会以最新的Epoch通过PONG消息广播自己成为master,让Cluster的其他节点尽快的更新拓扑结构(node.conf)
主从切换
从节点通过选举自动切换。
人工处理手动切换
如果主节点下线了,则采用cluster failover force或cluster failover takeover进行强制切换。
副本漂移
我们知道在一主一从的情况下,如果主从同时挂了,那整个集群就挂了。
为了避免这种情况我们可以做一主多从,但这样成本就增加了。
Redis提供了一种方法叫副本漂移,这种方法既能提高集群的可靠性又不用增加太多的从机。
Master1宕机,则Slaver11提升为新的Master1
集群检测到新的Master1是单点的(无从机)
集群从拥有最多的从机的节点组(Master3)中,选择节点名称字母顺序最小的从机(Slaver31)漂移到单点的主从节点组(Master1)。具体流程如下(以上图为例):
虽然Redis集群操作很简单,但是其内的原理涉及到的知识点非常全面,了解其原理,可以更好的应对线上线下的问题。