Redis分布式缓存搭建
花了两天时间整理了之前记录的Redis单体与哨兵模式的搭建与使用,又补齐了集群模式的使用和搭建经验,并对集群的一些个原理做了理解。
1、安装Redis
$ wget http://download.redis.io/releases/redis-6.0.3.tar.gz
$ tar -xzf redis-6.0.3.tar.gz
$ cd redis-6.0.3
$ make
$ make install
笔者安装中遇到的一些问题:
如果make报错,可能是没装gcc或者gcc++编辑器,安装之 yum -y install gcc gcc-c++ kernel-devel
,有可能还是提示一些个c文件编译不过,gcc -v查看下版本,如果不到5.3那么升级一下gcc:
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
在/etc/profile
追加一行 source /opt/rh/devtoolset-9/enable
scl enable devtoolset-9 bash
重新make clean, make
这回编译通过了,提示让你最好make test一下/
执行make test ,如果提示You need tcl 8.5 or newer in order to run the Redis test
那就升级tcl, yum install tcl
重新make test,如果还有error就删了目录,重新tar包解压重新make , make test
\o/ All tests passed without errors!
,表示编译成功。
然后make install即可。
2、启动Redis
直接运行命令: ./redis-server /usr/redis-6.0.3/redis.conf &
[root@VM_0_11_centos src]# ./redis-server /usr/redis-6.0.3/redis.conf &
[1] 4588
[root@VM_0_11_centos src]# 4588:C 22 May 2020 19:45:15.179 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
4588:C 22 May 2020 19:45:15.179 # Redis version=6.0.3, bits=64, commit=00000000, modified=0, pid=4588, just started
4588:C 22 May 2020 19:45:15.179 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.3 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 4588
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
4588:M 22 May 2020 19:45:15.180 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
4588:M 22 May 2020 19:45:15.180 # Server initialized
4588:M 22 May 2020 19:45:15.180 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
4588:M 22 May 2020 19:45:15.180 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
4588:M 22 May 2020 19:45:15.180 * Loading RDB produced by version 6.0.3
4588:M 22 May 2020 19:45:15.180 * RDB age 44 seconds
4588:M 22 May 2020 19:45:15.180 * RDB memory usage when created 0.77 Mb
4588:M 22 May 2020 19:45:15.180 * DB loaded from disk: 0.000 seconds
4588:M 22 May 2020 19:45:15.180 * Ready to accept connections
redis.conf
配置文件里bind 0.0.0.0
设置外部访问, requirepass xxxx
设置密码。
3、Redis高可用
redis高可用方案有两种:
-
Replication-Sentinel 主从复制+哨兵
-
cluster 集群模式
常用搭建方案为1主1从或1主2从+3哨兵监控主节点, 以及3主3从6节点集群。
(1)sentinel哨兵
/usr/redis-6.0.3/src/redis-sentinel /usr/redis-6.0.3/sentinel2.conf &
sentinel2.conf配置:
port 26380 #本哨兵的端口
daemonize yes
pidfile "/var/run/redis-sentinel2.pid" #哨兵daemonize模式需要的pid文件
logfile ""
dir "/tmp"
sentinel myid 5736b9ca22cf0899276316e71810566044d75d14
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 122.xx.xxx.xxx 6379 2 #至少2个哨兵投票选举认为master挂了
sentinel auth-pass mymaster xxxxxxx #哨兵连接master的密码
sentinel down-after-milliseconds mymaster 30000 #30秒无应答认为master挂了
sentinel failover-timeout mymaster 30000 #如果在该30秒内未能完成failover操作,则认为该failover失败
sentinel config-epoch mymaster 0
protected-mode no
user default on nopass ~* +@all
sentinel leader-epoch mymaster 0
sentinel current-epoch 0
坑1:master节点也会在故障转移后成为从节点,也需要配置masterauth
当kill master进程之后,经过sentinel选举,slave成为了新的master,再次启动原master,提示如下错误:
692:S 06 Jun 2020 13:19:35.280 * (Non critical) Master does not understand REPLCONF listening-port: -NOAUTH Authentication required.
692:S 06 Jun 2020 13:19:35.280 * (Non critical) Master does not understand REPLCONF capa: -NOAUTH Authentication required.
692:S 06 Jun 2020 13:19:35.280 * Partial resynchronization not possible (no cached master)
692:S 06 Jun 2020 13:19:35.280 # Unexpected reply to PSYNC from master: -NOAUTH Authentication required.
692:S 06 Jun 2020 13:19:35.280 * Retrying with SYNC...
692:S 06 Jun 2020 13:19:35.280 # MASTER aborted replication with an error: NOAUTH Authentication required.
692:S 06 Jun 2020 13:19:36.282 * Connecting to MASTER 127.0.0.1:7001
692:S 06 Jun 2020 13:19:36.282 * MASTER <-> REPLICA sync started
692:S 06 Jun 2020 13:19:36.282 * Non blocking connect for SYNC fired the event.
692:S 06 Jun 2020 13:19:36.282 * Master replied to PING, replication can continue...
原因是此时的master再次启动已经是slave了,需要向现在的新master输入密码,所以需要在master.conf
中配置:
masterauth xxxx #xxxx是在slave.conf中的requirepass xxxx 密码
坑2:哨兵配置文件要暴露客户端可以访问到的master地址
在sentinel.conf
配置文件的sentinel monitor mymaster 122.xx.xxx.xxx 6379 2
中,配置该哨兵对应的master名字、master地址和端口,以及达到多少个哨兵选举通过认为master挂掉。其中master地址要站在redis访问者(也就是客户端)的角度、配置访问者能访问的地址,例如sentinel与master在一台服务器(122.xx.xxx.xxx)上,那么相对sentinel其master在本机也就是127.0.0.1上,这样sentinel monitor mymaster 127.0.0.1 6379 2
逻辑上没有问题,但是如果另外服务器上的springboot通过lettuce访问这个redis哨兵,则得到的master地址为127.0.0.1,也就是springboot所在服务器本机,这显然就有问题了。
附springboot2.1 redis哨兵配置:
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=122.xx.xxx.xxx:26379,122.xx.xxx.xxx:26380,122.xx.xxx.xxx:26381
#spring.redis.host=122.xx.xxx.xxx #单机模式
#spring.redis.port=6379 #单机模式
spring.redis.timeout=6000
spring.redis.password=xxxxxx
#spring.redis.lettuce.pool.max-active=16 #lettuce底层使用Netty,连接共享,一般不需要连接池
#spring.redis.lettuce.pool.max-wait=3000
#spring.redis.letture.pool.max-idle=12
#spring.redis.lettuce.pool.min-idle=4
坑3:要注意配置文件.conf会被哨兵修改
redis-cli -h localhost -p 26379
,可以登到sentinel上用info命令查看一下哨兵的信息。
曾经遇到过这样一个问题,大致的信息如下
master0:name=mymaster,status=down,address=127.0.0.1:7001,slaves=2,sentinels=3
slaves莫名其妙多了一个,master的地址也明明改了真实对外的地址,这里又变成127.0.0.1 !
最后,把5个redis进程都停掉,逐个检查配置文件,发现redis的配置文件在主从哨兵模式会被修改,master的配置文件最后边莫名其妙多了一行replicaof 127.0.0.1 7001, 怀疑应该是之前配置错误的时候(见坑2)被哨兵动态加上去的! 总之,实践中一定要多注意配置文件的变化。
(2)集群
当数据量大到一定程度,比如几十上百G,哨兵模式不够用了需要做水平拆分,早些年是使用codis,twemproxy这些第三方中间件来做分片的,即客户端 -> 中间件 -> Redis server
这样的模式,中间件使用一致性Hash算法来确定key在哪个分片上。后来Redis官方提供了方案,大家就都采用官方的Redis Cluster方案了。
Redis Cluster从逻辑上分16384个hash slot,分片算法是 CRC16(key) mod 16384
得到key应该对应哪个slot,据此判断这个slot属于哪个节点。
每个节点可以设置1或多个从节点,常用的是3主节点3从节点的方案。
reshard,重新分片,可以指定从哪几个节点移动一些hash槽到另一个节点去。重新分片的过程对客户端透明,不影响线上业务。
搭建Redis cluster
redis.conf文件关键的几个配置:
port 7001 # 端口,每个配置文件不同7001-7006
cluster-enabled yes # 启用集群模式
cluster-config-file nodes-7001.conf #节点配置文件
cluster-node-timeout 15000 # 超时时间
appendonly yes # 打开aof持久化
daemonize yes # 后台运行
pidfile /var/run/redis_7001.pid # 根据端口修改
dir /usr/redis-6.0.3/cluster-data/7001 # redis实例数据配置存储位置
启动6个集群节点
/usr/redis-6.0.3/src/redis-server /usr/redis-6.0.3/cluster-conf/7001/redis.conf &
/usr/redis-6.0.3/src/redis-server /usr/redis-6.0.3/cluster-conf/7002/redis.conf &
/usr/redis-6.0.3/src/redis-server /usr/redis-6.0.3/cluster-conf/7003/redis.conf &
/usr/redis-6.0.3/src/redis-server /usr/redis-6.0.3/cluster-conf/7004/redis.conf &
/usr/redis-6.0.3/src/redis-server /usr/redis-6.0.3/cluster-conf/7005/redis.conf &
/usr/redis-6.0.3/src/redis-server /usr/redis-6.0.3/cluster-conf/7006/redis.conf &
[root@VM_0_11_centos redis-6.0.3]# ps -ef|grep redis
root 5508 1 0 21:25 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7001 [cluster]
root 6903 1 0 21:32 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7002 [cluster]
root 6939 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7003 [cluster]
root 6966 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7004 [cluster]
root 6993 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7005 [cluster]
root 7015 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7006 [cluster]
这时候这6个节点还是独立的,要把他们配置成集群:
redis-cli -a xxxx --cluster create --cluster-replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006
说明: -a xxxx
是因为笔者在redis.conf中配置了requirepass xxxx密码,然后--cluster-replicas 1
中的1表示每个master节点有1个从节点。
上述命令执行完以后会有一个询问:Can I set the above configuration?
yes同意自动做好的分片即可。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
Adding replica 127.0.0.1:7006 to 127.0.0.1:7002
Adding replica 127.0.0.1:7004 to 127.0.0.1:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 91143d1715cb3d5234f1ab67559b621ec51475c9 127.0.0.1:7001
slots:[0-5460] (5461 slots) master
M: 04acd6b82ec44ff9ff738a1dd0c77a2efc29f494 127.0.0.1:7002
slots:[5461-10922] (5462 slots) master
M: 3adde17eca1e7e7baf03fd2220565c0f2b6c7bbf 127.0.0.1:7003
slots:[10923-16383] (5461 slots) master
S: d8df06b10d75613328b30f38d458b0ca094fc997 127.0.0.1:7004
replicates 04acd6b82ec44ff9ff738a1dd0c77a2efc29f494
S: 45f8a0253fc5653fdab27dc4a0455e7a92dae88d 127.0.0.1:7005
replicates 3adde17eca1e7e7baf03fd2220565c0f2b6c7bbf
S: 80350bc7927c943ffd4afdc014448be5407c258b 127.0.0.1:7006
replicates 91143d1715cb3d5234f1ab67559b621ec51475c9
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:7001)
M: 91143d1715cb3d5234f1ab67559b621ec51475c9 127.0.0.1:7001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 3adde17eca1e7e7baf03fd2220565c0f2b6c7bbf 127.0.0.1:7003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: d8df06b10d75613328b30f38d458b0ca094fc997 127.0.0.1:7004
slots: (0 slots) slave
replicates 04acd6b82ec44ff9ff738a1dd0c77a2efc29f494
S: 45f8a0253fc5653fdab27dc4a0455e7a92dae88d 127.0.0.1:7005
slots: (0 slots) slave
replicates 3adde17eca1e7e7baf03fd2220565c0f2b6c7bbf
M: 04acd6b82ec44ff9ff738a1dd0c77a2efc29f494 127.0.0.1:7002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 80350bc7927c943ffd4afdc014448be5407c258b 127.0.0.1:7006
slots: (0 slots) slave
replicates 91143d1715cb3d5234f1ab67559b621ec51475c9
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
最后All 16384 slots covered.
表示集群中16384个slot中的每一个都有至少有1个master节点在处理,集群启动成功。
查看集群状态:
[root@VM_0_11_centos redis-6.0.3]# redis-cli -c -p 7001
127.0.0.1:7001> auth xxxx
OK
127.0.0.1:7001> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:859
cluster_stats_messages_pong_sent:893
cluster_stats_messages_sent:1752
cluster_stats_messages_ping_received:888
cluster_stats_messages_pong_received:859
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1752
坑1:暴露给客户端的节点地址不对
使用lettuce连接发现连不上,查看日志Connection refused: no further information: /127.0.0.1:7002
,跟之前哨兵配置文件sentinel.conf里边配置master地址犯的错误一样,集群启动的时候带的地址应该是提供给客户端访问的地址。
我们要重建集群:先把6个redis进程停掉,然后删除nodes-7001.conf
这些节点配置文件,删除持久化文件dump.rdb
、appendonly.aof
,重新启动6个进程,在重新建立集群:
redis-cli -a xxpwdxx --cluster create --cluster-replicas 1 122.xx.xxx.xxx:7001 122.xx.xxx.xxx:7002 122.xx.xxx.xxx:7003 122.xx.xxx.xxx:7004 122.xx.xxx.xxx:7005 122.xx.xxx.xxx:7006
然后,还是连不上,这次报错connection timed out: /172.xx.0.xx:7004
,发现连到企鹅云服务器的内网地址上了!
解决办法,修改每个节点的redis.conf配置文件,找到如下说明:
# In certain deployments, Redis Cluster nodes address discovery fails, because
# addresses are NAT-ted or because ports are forwarded (the typical case is
# Docker and other containers).
#
# In order to make Redis Cluster working in such environments, a static
# configuration where each node knows its public address is needed. The
# following two options are used for this scope, and are:
#
# * cluster-announce-ip
# * cluster-announce-port
# * cluster-announce-bus-port
所以增加配置:
cluster-announce-ip 122.xx.xxx.xxx
cluster-announce-port 7001
cluster-announce-bus-port 17001
然后再重新构建集群,停进程、改配置、删除节点文件和持久化文件、启动进程、配置集群。。。再来一套(累死了)
重新使用Lettuce测试,这次终于连上了!
坑2:Lettuce客户端在master节点故障时没有自动切换到从节点
name这个key在7002上,kill这个进程模拟master下线,然后Lettuce一直重连。我们期望的是应该能自动切换到其slave 7006上去,如下图:
重新启动7002进程,
127.0.0.1:7001> cluster nodes
4b66a7d28ebbfa149f7f2ad1dd1d3cbbc1e79659 122.51.112.187:7003@17003 master - 0 1638243049258 3 connected 10923-16383
16a3da4143ee873b9ed82d217db9819c8d945d30 122.51.112.187:7005@17005 slave bfdb90a0b0e3217fad5e5eb44ec253531930a418 0 1638243052264 5 connected
110d047d5b6c827c018dbebf83d9db350f12b931 122.51.112.187:7004@17004 slave 4b66a7d28ebbfa149f7f2ad1dd1d3cbbc1e79659 0 1638243051000 4 connected
e4406cfeb0e6944bfd6c5af82ba5f4f1ab38190d 122.51.112.187:7002@17002 slave c7cd30d3843f9f4b113614672cabce193b1bc7b9 0 1638243054267 7 connected
c7cd30d3843f9f4b113614672cabce193b1bc7b9 122.51.112.187:7006@17006 master - 0 1638243053266 7 connected 5461-10922
bfdb90a0b0e3217fad5e5eb44ec253531930a418 122.51.112.187:7001@17001 myself,master - 0 1638243052000 1 connected 0-5460
7006已成为新master,7002成为它的slave,然后Lettuce也能连接上了。
解决办法,修改Lettuce的配置:
import java.time.Duration;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
@Configuration
public class RedisConfig {
@Value("${spring.redis.cluster.nodes:nocluster}")
private String clusterNodes;
@Value("${spring.redis.password:123456}")
private String password;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
return redisTemplate;
}
/**
* 如果是Lettuce集群模式则重新构建RedisConnectionFactory并注入Spring
* */
@Bean
@ConditionalOnProperty(prefix="spring.redis.cluster" , name="nodes")
public RedisConnectionFactory redisConnectionFactory() {
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enableAllAdaptiveRefreshTriggers() // 开启自适应刷新,自适应刷新不开启,Redis集群变更时将会导致连接异常
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30)) //自适应刷新超时时间(默认30秒),默认关闭开启后时间为30秒
.enablePeriodicRefresh(Duration.ofSeconds(60)) // 默认关闭开启后时间为60秒
.build();
ClusterClientOptions clientOptions = ClusterClientOptions.builder()
.topologyRefreshOptions(clusterTopologyRefreshOptions)
.build();
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.clientOptions(clientOptions)
.build();
String[] nodes = clusterNodes.split(",");
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(Arrays.asList(nodes));
redisClusterConfiguration.setPassword(password);
return new LettuceConnectionFactory(redisClusterConfiguration , clientConfig);
}
}
笔者用的是springboot 2.1 spring-boot-starter-data-redis 默认的Lettuce客户端,当使用Redis cluster集群模式时,需要配置一下RedisConnectionFactory
开启自适应刷新来做故障转移时的自动切换从节点进行连接。
重新测试:停掉master 7006,这次Lettuce可以正常切换连到7002slave上去了。(仍然会不断的在日志里报连接错误,因为需要一直尝试重连7006,但因为有7002从节点顶上了、所以应用是可以正常使用的)
Redis不保证数据的强一致性
Redis并不保证数据的强一致性,也就是取CAP定理中的AP
- 主从节点之间使用的是异步复制 , 为了保证高性能,Redis主从同步使用的是异步复制的方式,主节点不会等从节点同步成功而是马上就返回客户端。这样当返回客户端后还没来得及同步从节点成功、这时候如果主节点挂了,那么就会发生数据丢失。
- 发生了网络分区时的一种情况,整个集群分为了隔离的两个大小分区,小分区中有某个主节点A0,大分区中会从A的从节点中选出新的主节点A1,但此时A0并未挂掉还是正常能接受客户端(这个客户端也在小分区里)请求的,这样分区故障的这段时间针对A分片就有两个主节点了、这就是所谓的“脑裂”现象。等网络分区故障恢复之后,A0会称为A1的从节点、清空自己的数据重新从A1上同步数据。这样在小分区时代从客户端写入的数据就丢失了。
Redis集群核心原理
1、Redis cluster没采用一致性Hash算法,添加集群节点或删除节点需要手工维护slot迁移,然后怎么做到热迁移对线上业务无影响的?
关于一致性Hash算法,可以参考一致性Hash算法 - 简书 (jianshu.com)
Redis cluster使用的是hash slot算法,跟一致性Hash算法不太一样,固定16384个hash槽,然后计算key落在哪个slot里边(计算key的CRC16值再对16384取模),key找的是slot而不是节点,而slot与节点的对应关系可以通过reshard改变并通过gossip协议扩散到集群中的每一个节点、进而可以为客户端获知,这样key的节点寻址就跟具体的节点个数没关系了。也同样解决了普通hash取模算法当节点个数发生变化时,大量key对应的寻址都发生改动导致缓存失效的问题。
比如集群增加了1个节点,这时候如果不做任何操作,那么新增加的这个节点上是没有slot的,所有slot都在原来的节点上且对应关系不变、所以没有因为节点个数变动而缓存失效,当reshard一部分slot到新节点后,客户端获取到新迁移的这部分slot与新节点的对应关系、寻址到新节点,而没迁移的slot仍然寻址到原来的节点。
关于热迁移,猜想,内部应该是先做复制迁移,等迁移完了,再切换slot与节点的对应关系,复制没有完成之前仍按照原来的slot与节点对应关系去原节点访问。复制结束之后,再删除原节点上已经迁移的slot所对应的key。
2、当主节点出现故障时,集群是如何识别和如何选主的?
与哨兵模式比较类似,当1个节点发现某个master节点故障了、会对这个故障节点进行pfail主观宕机,然后会通过gossip协议通知到集群中的其他节点、其他节点也执行判断pfail并gossip扩散广播这一过程,当超过半数节点pfail时那么故障节点就是fail客观宕机。接下来所有的master节点会在故障节点的从节点中选出一个新的主节点,此时所有的master节点中超过半数的都投票选举了故障节点的某个从节点,那么这个从节点当选新的master节点。
3、去中心化设计与gossip协议
所有节点都持有元数据,节点之间通过gossip这种二进制协议进行通信、发送自己的元数据信息给其他节点、故障检测、集群配置更新、故障转移授权等等。
这种去中心化的分布式节点之间内部协调,包括故障识别、故障转移、选主等等,核心在于gossip扩散协议,能够支撑这样的广播协议在于所有的节点都持有一份完整的集群元数据,即所有的节点都知悉当前集群全局的情况。
参考:
面试题:Redis 集群模式的工作原理能说一下么 - 云+社区 - 腾讯云 (tencent.com)
深度图解Redis Cluster原理 - detectiveHLH - 博客园 (cnblogs.com)