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, 例如 sunionstore zinterstore命令;

    • 使用返回结果但不存储它的命令 sunion, zinter
  • 使用 sort命令。 可以使用 sort_ro 替代; 这是read-only命令

  • 使用eval evalsha

    • 可以使用 eval_ro evalsha_ro

至少存在N个副本时才允许写

从2.8开始,可以通过配置方式让master在有N个副本连接到当前master时才允许写操作。

但是,由于redis采用异步复制无法保证副本接收到给定的写操作,所以始终存在数据丢失的窗口;

工作原理:

  1. redis副本会每秒发送ping请求,确认已处理的副本流数量
  2. redis Master会记住最后一次收取到ping的副本
  3. 用户可以配置延迟不超过最大秒数的最小副本数。

在延迟M秒内如果有至少N个副本则允许写。

min-replicas-to-write <number of replicas>
min-replicas-max-lag <number of seconds>

Redis副本如何处理过期的key

redis允许为键指定存活期限,这个特定依赖于对时间的计算。

为了实现这个特性,redis无法依赖于master与replica之间有同步的时钟,因为这是一个无法解决的问题,并会导致竞争条件和数据集差集。

redis主要使用三种技术确保过期的key可以正常工作:

  1. 副本不会使key过期,相反副本会等待master中的key过期。 当master中的key过期后发送 DEL 命令到所有的副本
  2. 由于key的过期由master驱动,有些时候由于master没有及时发送DEL命令到副本,所有逻辑上过期的key仍然存在于副本的内存中。为了解决这个问题,副本机器会使用逻辑时钟为读操作报告key不存在,这不会侵犯数据一致性。
  3. 在LUA脚本执行的时候不会执行key的过期操作,这是因为在lua执行过程中,主机的时间是冻结的,所以任何给定key要么存在要么不存在,这可以阻止key在脚本执行过程中被过期。

主从设置的方案

  • 一主多从(主->从, 主->从)
  • 代代相传(主->从->从)
  • 揭竿而起(从->主)

一主多从

一个从服务器可以有一个主,一个主可以有多个从

flowchart TD a(Master)-->b(Slave1) a-->c(Slave2)

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 

代代相传

flowchart LR a(Master)-->b(Slave1)-->c(Slave2)

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

image

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);      
    }
}
posted @ 2024-04-20 12:57  Dreamsrj  阅读(5)  评论(0)    收藏  举报