主从复制
- 官方文档:redis.io/docs/latest...
- 极简概括:将一个主Redis服务器的数据复制到其它从Redis服务器的过程。
- 角色:
- 主节点(Master):负责处理客户端的写(或者读)请求,并将写操作同步到从节点。
- 从节点(Slave):负责处理客户端的读请求,并将主节点发送过来的数据更新数据复制到本地。
- 解决问题:
- 容灾备份:主节点发生故障,从节点有对应的数据备份。
- 负载均衡:当单个Redis节点扛不住时,利用读写分离的特性,多个Redis从节点分摊主节点读的压力。
- 适用场景:读多写少大流量的服务端应用。
- 优点:
- 数据备份。
- 负载均衡。
- 有一定的容灾性:当主节点和从节点之间的由于网络问题断开,从服务器会重新连接并尝试继续进行部分重新同步。当部分重新同步不可行时,复制副本将要求完全重新同步。 +非阻塞: Redis主从复制是在主节点上非阻塞的。这意味着当一个或多个从节点执行初始同步或部分重新同步时,主服务器将继续响应客户端的请求。
- Redis主从复制在从节点上,基本也是非阻塞的,大key情况除外。
- 支持级联架构:Redis从节点也可成为其它实例的主节点,形成级联架构。
- 低耦合容灾:当Redis主节点挂掉时,从节点还能维持基本的读操作。
- 进度同步:不同从节点可能存在着不同的进度,Redis有自动纠正偏移量的机制,使其每个节点保持同一进度。
- 缺点:
- 多台机器不可避免的增加运维成本,
- 受网络隔离和Redis本身的特性,无法保证主从强一致性,极端情况下,主节点活从节点挂掉,就会导致不一致。
- 高并发或有大key的前提下,不可避免的出现主从复制延迟问题。
- 若主节点挂掉,Redis主从功能本身缺少从节点自动升级为主节点的策略,因此出现了哨兵模式。
相关命令或配置
- 命令(Redis服务停止前一直生效,适用于不停机热配置)
- info replication:查看主从节点的状况。
- slaveof 主库IP 主节点端口:在从节点配置主节点。
- slaveof no one:让Redis不做任何节点的从节点。
- 配置(持久化配置):
- replicaof 主库IP 主库端口:在从节点配置主节点。
- masterauth 主节点密码:在从节点配置主节点密码。
一主二从实操
- 误区:Redis主从不比MySQL,Redis只需要在从库上配置主库就可以了。
- 环境:CentOS7.6,配置了3台可正常运行Redis的服务,且保证每个Redis实例可远程连接(同一局域网、防火墙、端口放行、远程连接权限配置到位)。192.168.0.180(主)、192.168.0.181(从1)、192.168.0.182(从2)。
- 内存问题:3个系统占内存,在虚拟机上将每个系统内存配置到256MB,Linux轻松启动。
sh
在从节点1和从节点2上分别进行配置
vim /usr/local/redis/etc/redis.conf
配置IP和端口
replicaof 192.168.0.180 6379
配置密码
masterauth 123456
之后重启redis
测试:
主节点上执行set a a
两台从节点上get a发现都能获取到值。
若在从节点上设置值,会有如下错误:
(error) READONLY You can't write against a read only replica.
此时主节点执行info replication,会得到如下结果:
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.0.182,port=6379,state=online,offset=2736,lag=1
slave1:ip=192.168.0.181,port=6379,state=online,offset=2736,lag=1
master_replid:9aaf7cbbcfb924c8a793e1b69f0597a57768844b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2736
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:2736
注释:
Replication是注释。
connected_slaves:表示从节点数量
slave0和slave1:描述两个从节点的基本信息。
master_replid: 主服务器的复制 ID
master_replid2: 用于支持部分同步复制的第二复制 ID
master_repl_offset: 主服务器的复制偏移量,复制的进度。
repl_backlog_active: 启用复制 backlog 功能
repl_backlog_size: 复制 backlog 的大小为1048576字节
repl_backlog_first_byte_offset: 复制 backlog 中第一个字节的偏移量
repl_backlog_histlen: 复制 backlog 的历史长度为2736字节
backlog
复制积压缓冲区,当主节点有连接的slave时创建,主节点响应写请求时,会先写到自己的backlog buffer中。
可以简单的理解为,避免数据改动一个字节就频繁同步从节点,而是积累到一定的批次做缓冲。
从节点执行得到如下结果
# Replication
role:slave
master_host:192.168.0.180
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:518
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f1f768608ae81b330bb152f69a4a624bf3a9657e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:518
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:518
role:slave: 表示这是一个从节点。
master_host:192.168.0.180: 指定了主节点的IP地址。
master_port:6379: 指定了主节点的端口号。
master_link_status:up: 表示从节点与主节点的连接状态为正常(已连接)。
master_last_io_seconds_ago:3: 上次与主节点进行IO操作的时间,这里是3秒前。
master_sync_in_progress:0: 没有正在进行的同步操作。
slave_repl_offset:518: 从节点当前复制偏移量为518。
slave_priority:100: 从节点的复制优先级为100。
slave_read_only:1: 从节点设置为只读模式(read-only)。
connected_slaves:0: 连接的从节点数为0,即没有其他从节点连接到这个从节点。
master_replid:f1f768608ae81b330bb152f69a4a624bf3a9657e: 主节点的复制ID。
master_replid2:0000000000000000000000000000000000000000: 第二复制ID。
master_repl_offset:518: 主节点当前复制偏移量为518。
second_repl_offset:-1: 第二复制偏移量为-1。
repl_backlog_active:1: 复制后备日志(repl backlog)处于激活状态。
repl_backlog_size:1048576: 复制后备日志的大小为1048576字节(1MB)。
repl_backlog_first_byte_offset:1: 复制后备日志的第一个字节偏移量为1。
repl_backlog_histlen:518: 复制后备日志历史长度为518。
一主一从再一从实操
可以理解为爹子孙三代。 这个用法与一主二从没有什么区别,可以接着一主二从的基础上,在从节点2上临时设置slave of 192.168.0.181 6379
(从1为从2主节点)即可。 注意密码问题,由于命令行不支持masterauth命令,所以可能需要在从节点配置文件中先把主节点密码设置到位。
编程语言调用的绑定问题
- 以PHP为例,读写调用主或者从服务器,是由PHP控制的,不是由Redis主节点控制的(Redis主节点就没有从节点的配置)。
- PHP Redis扩展虽然提供了丰富的Redis Client API,但是没有专门应对主从的解决方案。因此原生开发,读写操作调用那台服务器,是由开发者自定义控制,而不是PHP的内部实现。 例如:
php
$redis_master = new Redis();
$redis_master->connect('192.168.0.180', 6379);
$redis_master->auth('123456');
$redis_slave = new Redis();
$redis_slave->connect('192.168.0.181', 6379);
$redis_master->set('k', 'v');
print_r($redis_master->get('k'));
如果是从库写,也会报如上一样的错误:
Fatal error: Uncaught RedisException: READONLY You can't write against a read only replica. in E:\Host\test\t1.php:10 Stack trace: #0 E:\Host\test\t1.php(10): Redis->set('k', 'v') #1 {main} thrown in E:\Host\test\t1.php on line 10
对于Laravel框架:
php
config/database.php中的redis段,添加
'slave' => [
'client' => 'predis',
'host' => 'your_slave_redis_host',
'password' => 'your_slave_redis_password',
'port' => 6379,
'database' => 0,
],
使用主节点
Redis::connection('default')->set('key', 'value');
使用从节点
Redis::connection('slave')->get('key');
从节点数据覆盖问题
经过实测,若一个redis实例被做为从节点,那么原先的老数据会被清空,这可能会造成数据丢失。
主从节点进度不一致问题
Redis的特性,主从复制性能不会差,除非遇到大key。所以这里讨论的是,从节点1和主节点进度相同,从节点2和主节点进度不同的问题。 可以模拟从节点2挂掉,直接关停从节点2,主节点多写几个key,从节点1正常同步。此时启动从节点2,经过实测,发现进度得到了同步。 这意味Redis会自动处理进度不一致的问题,达到的是最终一致性。
主节点挂掉后从节点的状态
经过实测,从节点不会自动变成主节点,从节点上的数据可以正常读取,仍旧不能写入。 比较好的一点是,虽然数据层,从节点强依赖主节点,但是主节点挂掉后,从节点没有挂掉,勉强使用。 此时从节点不进行任何操作,若主节点恢复,则整个主从架构正常运行。
单线程下却可以异步复制的思考
官网有说:Redis主从复制的内部过程,是异步进行的(异步非阻塞,高性能)。由于Redis是单线程,通俗讲就是同时只能执行一个任务,却进行了异步实现,这个问题值得理解。
通过查阅资料,得知单线程异步是通过事件循环机制来处理。 单线程环境下:当一个异步操作被启动后,程序会继续执行其它任务,但异步操作的结果或者状态变化会被放入事件队列中,等待事件循环处理。当事件循环处理到该事件时,会调用相应的回调函数来处理操作的结果或者状态变化。在这种情况下,不需要额外创建新的线程。
按照我的理解,通俗讲,整个过程就是一个循环机制,不停的循环,获取事件队列上的数据,一是可以让待执行的异步任务再下一轮循环中去执行,二是可以再本次循环中执行同步的任务,直到任务为空从而停止,不需要额外现成的依赖。
从节点备份的不可靠因素
纯缓存模式下:主节点崩溃,但是服务器设定了一些运维策略,可以自动重启进程。但Redis主节点启动时,将以空数据集重新启动。其它从节点缓存的数据,也将会被销毁。
所以: 复制与不带持久性配置的master一起使用时,应该禁用实例的自动重启。 或者,做好主节点的AOF与RDB配置。
RDB:配置save '',并注释掉其它save项。 AOF:配置appendonly no 可修改为纯缓存模式,亲测确实会丢失数据。