07-主从复制
Redis Replication
redis replication 重要考虑
redis 如何通过副本提供高可用及容灾
在redis的基础上使用和配置副本是非常简单的(不包含高可用和集群、哨兵特性);redis实例包含两个角色:master、salve
slave实例是master实例的精确副本, 副本会在连接断开的时候自动重连到master,并且不管master发生了什么操作都会尝试精准拷贝master
Redis主要使用三个机制确保其工作
-
当master和副本实例之间连接良好时,master通过发送命令流到副本实例确保数据更新;
- 当客户端对master进行了写操作
- 当key过期
- 当key被驱逐时
-
当master和副本之间连接断开(网络错误或超时等),副本会重新连接并尝试执行特殊的同步(增量同步)
- 只同步断连时间段内更新数据的命令流
-
当无法增量同步时,副本会请求master进行全量同步:master会创建一个全部数据的快照然后继续发送修改数据集的命令流
同步复制、异步复制
- 大多数用户场景都使用异步复制,它具有低延迟高性能的特点
- redis也支持可选的同步复制
同步复制某些数据可以通过客户端使用WAIT命令请求;
redis副本中的重要考量
- redis使用异步复制,通过异步确认数据处理的数量
- 一个master可以有多个副本
- 副本可以尝试取连接其他副本(级联结构)。
- redis复制在master端是非阻塞的
- 在副本端大致上也是非阻塞的,当副本端执行初始同步时,它可以使用旧版本的数据集
- 在初始同步后需要删除旧数据集加载新数据集,这会阻塞进来的连接;
- redis可以配置在不同的线程中执行删除,但是加载数据必须在主线程中进行所以会阻塞副本实例
- 副本可以用于可扩展性,使用多个只读副本用于查询,或简单地改善数据安全性或高可用性
- 可以使用副本避免将写磁盘的花费;
- 在master关闭持久化,而在副本上不时地保存数据或开启AOF
- 此操作需要
注意:当master重启时数据集是空的,当副本尝试同步时也会清空数据集;特别是切换主从时
在master关闭持久化的安全性
在使用redis副本设置时,强烈建议开启master和副本的持久化设置;如果因为磁盘慢的延迟问题,redis需要避免自动重新启动
因为这会导致master在空数据集上重启,而副本实例在连接到master后会复制空数据集导致副本中的数据被擦除。
在使用哨兵的时候关闭持久化也有同样的问题(哨兵会自动切换master)
Redis 副本如何工作
每个master都有一个replication ID, 它是一个长伪随机字符串,标记一个数据集;
每个master还持有一个按字节自增的偏移量(offset),它会发送到副本实例,用于更新副本更新数据的状态;
replication offset 即使在没有副本连接到master也会自增;所以可以使用 Replication ID, offset来标记数据的具体版本。
当副本连接到master时,使用PSYNC命令发送旧ReplicationID和当前offset,这样master就可以只发送递增的数据;
但是,如果在master缓存中没有足够的backlog,或副本涉及到一个未知的版本,就会发送全量同步;
Diskless replication
通常一个全量同步需要在磁盘上创建一个RDB文件,然后在副本加载RDB文件
对于速度很慢的磁盘,这对主机来说是很重的操作。redis2.8.18开始支持无磁盘复制,这个配置会使用子进程直接通过网络发送RDB到副本而不需要磁盘进行中间存储
配置从机
在从机的redis.conf中配置如下,
# 配置主机IP和端口
replicaof 192.168.10.10 6379
# 认证
masterauth 123456
# 可以开启无磁盘复制
repl-diskless-sync
repl-diskless-sync-delay
只读副本
- redis2.6开始支持只读模式并且默认开启;可以通过
replica-read-only选项关闭;(config set 命令也可以) - 只读模式会拒绝所有写命令,这不代表这个特性用于将副本暴露到网络或不信任的客户端,因为管理命令仍然可以在该模式下执行
- 使用可写的副本会导致master和副本的数据不一致问题
淘汰的用例
-
set比较或有序集合操作并存储结果到临时key, 例如
sunionstorezinterstore命令;- 使用返回结果但不存储它的命令 sunion, zinter
-
使用
sort命令。 可以使用 sort_ro 替代; 这是read-only命令 -
使用eval evalsha
- 可以使用 eval_ro evalsha_ro
至少存在N个副本时才允许写
从2.8开始,可以通过配置方式让master在有N个副本连接到当前master时才允许写操作。
但是,由于redis采用异步复制无法保证副本接收到给定的写操作,所以始终存在数据丢失的窗口;
工作原理:
- redis副本会每秒发送ping请求,确认已处理的副本流数量
- redis Master会记住最后一次收取到ping的副本
- 用户可以配置延迟不超过最大秒数的最小副本数。
在延迟M秒内如果有至少N个副本则允许写。
min-replicas-to-write <number of replicas>
min-replicas-max-lag <number of seconds>
Redis副本如何处理过期的key
redis允许为键指定存活期限,这个特定依赖于对时间的计算。
为了实现这个特性,redis无法依赖于master与replica之间有同步的时钟,因为这是一个无法解决的问题,并会导致竞争条件和数据集差集。
redis主要使用三种技术确保过期的key可以正常工作:
- 副本不会使key过期,相反副本会等待master中的key过期。 当master中的key过期后发送 DEL 命令到所有的副本
- 由于key的过期由master驱动,有些时候由于master没有及时发送DEL命令到副本,所有逻辑上过期的key仍然存在于副本的内存中。为了解决这个问题,副本机器会使用逻辑时钟为读操作报告key不存在,这不会侵犯数据一致性。
- 在LUA脚本执行的时候不会执行key的过期操作,这是因为在lua执行过程中,主机的时间是冻结的,所以任何给定key要么存在要么不存在,这可以阻止key在脚本执行过程中被过期。
主从设置的方案
- 一主多从(主->从, 主->从)
- 代代相传(主->从->从)
- 揭竿而起(从->主)
一主多从
一个从服务器可以有一个主,一个主可以有多个从
Master: 192.168.10.10 6379
Slave1
# 配置主机IP和端口
replicaof 192.168.10.10 6379
# 认证
masterauth 123456
# 可以开启无磁盘复制
repl-diskless-sync
repl-diskless-sync-delay
Slave2
# 配置主机IP和端口
replicaof 192.168.10.10 6379
# 认证
masterauth 123456
# 可以开启无磁盘复制
repl-diskless-sync
repl-diskless-sync-delay
代代相传
Master: 192.168.10.10 6379
Slate1: 192.168.10.11 6379
Slave2: 192.168.10.12 6379
Slave1
# 配置主机IP和端口
replicaof 192.168.10.10 6379
# 认证
masterauth 123456
# 可以开启无磁盘复制
repl-diskless-sync
repl-diskless-sync-delay
Slave2
# 配置主机IP和端口
replicaof 192.168.10.11 6379
# 认证
masterauth 123456
# 可以开启无磁盘复制
repl-diskless-sync
repl-diskless-sync-delay
slave 切换为主
slaveof no one
哨兵模式 sentinel
配置哨兵
sentinel.conf
# sentinel monitor {master_redis} {ip} {port} {num}
# master_redis 为master取一个别名
# ip master 的IP;不要写127.0.0.1
# port master 的端口
# num
sentinel monitor master_redis 192.168.10.10 6379 1
启动哨兵
redis-sentinel sentinel.conf

Jedis 使用哨兵服务
public class AppSentinelPool {
private static JedisSentinelPool pool;
static {
HashSet<String> sentinelSet = new HashSet<>();
sentinelSet.add("192.168.31.54:26379");
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxIdle(5);
poolConfig.setMinIdle(5);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setMaxWaitMillis(2000);
poolConfig.setTestOnBorrow(true);
//这里的 master_redis 要和哨兵配置中的别名一致
pool = new JedisSentinelPool("master_redis", sentinelSet, poolConfig);
}
public static Jedis getJedisFromSentinel() {
return pool.getResource();
}
public static void main(String[] args) {
Jedis jedis = getJedisFromSentinel();
String ping = jedis.ping();
System.out.println(ping);
}
}

浙公网安备 33010602011771号