Redis系列(2)—主从读写分离

Redis 主从架构

主从读写分离架构

Redis高可用性一般来说有两方面,一个是数据尽量少丢失,这个可以通过 AOF 和 RDB 来保证。另一个则是服务尽量少中断,不会出现单点故障,这个Redis的做法就是增加副本冗余,Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

缓存一般都是用来支撑读高并发的,Redis 单机并发最多可能也就几万QPS,如果想要支持更高的并发,也需要通过主从读写分离的模式来部署 Redis,这样从节点就可以支持横向扩展,来提高读的吞吐量。接下来就来实践Redis主从架构高可用。

主从读写分离:主库、从库都可以接收读操作,而写操作先到主库执行,然后主库将写操作同步给从库。

部署主从读写分离

假设我们要部署的主从架构包含一个 master 节点和两个 slave 节点,信息如下:

节点

IP

Master

172.17.0.2:6379

Slave

172.17.0.3:6379

Slave

172.17.0.4:6379

按照前一篇文章的部署步骤,在另两台虚拟机(172.17.0.3/172.17.0.4)中各搭建一个 Redis。

注意有两个地方的IP需要更改:

  • /etc/redis/6379.conf 配置文件中的 bind <本机IP>
  • /etc/init.d/redis_6379 脚本中 shutdown 制定的IP改为本机IP

接下来只需要在从节点的配置文件中修改一项配置即可:

# vim /etc/redis/6379.conf

将注释掉的 replicaof 打开,并配置master节点的IP和端口:

replicaof 172.17.0.2 6379

然后重启 redis 即可:

# cd /etc/init.d

# ./redis_6379 stop

# ./redis_6379 start

之后就可以做些测试,可以看到数据已经从主节点复制过来了,并且只能读取不能写入。

[root@centos-02 /]# redis-cli -h 172.17.0.3
172.17.0.3:6379> get name
"bojiangzhou"
172.17.0.3:6379> set age 20
(error) READONLY You can't write against a read only replica.
172.17.0.3:6379

除了修改配置文件之外,也可以直接执行 replicaof <ip> <port> 命令来形成主库和从库的关系(Redis 5.0 之前使用 slaveof)。

主从搭建完成之后,可在主服务器上通过 info replication 命令查看连接的从服务器。

强制读写分离

配置了 replicaof 后,默认就是强制读写分离的,只接收读请求,拒绝写请求。

replica-read-only yes

一般不建议修改这个配置,如果主库和从库都可以接收写请求,那么最直接的一个问题就是客户端对同一个key多次修改,可能会落到不同的库上,那么数据副本一致性就是一个问题。

集群安全认证

如果要开启集群间的安全认证,首先需在 master 实例的配置文件中设置密码:

requirepass <密码>

然后在 slave 实例的配置文件中配置认证密码:

masterauth <密码>

集群压测

如果要对搭建好的redis做一个基准的压测,看下redis的性能和QPS,可以使用redis提供的 redis-benchmark压测工具。此工具在安装目录下:
/usr/local/src/redis-6.2.5/src。

redis-benchmark 的基本用法如下

[root@centos-01 src]# ./redis-benchmark --help
Usage: redis-benchmark [-h <host>] [-c <clients>] [-n <requests>] [-d <size>]

 -h <hostname>      Server hostname (default 127.0.0.1)
 -p <port>          Server port (default 6379)
 -a <password>      Password for Redis Auth
 -c <clients>       Number of parallel connections (default 50)
 -n <requests>      Total number of requests (default 100000)
 -d <size>          Data size of SET/GET value in bytes (default 3)

例如测试 10000 并发,100万次请求,这个可以根据系统高峰时期的用户访问量来调整。

由于客户端连接会非常多,需调整下redis的最大连接数配置:

maxclients 20000

在 master 节点上做一次压测:

  • set:QPS 为 84623,平均每条命令执行时间59.4毫秒
  • get:QPS 为 88261,平均每条命令执行时间56.4毫秒
[root@centos-01 src]# ./redis-benchmark -h 172.17.0.2 -p 6379 -c 10000 -n 1000000

====== SET ======                                                      
Summary:
  throughput summary: 84623.84 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
       59.473    34.656    59.359    69.439    85.567   110.399
       
====== GET ======                                                      
Summary:
  throughput summary: 88261.25 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
       56.486    23.408    56.127    62.623    68.671    94.783

也就是说Redis单节点读 QPS 在8万多,如果想再提高读吞吐量,就可以部署主从架构,横向扩展读节点,比如再部署两个 slave 节点,集群的读 QPS 就可以达到20几万,读QPS是随着节点数线性增长的。

不过Redis的吞吐量和服务器本身的性能、配置,以及生产环境的网络等都有关系,不同的环境,需要针对性的去测试,然后调整节点数。

主从复制原理

Master 必须开启持久化

如果采用主从架构,Master 节点必须开启数据持久化,并且必须要做好数据备份,在数据丢失时做恢复。如果 master 没有开启数据持久化,那 master 的数据都在内存中,重启后数据就没了,master 就会将空的数据集同步到 slave 节点上,所有 slave 节点的数据也会被清空。

所以 master 节点一定要开启 AOF 和 RDB 持久化,可参考 Redis系列(1) — 单机版安装及数据持久化

主从同步原理

我们在配置文件中配置了 replicaof 参数,这样在启动时,或者直接在客户端执行 replicaof 命令时,slave 节点会发送一个 PSYNC 命令给 Master 节点,表示要进行数据同步。

psync 命令的格式为:psync {runID} {offset}。

  • runID:每个 Redis 实例启动时都会自动生成一个随机 ID,用来唯一标记这个实例。runID 表示 master 节点的运行ID。
  • offset:表示复制的偏移量,也就是接收数据量的字节数,master 和 slave 节点都会维护一个 offset,master 写入 N 字节的命令,偏移量就会加 N;slave 接收了 N 字节的数据,slave 的偏移量就会加上 N。

首次同步的步骤:

  • 1、首次同步时,slave 节点会发送 psync ? -1,runID为 ? 是因为首次不知道 master 的 runID;offset 为 -1 表示第一次复制。
  • 2、master 收到 psync 命令后,会响应命令 +FULLRESYNC {runID} {offset} 给 slave,FULLRESYNC 表示第一次复制采用全量复制,runID 表示 master 的唯一标识,offset 表示 master 当前的偏移量。slave 收到响应后,会记录下这两个参数。
  • 3、然后 master 执行 bgsave 命令,生成 RDB 文件,接着将文件发给 slave。slave 接收到 RDB 文件后,为了避免之前数据的影响,会先清空当前数据库,然后再加载 RDB 文件,这样就同步到 master 创建 RDB 文件的时刻了。如果 slave 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
  • 4、在 master 将数据同步给 slave 的过程中,master 不会阻塞,仍然会接收写请求,否则 Redis 就无法提供服务了。这个阶段的写命令会写入 slave 连接客户端的缓冲区中(replication buffer)。
  • 5、最后 RDB 文件发送完成后,master 就会把缓冲区中的命令发给 slave,slave 再重放这些命令,并更新偏移量 offset。至此,master-slave 就完成首次数据同步了。
  • 6、一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,master 会通过这个连接将后续收到的命令操作再同步给 slave,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。

需注意,replication buffer 其实就是客户端连接的缓冲区,无论是客户端还是从库,都是一个 client,每个 client 连上 Redis 后,Redis 都会分配一个 client buffer。所有数据交互都是通过这个 buffer 进行的,redis 先把数据写入这个 buffer 中,然后再把 buffer 中的数据发到 client socket 中再通过网络发送出去,这样就完成了数据交互。只不过在主从同步时,这个 client buffer 专门用来传播用户的写命令到从库,所以通常叫做 replication buffer。

这块 buffer 区域的大小通过下面这个参数来控制,超过这个阈值后,主库就会强制断开这个 client 的连接。

# The limit can be set differently for the three different classes of clients:
#
# normal -> normal clients including MONITOR clients
# replica  -> replica clients
# pubsub -> clients subscribed to at least one pubsub channel or pattern
#
# The syntax of every client-output-buffer-limit directive is the following:
#
# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

断线增量复制

第一次全量同步完成后,就会基于长连接来进行命令传播,这个过程就可能会出现网络断连或阻塞的情况,这时就无法进行命令传播了,那么主从库的数据就可能不一致了。

在 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,从库就会和主库重新进行一次全量复制,开销非常大。

从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步,下面就是增量复制的步骤。

  • 1、master 在命令同步时,不仅会发给 slave,还会写入一个 repl_backlog_buffer 的复制积压缓冲区中,还记录了每个字节对应的偏移量。
  • 2、刚开始,master 将命令同步给各个 slave,offset 基本都是一致的。如果某个 slave 与 master 断开连接后,master 还会接收写请求,所以 master 的 offset 可能会继续增大,而断开连接的 slave 的 offset 就会小于 master 了,这样数据就不一致了。
  • 3、当网络连接恢复后,slave 会发一个 psync {runID} {offset} 命令给 master,runID 表示 master 的唯一标识,offset 表示 slave 当前复制的偏移量。
  • 4、如果 slave 传过来的 offset 在还在缓冲区中,则向 slave 返回 CONTINUE 命令,表示增量复制。如果不存在或者 runID 不一致,则返回 +FULLRESYNC {runID} {offset} 命令,表示全量复制。
  • 5、master 收到 psync 命令后,这时就会将 backlog 缓冲区中 master offset 和 slave offset 偏移量之间的字节发送给客户端。然后客户端执行这些命令,实现断连后的增量同步。

复制积压缓冲区

可以看出,增量复制主要用到了主从库的 offset 和复制积压缓冲区来实现。主从库的 offset 一致则表示数据一致,否则就是不一致。而复制积压缓冲区是由主库维护的一个固定长度先进先出(FIFO) 队列,默认大小为1MB。

这里就会有一个问题,由于复制积压缓冲区是一个固定大小的环形缓冲区,那么可能在 slave 重连后,未同步的数据可能被覆盖了,即 slave_offset+1 之后的数据在缓冲区中已经不存在了,这时就会执行全量同步。否则就执行增量同步,将 slave_offset+1 到 master_offset 之间的数据同步给 slave。

如果 master 会执行大量写入命令,或者 slave 断开连接后重连时间较长,那么复制积压缓冲区很快就会被覆盖,就会导致 slave 重连后执行全量复制,全量复制就会影响主库的性能,所以默认的 1MB 大小可能并不合适。

复制积压缓冲区的大小可以根据公式 seconds * write_size_per_second * 2 来计算,seconds 表示断开连接重连的平均时间,write_size_per_second 表示服务器平均每秒产生的写命令数据量,为了应对突发压力,再扩大一倍。参数配置如下。

repl-backlog-size 1mb
复制代码

服务器运行ID

断线重连还用到了服务器运行ID(runID),每个 Redis 在启动时会自动生成运行ID,它是一个40位长度的字符串。

在服务器上可通过 info server 命令查看:

在 slave 初次连接 master 复制时,master 会把 runID 传给 slave 保存起来,slave 断线重连的时候,会把这个 runID 传给 master。master 会验证这个 runID 和自身的 runID 是否一致,一致则说明 slave 断线之前就是连接的自己,则进行增量复制。否则说明 slave 之前连接的 master 不是自己,master 将对 slave 执行全量同步。

RDB 复制超时

每次全量同步时,主服务器要执行bgsave命令生成RDB文件,并将RDB文件传输到从服务器。因此全量同步是一个非常耗费资源和耗时的操作,如果 RDB 复制时间超过60秒,那么 slave 就会认为复制失败。RDB 文件比较大时,传输时间就可能会超过 60秒,可以适当调大这个参数。

repl-timeout 60

无磁盘化复制

默认情况下,生成的 RDB 文件会落到磁盘,并从磁盘复制到 slave。Redis可以配置无磁盘化复制,开启后,RDB 文件会生成在内存中,然后等待一段时间,这样就可以合并多个 slave 的同步请求,使用同一份 RDB 文件。之后在 RDB 文件传输期间,新的slave同步请求就会进入阻塞队列排队等待。

无磁盘化复制在 master 节点磁盘性能较低,slave 节点数量很多的情况下的性能是更好的,配置如下。

# 是否开启无磁盘化复制
repl-diskless-sync no

# 无磁盘化开启后,延迟多久发送 RDB 文件
repl-diskless-sync-delay 5

心跳检测

在命令传播阶段,主从节点会互相发送心跳检查,master 默认每隔10秒发送一次心跳给 slave,salve 默认每隔1秒发送一个心跳给 master。心跳命令格式为:REPLCONF ACK {slave_offset},slave_offset 表示 slave 当前复制的偏移量。

发送心跳主要有如下几个作用:

1、检测主从服务器的网络连接状态

通过发送心跳,主服务器可以知道主从服务器间的网络连接是否正常,如果 master 超过1秒没有收到 slave 的心跳命令,那么 master 就会知道主从间的网络可能出了问题。

可以通过 info replication 命令查看 slave 节点列表,正常情况下,lag 的值等于 0 或者 1,如果超过 1秒的话,说明主从服务器之间的连接出现了故障。

2、控制 master 停止写入

Redis 有两个配置需要借助心跳检测来实现:

# slave 的数量
min-replicas-to-write 0

# slave心跳延迟时间(lag)
min-replicas-max-lag 10

min-replicas-max-lag 表示多久没有接收到 slave 的心跳后(lag),就认为 salve 故障,默认为 10秒。

min-replicas-to-write 表示健康的 slave 数量,即每个 slave 的 lag 小于等于 min-replicas-max-lag 认为是健康的,否则是不正常的。当超过 min-replicas-to-write 数量的 slave 不正常时,master 会停止接收写请求,避免没有足够健康的 slave,导致数据丢失。

min-replicas-to-write 默认值为0,表示关闭这个特性,可以设置大于0的值来开启这个特性。

3、检测命令丢失

如果因为网络故障,master 传播给 slave 的写命令在半路丢失,那么当 slave 向 master 发送 REPLCONF ACK {slave_offset} 命令时,master 将发现 slave 当前的复制偏移量少于自己的复制偏移量,然后 master 就会根据 slave 提交的复制偏移量,在复制积压缓冲区里面找到 slave 缺少的数据,并将这些数据重新发送给 slave。

主从从级联模式

每次执行全量同步,对于主库来说,有两个耗费资源和性能的操作:

  • 执行BGSAVE命令,会 fork 子进程生成 RDB 文件,fork 这个操作会阻塞主线程处理正常请求,这个生成操作会耗费服务器大量的CPU、内存和磁盘I/O资源,从而导致主库响应客户端的请求速度变慢。
  • 接着主服务器还要将 RDB 文件发送给从服务器,这个操作会耗费主服务器大量的网络带宽资源(流量),并对主服务器响应客户端命令的时间产生影响。

因此全量同步是一个非常耗费资源和耗时的操作,所以要尽量让Redis在必要的时候才执行全量同步操作,这个可以通过上面介绍的一些配置来进行调优。

除此之外,如果从库数量很多,而且都要和主库进行全量复制的话,这会给主库的资源使用带来很大的压力。这时可以使用主 - 从 - 从的级联模式来分担主库压力,通过主 - 从 - 从模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。

在部署主从集群的时候,可以手动选择一个从库(内存资源配置较高的从库),用于级联其他的从库。


作者:bojiangzhou
链接:
https://juejin.cn/post/7033001311180488735

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值