标题:【Redis21】Redis进阶:主从复制

文章目录
    分类:存储运维 标签:Redis

    Redis进阶:主从复制

    对于大型企业来说,一台 Redis 实例要保证可用性,往往会配置主从库。这一点上其实和 MySQL 是一样的,我们绝大部分的业务需求通常的情况都是读多写少。在这种情况下,合理的分摊读库请求,就可以大大加强请求的响应速度。对比 MySQL 来说,Redis 的主从复制简单的没朋友。

    主从复制配置

    对于我们测试学习来说,一般不会去建立多个虚拟机或者 Docker ,更不可能去买多台真正的服务器来练习。大家一般都是多开几个实例,只要端口号不重复就行了。因此,我们需要多建两个配置文件,区分开端口号。

    // vim redis_80.conf
    include /usr/local/etc/redis.conf
    port 6380
    pidfile "/var/run/redis_6380.pid"
    dbfilename "dump6380.rdb"

    默认的 Redis 端口号是 6379 ,为了测试方便,我们就新建两个文件,一个用 6380 ,一个用 6381 。配置文件可以直接 include 默认的那个主配置文件,然后覆盖写上新的端口号就可以了。另外需要注意的是,pidfile 和 dbfilename 也要换个名字,要不就会共用 6379 的。6381 的配置文件也是类似的,这里就不粘出来了。然后我们启动 6380 和 6381 。

    // 服务端 6380
    redis-server redis_80.conf
    
    // 服务端 6381
    redis-server redis_81.conf

    如果你不想在控制台看到启动情况,或者说想让实例在后台运行的话,可以再加上下面这个配置。

    // 配置文件
    daemonize yes

    然后使用客户端进入 6380 和 6381 ,直接运行 SLAVEOF <host> <port> 命令,其中 host 指的是主库的地址,port 指的是主库的端口号。

    // 客户端 6380
    ➜  redis-cli -p 6380
    127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
    OK
    
    // 客户端 6381
    ➜  redis-cli -p 6381
    127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
    OK

    好了,6380 和 6381 就是 6379 的两台从库了。是不是很简单,我们去验证一下。

    // 客户端 6379
    127.0.0.1:6379> INFO Replication
    # Replication
    role:master
    connected_slaves:2
    slave0:ip=127.0.0.1,port=6380,state=online,offset=252,lag=0
    slave1:ip=127.0.0.1,port=6381,state=online,offset=252,lag=1
    ………………
    
    127.0.0.1:6379> role
    1) "master"
    2) (integer) 1313
    3) 1) 1) "127.0.0.1"
          2) "6380"
          3) "1313"
       2) 1) "127.0.0.1"
          2) "6381"
          3) "1313"

    在 6379 上,使用 INFO Replication 就可以看到当前的角色是 master ,有两台从库,它们的状态信息也都能看到。另外还有一个命令就是 role ,它也能看到当前的主从状态信息。第一行是本机的角色,下面是从库的情况。同样的,在从库上也能看到相关的信息。

    // 客户端 6380
    127.0.0.1:6380> INFO Replication
    # Replication
    role:slave
    master_host:127.0.0.1
    master_port:6379
    master_link_status:up
    master_last_io_seconds_ago:4
    master_sync_in_progress:0
    slave_read_repl_offset:378
    slave_repl_offset:378
    slave_priority:100
    slave_read_only:1
    ………………
    
    127.0.0.1:6380> role
    1) "slave"
    2) "127.0.0.1"
    3) (integer) 6379
    4) "connected"
    5) (integer) 1341

    好了,现在试试看主从之间有没有真正的实现复制吧。

    // 客户端 6379
    ➜  redis-cli -p 6379
    127.0.0.1:6379> set a 111
    OK
    
    // 客户端 6380
    ➜  redis-cli -p 6380
    127.0.0.1:6380> get a
    "111"

    可以看到,我们在 6379 添加了一个 a 的数据,然后到 6380 可以 GET 到这条数据。嗯,目前来看主从配置是完全没问题啦。


    要说明的是,SLAVEOF 命令也可以配置到配置文件中,这样实例一启动就直接会进行主从连接。

    复制机制

    当主库和从库建立起连接后,主库会发送命令流来保持对从库的更新,包括客户端的写入、key的过期等。如果从库断开连接,过一段时间又重新连接之后,会进行部分重同步,可能会丢失部分命令。如果无法进行部分重同步的话,从库会请求进行全量同步,也就是主库会把 rdb 整个发送给从库。


    整个同步复制的过程都是走的异步复制,具有低延迟和高性能的特点。从库和从库之间也可以建立主从关系,从而形成一个树状的同步结构,当最顶上的主库挂了的时候,可以马上选择一台拥有从库的从库来切换成主库,从而保证服务提供的可用性。

    持久化问题

    如果想要高性能,我们其实可以这样配置,那就是主库不做持久化,而在从库中选一台出来做持久化。虽然这样做可以获取到很高的性能,但是,假如主库挂了,那么当它重启的时候,会是一个空的数据库。这样的空数据库也会同步给从库,从而导致所有数据全部被清空,这一点是需要注意的。特别是配置了一些自动重启实例的工具的情况下,非常危险。


    因此,还是更建议主从都开启持久化,或者使用哨兵机制以及分布式的方式来进行部署,这两块我们后面的文章马上就会学习到哦。

    密码

    如果主库有密码,我们可以通过 masterauth <password> 这个命令或者直接在配置文件中写上,来设置从库连接到主库之后使用的密码。

    REPLICAOF

    SLAVEOF 命令已经是过时的命令了,在 Redis5 之后,更推荐使用的是 REPLICAOF <masterip> <masterport> ,参数和使用方式与 SLAVEOF 是一样的。在现在的版本中,默认的配置文件中也只能搜到 REPLICAOF ,搜不到 SLAVEOF ,但是这个命令还是可以正常使用的。大家将来应用的时候,如果版本是在 Redis5 及以上的话,还是尽量使用新的命令哦。

    从库对于过期 Key 的处理

    Redis 的从库不会主动清理过期的 Key ,这是为啥呢?因为要听老大的嘛。当主库的 Key 过期后,主库会发送一个 DEL 给从库,从而实现过期 Key 的删除。


    但是,主从多少会有延迟的嘛,所以从库也有自己的一个逻辑时钟用以报告不违反数据库一致性的读操作中存在的 Key 。也就是说,从库虽然不会主动过期,但也会在自己的计时中标注当前这个 Key 是否已经过期,只不过,它不会真的删除这个 Key ,而是要等待主库的命令到达。如果客户端请求,它将返回一个空值。在 Redis3.2 之前的版本中,从库没有自己的逻辑时钟,这是一个大问题,所以老版本的小伙伴一定要注意(这个现在应该很少了吧)。


    因此,要注意主从库,特别是不在一台服务器上的主从库之间的时钟同步性。


    另外一种情况就是没有设置过期时间的,就是主库主动删除的 Key 。只要是有同步,那么必然就会有延迟,因此,尽量将主从实例的主机部署在同一个机房,尽量的减少同步延迟就是最重要的步骤了,这一块不仅是 Redis ,任何牵涉到主从复制这种机制的应用都会有这个问题,可以考虑应用层面上的代码解决方案,更深层次上就是整体架构设计方面的问题了。这一块咱的水平不够,也说不出个所以然来,更重要的是,没经历过啊,中小型公司能将一台 Redis 实例的极限性能能发挥出来的都少之又少,所以只能将来我们学习到相关架构实践的时候再来说吧!


    另外配置文件中的 repl-disable-tcp-nodelay 参数如果选择 no 的话,可以降低主从延迟,但是会加大带宽消耗,如果在一个机房是可以考虑关闭这个参数的,毕竟内网基本是不收费的,而且带宽还大。

    同步过程

    第一台主库会有一个 replication ID ,这是一个随机字符串,在 INFO 中可以看到。

    127.0.0.1:6379> info replication
    ………………
    master_replid:1cf72f79da111329945c43855616ca5cf4b43f1a
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:0
    second_repl_offset:-1
    ………………

    同时也有一个偏移量 offset 。


    主库将自己产生的复制流发送给从库的时候,发送多少个字节的数据,自身的偏移量就会增加多少,目的就是当有新的操作修改数据时,可以以此更新从库的状态。这个其实就和 MySQL 主从中的 binlog 日志一样,在配置主库的时候,我们也会记录当前的一个偏移量,然后等待从库同步到与主库相同的状态时,才开放主库的写操作。一般也会通过偏移量来确定主从之间是否产生延迟。


    接着,从库通过 PSYNC 命令发送请求并获得 replication ID 和 offset ,开始比较自己的同步情况,然后进行数据同步。这一块我们可以通过 SYNC 命令看到。

    127.0.0.1:6380> SYNC
    Entering replica output mode...  (press Ctrl-C to quit)
    SYNC with master, discarding 14838 bytes of bulk transfer...
    SYNC done. Logging commands from master.
    "set","b","222"
    "ping"

    开启 SYNC 命令之后,从库会不停地发送 PING 命令,这时你可以在主库上随便进行一个操作,然后回到从库,就可以看到 SYNC 也输出了从主库上获得的命令信息。

    只读

    默认情况下,Redis 中的从库是只读的,就像下面这样。

    // 客户端 6380
    127.0.0.1:6380> set a 123
    (error) READONLY You can't write against a read only replica.

    这是因为默认情况下,在配置文件中,有这样一个配置 slave-read-only yes ,它的意思很明显,就是从库只能读。我们可以修改配置文件,也可以动态修改它。

    127.0.0.1:6380> config set slave-read-only no
    OK
    127.0.0.1:6380> set a 123
    OK
    127.0.0.1:6380> get a
    "123"
    
    // 客户端 6379
    ➜  ~ redis-cli -p 6379
    127.0.0.1:6379> get a
    "111"
    127.0.0.1:6379> set a 456
    OK
    
    // 客户端 6380
    ➜  ~ redis-cli -p 6380
    127.0.0.1:6380> get a
    "456"

    从上面的测试情况可以看出,将 slave-read-only 设置为 no 之后,从库也可以写数据了。但是主库一旦修改了这条数据,那么从库还会跟着主库变。


    说实话,这一块不要有太大别的想法,从库做好自己的事就好了,要是从库能够任意修改,那么数据一致性就会出现大问题了。

    故障切换

    除了提供读库的分摊流量职责之外,主从复制还有一个好处就是可以快速地进行主从切换,从而提高整个 Redis 实例的可用性,也就是我们常说的 主备架构 。将从机或者多个从机的某一台从机当做备份机,如果主机崩掉了,可以马上用备份机顶上。今天我们先来看看怎么手动实现主备切换。

    // 关闭6379
    ➜  brew services stop redis
    Stopping `redis`... (might take a while)
    ==> Successfully stopped `redis` (label: homebrew.mxcl.redis)
    
    // 查看6380状态
    ➜  redis-cli -p 6380
    127.0.0.1:6380> role
    1) "slave"
    2) "127.0.0.1"
    3) (integer) 6379
    4) "connect"
    5) (integer) -1
    
    // 6380服务端日志
    48041:S 22 Jun 2022 09:58:32.532 * Connecting to MASTER 127.0.0.1:6379
    48041:S 22 Jun 2022 09:58:32.532 * MASTER <-> REPLICA sync started
    48041:S 22 Jun 2022 09:58:32.532 # Error condition on socket for SYNC: Connection refused

    在上面的操作中,当我们关闭了 6379 ,也就是主库之后,从库的状态中,connect 变成了 -1 ,同时,服务实例输出的日志也出现了报错信息,表示当前的连接不可用。接下来,我们就赶紧把 6380 升级成主库吧。

    // 6380
    127.0.0.1:6380> slaveof no one
    OK
    127.0.0.1:6380> role
    1) "master"
    2) (integer) 2826
    3) (empty array)

    使用命令 SLAVEOF no one 就可以将当前的从库转回一台默认的普通实例,这时默认它就是主库,不过是一台还没有从库的主库而已。接下来,把 6381 转换成 6380 的从库。

    // 6381
    127.0.0.1:6381> slaveof 127.0.0.1 6380
    OK
    127.0.0.1:6381> role
    1) "slave"
    2) "127.0.0.1"
    3) (integer) 6380
    4) "connected"
    5) (integer) 2826

    这个就不多说了,当 6379 恢复之后,我们再让 6379 成为 6380 的主库就好了。


    应用程序上呢?这个真没办法,我们需要去修改连接信息,让程序去连接新的主库,从库的配置也要修改。是不是很麻烦?别急,下一篇我们就来学习自动切换的哨兵机制。

    总结

    今天的内容很简单,因为确实 Redis 在主从配置这一块非常方便。而且因为 Redis 本身就非常快,所以大部分情况下我们不需要像 MySQL 那样在新开从库的时候要等从库同步很久才能使用。它也是可以边提供写入服务,边进行从库同步的,如果确实主库的数据量非常大,需要进行全量同步从库的话,可以在主库使用 WAIT 命令进行锁写等待。


    好了,下一篇我们就要学习激动人心的主从高可用功能:哨兵机制 。别掉队哦,进阶系列的内容不多了,但后面可是篇篇精彩。


    参考文档:


    https://redis.io/docs/manual/replication/

    视频链接

    微信文章地址:https://mp.weixin.qq.com/s/Erj0RdhAkBFQF5vRdZHKlQ

    B站视频地址:https://www.bilibili.com/video/BV1Zj411Z7xT

    微信视频地址:https://mp.weixin.qq.com/s/tUgXKabwdMSYZ0mDnGvKnw

    搜索
    关注