赞
踩
下图就是一个简单的Redis主从集群结构:
如图所示,集群中有一个master节点、两个slave节点(现在叫replica)。当我们通过Redis的Java客户端访问主从集群时,应该做好路由:
我们会在同一个虚拟机中利用3个Docker容器来搭建主从集群,容器信息如下:
| ##### 容器名
| ##### 角色
| ##### IP
| ##### 映射端口
|
| — | — | — | — |
| r1 | master | 192.168.70.145 | 7001 |
| r2 | slave | 192.168.70.145 | 7002 |
| r3 | slave | 192.168.70.145 | 7003 |
version: "3.2" services: r1: image: redis container_name: r1 network_mode: "host" #直接在宿主机上,没有通过网桥 entrypoint: ["redis-server", "--port", "7001"] r2: image: redis container_name: r2 network_mode: "host" entrypoint: ["redis-server", "--port", "7002"] r3: image: redis container_name: r3 network_mode: "host" entrypoint: ["redis-server", "--port", "7003"]
[root@Docker redis]# docker load -i redis.tar
2edcec3590a4: Loading layer [==================================================>] 83.86MB/83.86MB
9b24afeb7c2f: Loading layer [==================================================>] 338.4kB/338.4kB
4b8e2801e0f9: Loading layer [==================================================>] 4.274MB/4.274MB
529cdb636f61: Loading layer [==================================================>] 27.8MB/27.8MB
9975392591f2: Loading layer [==================================================>] 2.048kB/2.048kB
8e5669d83291: Loading layer [==================================================>] 3.584kB/3.584kB
Loaded image: redis:latest
[root@Docker redis]# docker compose up -d
WARN[0000] /root/redis/docker-compose.yaml: `version` is obsolete
[+] Running 3/3
✔ Container r1 Started 0.5s
✔ Container r2 Started 0.5s
✔ Container r3 Started
[root@Docker redis]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ebcb81cf22a7 redis "redis-server --port…" 3 minutes ago Up 3 minutes r3
6f9fdad55162 redis "redis-server --port…" 3 minutes ago Up 3 minutes r1
d4b87d243843 redis "redis-server --port…" 3 minutes ago Up 3 minutes r2
[root@Docker ~]# ps -ef|grep redis
root 37485 37423 0 19:23 ? 00:00:00 redis-server *:7003
root 37489 37417 0 19:23 ? 00:00:00 redis-server *:7002
root 37493 37428 0 19:23 ? 00:00:00 redis-server *:7001
root 37805 36419 0 19:28 pts/0 00:00:00 grep --color=auto redi
# Redis5.0以前
slaveof <masterip> <masterport>
# Redis5.0以后
replicaof <masterip> <masterport>
r1 127.0.0.1:7001> info replication # Replication role:master connected_slaves:0 master_failover_state:no-failover master_replid:60e71a4245287790354b761da5293f277fc06bbd master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 127.0.0.1:7001> ---------------------------------------------------------------- r2 127.0.0.1:7002> info replication # Replication role:master connected_slaves:0 master_failover_state:no-failover master_replid:3748fe320f7ade3e218ecc02e465ed0e37fa2906 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 ------------------------------------------------------------------- r3 127.0.0.1:7003> info replication # Replication role:master connected_slaves:0 master_failover_state:no-failover master_replid:24a8e2f9c93757046a37e65c2424655f082fbb64 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
127.0.0.1:7002> slaveof 192.168.70.145 7001 OK # 查看是否成功 127.0.0.1:7002> info replication # Replication role:slave master_host:192.168.70.145 master_port:7001 master_link_status:up master_last_io_seconds_ago:3 master_sync_in_progress:0 slave_read_repl_offset:0 slave_repl_offset:0 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:1b21b42e9e00ab7972832625056ea34cbf210541 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:0 127.0.0.1:7002>
127.0.0.1:7003> slaveof 192.168.70.145 7001 OK 127.0.0.1:7003> info replication # Replication role:slave master_host:192.168.70.145 master_port:7001 master_link_status:up master_last_io_seconds_ago:2 master_sync_in_progress:0 slave_read_repl_offset:42 slave_repl_offset:42 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:1b21b42e9e00ab7972832625056ea34cbf210541 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:42 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:43 repl_backlog_histlen:0 127.0.0.1:7003>
127.0.0.1:7001> info replication # Replication role:master connected_slaves:2 slave0:ip=192.168.70.145,port=7002,state=online,offset=42,lag=0 slave1:ip=192.168.70.145,port=7003,state=online,offset=42,lag=0 master_failover_state:no-failover master_replid:1b21b42e9e00ab7972832625056ea34cbf210541 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:42 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:42 127.0.0.1:7001>
127.0.0.1:7001> set k1 v1
OK
127.0.0.1:7001> get k1
"v1"
127.0.0.1:7001>
127.0.0.1:7003> get k1
"v1"
127.0.0.1:7003> set k2 v2
(error) READONLY You can't write against a read only replica.
127.0.0.1:7003>
127.0.0.1:7002> get k1
"v1"
127.0.0.1:7002> set k2 v2
(error) READONLY You can't write against a read only replica.
127.0.0.1:7002>
这里有一个问题,master如何得知salve是否是第一次来同步呢??
有几个概念,可以作为判断依据:
由于我们在执行slaveof命令之前,所有redis节点都是master,有自己的replid和offset。
当我们第一次执行slaveof命令,与master建立主从关系时,发送的replid和offset是自己的,与master肯定不一致。
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。
master会将自己的replid和offset都发送给这个slave,slave保存这些信息到本地。自此以后slave的replid就与master一致了
因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致。流程如图:
完整流程描述:
来看下r1节点的运行日志:
再看下r2节点执行replicaof命令时的日志:
与我们描述的完全一致。
repl_baklog中会记录Redis处理过的命令及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满:
此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分:
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,master的offset就会覆盖repl_baklog中旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于repl_baklog做增量同步,只能再次全量同步。
简述全量同步和增量同步区别?
- 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
- 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
- slave节点第一次连接master节点时
- slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
- slave节点断开又恢复,并且在repl_baklog中能找到offset时
Redis提供了哨兵(Sentinel)机制来监控主从集群监控状态,确保集群的高可用性
那么问题来了,Sentinel怎么知道一个Redis节点是否宕机呢?
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个节点发送ping命令,并通过实例的响应结果来做出判断
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
问题来了,当选出一个新的master后,该如何实现身份切换呢?
大概分为两步:
OK,sentinel找到leader以后,该如何完成failover呢?
我们举个例子,有一个集群,初始状态下7001为master,7002和7003为slave:
假如master发生故障,slave1当选。则故障转移的流程如下:
1)sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
2)sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些节点成为新master,也就是7002的slave节点,开始从新的master上同步数据。
3)最后,当故障节点恢复后会接收到哨兵信号,执行slaveof 192.168.150.101 7002命令,成为slave:
sentinel announce-ip "192.168.150.101" #--改成自己的ip
sentinel monitor hmaster 192.168.150.101 7001 2 # 2 认定master下线时的quorum值
sentinel down-after-milliseconds hmaster 5000 #- hmaster:主节点名称,自定义,任意写
sentinel failover-timeout hmaster 60000
# - sentinel down-after-milliseconds hmaster 5000:声明master节点超时多久后被标记下线
# - sentinel failover-timeout hmaster 60000:在第一次故障转移失败后多久再次重试
[root@Docker redis]# pwd /root/redis [root@Docker redis]# mkdir s1 s2 s3 [root@Docker redis]# ll 总用量 113588 -rw-r--r--. 1 root root 408 6月 19 19:18 docker-compose.yaml -rw-r--r--. 1 root root 116304384 6月 19 19:18 redis.tar drwxr-xr-x. 2 root root 6 6月 19 20:50 s1 drwxr-xr-x. 2 root root 6 6月 19 20:50 s2 drwxr-xr-x. 2 root root 6 6月 19 20:50 s3 -rw-r--r--. 1 root root 171 6月 19 20:50 sentinel.conf [root@Docker redis]# cp sentinel.conf s1 [root@Docker redis]# cp sentinel.conf s2 [root@Docker redis]# cp sentinel.conf s3 [root@Docker redis]# ll
version: "3.2" services: r1: image: redis container_name: r1 network_mode: "host" entrypoint: ["redis-server", "--port", "7001"] r2: image: redis container_name: r2 network_mode: "host" entrypoint: ["redis-server", "--port", "7002", "--slaveof", "192.168.150.101", "7001"] r3: image: redis container_name: r3 network_mode: "host" entrypoint: ["redis-server", "--port", "7003", "--slaveof", "192.168.150.101", "7001"] s1: image: redis container_name: s1 volumes: - /root/redis/s1:/etc/redis network_mode: "host" entrypoint: ["redis-sentinel", "/etc/redis/sentinel.conf", "--port", "27001"] s2: image: redis container_name: s2 volumes: - /root/redis/s2:/etc/redis network_mode: "host" entrypoint: ["redis-sentinel", "/etc/redis/sentinel.conf", "--port", "27002"] s3: image: redis container_name: s3 volumes: - /root/redis/s3:/etc/redis network_mode: "host" entrypoint: ["redis-sentinel", "/etc/redis/sentinel.conf", "--port", "27003"]
docker compose up -d
查看日志
docker logs s1
# Sentinel ID is 8e91bd24ea8e5eb2aee38f1cf796dcb26bb88acf
# +monitor master hmaster 192.168.150.101 7001 quorum 2
* +slave slave 192.168.150.101:7003 192.168.150.101 7003 @ hmaster 192.168.150.101 7001
* +sentinel sentinel 5bafeb97fc16a82b431c339f67b015a51dad5e4f 192.168.150.101 27002 @ hmaster 192.168.150.101 7001
* +sentinel sentinel 56546568a2f7977da36abd3d2d7324c6c3f06b8d 192.168.150.101 27003 @ hmaster 192.168.150.101 7001
* +slave slave 192.168.150.101:7002 192.168.150.101 7002 @ hmaster 192.168.150.101 7001
Sentinel的三个作用是什么?
Sentinel如何判断一个redis实例是否健康?
故障转移步骤有哪些?
sentinel选举leader的依据是什么?
sentinel从slave中选取master的依据是什么?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
sentinel:
master: hmaster # 集群名
nodes: # 哨兵地址列表
- 192.168.70.145:27001
- 192.168.70.145:27002
- 192.168.70.145:27003
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这个bean中配置的就是读写策略,包括四种:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。