引入

单点复制的问题

单机故障

  • 如果发生机器故障,例如磁盘损坏,主板损坏等,未能在短时间内修复好,客户端将无法连接redis。
    当然如果仅仅是redis节点挂掉了,可以进行问题排查然后重启,姑且不考虑这段时间对外服务的可用性,那还是可以接受的。
  • 而发生机器故障,基本是无济于事。除非把redis迁移到另一台机器上,并且还要考虑数据同步的问题。

容量瓶颈

  • 假如一台机器是16G内存,redis使用了12G内存,而其他应用还需要使用内存,假设我们总共需要60G内存要如何去做呢,是否有必要购买64G内存的机器?

QPS瓶颈

  • redis官方数据显示可以达到10w的QPS,如果业务需要100w的QPS怎么去做呢?
  • 关于容量瓶颈和QPS瓶颈是redis分布式需要解决的问题,而机器故障就是高可用的问题了。

解决方法

在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器(主从模式),满足故障恢复和负载均衡的需求。

  • 主(Master)和从(Slave)分别部署在不同的服务器上,当主节点服务器写入数据时,同时也会将数据同步到从节点服务器。
  • 通常情况下,主节点负责写入数据,从节点负责读出数据

redis也是如此,它为我们提供了复制功能,实现了相同数据的多个redis副本。(复制功能是高可用redis的基础,哨兵和集群都是在复制的基础上实现高可用的)

但是这样会有如下问题,多个副本之间的数据如何保持一致呢?数据读写操作可以发送给所有实例呢?

实际上,Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

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

即:

单机redis性能测试 单机redis qps_单机redis性能测试

  • redis主机会一直将自己的数据复制给redis从机,从而实现主从同步
  • 在这个过程中,只有master主机可以执行写命令,其他slave从机只能执行读命令
  • 这种读写分离模式可以大大减轻redis主机的数据读取压力,从而提高了redis的效率,并同时提供了多个数据备份
  • 主从模式是搭建Redis Cluster 集群最简单的一种方式

配置

  • 参与复制的redis实例可以划分为主节点(master)和从节点(slave)
  • 默认情况下,redis都是主节点。
127.0.0.1:6379> info replication  
# Replication
role:master              --默认情况下每个节点都是master节点
connected_slaves:0
master_replid:b4c7335839af0228e8f3318baf68c7a38c29c5f1
master_replid2:93e727752d0ea5557a89dfb2bf6d78dfe6c437c2
master_repl_offset:1050
second_repl_offset:1051
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:407
repl_backlog_histlen:644
  • 每个从节点只能有一个主节点,而主节点可以同时有多个从节点
  • 复制的数据流是单向的,只能由主节点复制到从节点

建立复制

配置复制的方式有如下三种

  • 在配置文件中加入slaveof {masterHost} {masterPort} 随Redis启动生效
# 配置主节点的IP和端口号
slaveof 127.0.0.1 8000
# 从节点只做读的操作,保证主从数据的一致性
slave-read-only yes
  • 在redis-server启动命令后加入–slaveof {masterHost} {masterPort}生效。
  • 直接使用命令: slaveof {masterHost} {masterPort}生效。

综上所述, slaveof命令在使用时, 可以运行期动态配置, 也可以提前写到配置文件中。

  • 例如本地启动两个端口为6379和6380的Redis节点, 在127.0.0.1:6380执行如下命令:
127.0.0.1:6380>slaveof 127.0.0.1 6379
  • slaveof配置都是在从节点发起, 这时6379作为主节点, 6380作为从节点。 复制关系建立后执行如下命令测试:
127.0.0.1:6379>set hello redis
OK
127.0.0.1:6379>get hello
"redis"
 
127.0.0.1:6380>get hello
"redis"

从运行结果中看到复制已经工作了, 针对主节点6379的任何修改都可以同步到从节点6380中, 复制过程如图所示。

单机redis性能测试 单机redis qps_数据库_02


slaveof本身是异步命令,执行slave命令时,节点只保存主节点信息后返回,后继复制流程在节点内部异步执行。主从节点复制成功建立后,可以使用info replication查看复制相关状态

  • 主节点6379复制状态信息:
[root@localhost 6379]# export PATH=$PATH:/usr/local/redis/bin/
[root@localhost 6379]# redis-cli -h  127.0.0.1 -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=1260,lag=1
....
  • 从节点6380复制状态信息:
[root@localhost ~]# export PATH=$PATH:/usr/local/redis/bin/
[root@localhost ~]# redis-cli -h 127.0.0.1 -p 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:7
master_sync_in_progress:0
...

断开复制

slaveof命令不但可以建立负责,还可以在从节点执行slaveof no one来断开和主节点的复制关系。比如在6380节点上执行断开复制:

单机redis性能测试 单机redis qps_数据_03


断开复制的主要流程:

  • 断开与主节点复制关系
  • 从节点晋升为主节点

从节点断开复制之后并不会抛弃原有数据,只是无法再获取从节点上的数据变化

通过slaveof {newMasterIp} {newMasterPort}还可以实现切主操作,也就是把当前从节点对主节点的复制切换到另一个主节点

单机redis性能测试 单机redis qps_单机redis性能测试_04


从节点通过slave of切换新的主节点,切主操作流程如下:

  • 断开与旧主节点复制关系
  • 与新主节点建立复制关系
  • 删除从节点当前所有数据
  • 对新主节点进行复制操作
从节点slaveof no one之后再重新的主节点建立复制关系,从节点日志如下:
21609:S 08 May 2020 08:37:59.236 * Full resync from master: dbddc80fe41195ca35706a8a7bb396cde1bf1753:2548
21609:S 08 May 2020 08:37:59.237 * Discarding previously cached master state.
21609:S 08 May 2020 08:37:59.290 * MASTER <-> REPLICA sync: receiving 204 bytes from master
21609:S 08 May 2020 08:37:59.290 * MASTER <-> REPLICA sync: Flushing old data  -- 删除从节点当前所有数据

运维提示:切主后从节点会清空之前所有的数据, 线上人工操作时小心slaveof在错误的节点上执行或者指向错误的主节点。

安全性

  • 对于数据比较重要的节点,主节点会通过设置requirepass参数进行密码验证,这时所有的客户端访问必须使用auth命令进行校验
  • 从节点与主节点的复制连接是通过一个特殊标识的客户端来完成的,因此需要配置从节点的masterauth参数与主节点密码保持一致。这样从节点才可以正确连接到主节点并发起复制流程

只读

  • 默认情况下,从节点使用slave-read-only=yes配置为只读模式。
  • 由于配置只能从主节点到从节点,对于从节点的任何修改,主节点都无法感知,修改从节点会造成主从数据不一致。建议线上不需修改从节点的只读模式
127.0.0.1:6379> set key hellow
(error) READONLY You can't write against a read only replica.

网络延迟

主从节点一般部署在不同机器上,复制时的网络延迟就成为需要考虑的问题。redis为我们提供了repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY,默认关闭,说明如下:

  • 当关闭时,主节点产生的命令数据无论大小都会及时的发送给从节点,这样主从之间的延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景, 如同机架或同机房部署。
  • 当开启时,主节点会合并较小的TCP数据报从而节省网络带宽。默认发送时间间隔取决于Linux内核,一般默认为40ms,这种配置节省了带宽但是增大了主从之间的延迟。适用于主从主从网络环境复杂或带宽紧张的场景, 如跨机房部署

部署主从节点时需要考虑网络延迟、 带宽使用率、 防灾级别等因素, 如要求低延迟时, 建议同机架或同机房部署并关闭repl-disable-tcp-nodelay。如果考虑高容灾性, 可以同城跨机房部署并开启repl-disable-tcp-nodelay。

实践

使用命令实现

方法1

  • 使用命令在服务端搭建主从模式,其语法格式如下:
redis-server --port <slave-port> --slaveof <master-ip> <master-port>
  • 执行以下命令:
#开启开启一个port为6300的从机,它依赖的主机port=6379
> redis-server --port 6300 --slaveof 127.0.0.1 6379

单机redis性能测试 单机redis qps_数据库_05

  • 接下来开启客户端,并执行查询命令,如下所示:
> redis-cli -p 6300
127.0.0.1:6300> get name
"jack"
127.0.0.1:6300> get website
"www.biancheng.net"
#不能执行写命令
127.0.0.1:6300> set myname BangDe
(error) READONLY You can't write against a read only slave.
127.0.0.1:6300> keys *
1) "myset:__rand_int__"
2) "ID"
3) "title"
4) "course2"

从上述命令可以看出,port =6300 的主机,完全备份了主机的数据,它可以执行查询命令,但不能执行写入命令

从机服务端提示如下:

[18160] 20 Jan 17:40:34.101 # Server initialized #服务初始化
[18160] 20 Jan 17:40:34.108 * Ready to accept connections #准备连接
[18160] 20 Jan 17:40:34.108 * Connecting to MASTER 127.0.0.1:6379 #连接到主服务器
[18160] 20 Jan 17:40:34.109 * MASTER <-> REPLICA sync started #启动副本同步
[18160] 20 Jan 17:40:34.110 * Non blocking connect for SYNC fired the event.#自动触发SYNC命令,请求同步数据
[18160] 20 Jan 17:40:34.110 * Master replied to PING, replication can continue...
[18160] 20 Jan 17:40:34.112 * Partial resynchronization not possible (no cached master)
[18160] 20 Jan 17:40:34.431 * Full resync from master: 6eb220706f73107990c2b886dbc2c12a8d0d9d05:0
[18160] 20 Jan 17:40:34.857 * MASTER <-> REPLICA sync: receiving 6916 bytes from master #从主机接受了数据,并将其存在于磁盘
[18160] 20 Jan 17:40:34.874 * MASTER <-> REPLICA sync: Flushing old data #清空原有数据
[18160] 20 Jan 17:40:34.874 * MASTER <-> REPLICA sync: Loading DB in memory #将磁盘中数据载入内存
[18160] 20 Jan 17:40:34.879 * MASTER <-> REPLICA sync: Finished with success #同步数据完成

可以看出主从模式下,数据的同步是自动完成的,这个数据同步的过程,又称为全量复制

方法2

  • 启动一个服务端,并指定端口号
#指定端口号为63001,不要关闭
redis-server --port 63001
  • 打开一个客户端,连接服务器,如下所示:
# 连接port=63001的服务器
>redis-cli -p 63001
// 现在处于主机模式下,所以运行读写数据
127.0.0.1:63001> keys *
1) "name"
127.0.0.1:63001> get name
"aaaa"
127.0.0.1:63001> set name "bbbb"
OK
127.0.0.1:63001> get name
"bbbb"
127.0.0.1:63001> 

# 将当前服务器设置为从服务器,从属于6379
127.0.0.1:63001> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:63001> keys *   
1) "master_6379"    # 现在它的数据和主服务器的数据一样了
#写入命令执行失败
127.0.0.1:63001> SET mywebsite www.biancheng.net
(error) READONLY You can't write against a read only replica.
#再次切换为主机模式,执行下面命令
127.0.0.1:63001> SLAVEOF no one
OK
127.0.0.1:63001> SLAVEOF no one
OK
127.0.0.1:63001> keys *
1) "master_6379"       # 数据还是和之前主服务器的一样
127.0.0.1:63001> SET mywebsite www.biancheng.net  # 但是现在可以写入命令了
OK
127.0.0.1:63001> keys *
1) "mywebsite"
2) "master_6379"

修改配置文件实现

每个 Redis 服务器都有一个与其对应的配置文件,通过修改该配置文件也可以实现主从模式。

  • 新建 redis_6302.conf 文件,并添加以下配置信息:
slaveof 127.0.0.1 6379 #指定主机的ip与port
port 6302 #指定从机的端口
  • 启动 Redis 服务器,执行以下命令:
$ redis-server redis_6302.conf
  • 客户端连接服务器,并进行简单测试。执行以下命令:
$ redis-cli -p 6302
127.0.0.1:6300> HSET user:username biangcheng
#写入失败
(error) READONLY You can't write against a read only slave.

对比:

  • 通过命令搭建主从模式,简单又快捷,所以不建议使用修改配置文件的方法。

主从拓扑结构

Redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为三种:一主一从、一主多从、树状主从结构。

一主一从

  • 一主一从是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持
  • 当应用写命令并发量较高而且需要持久化时,可以只在从节点开启AOF,这样即保证了数据安全性同时也避免了持久化对主节点的性能干扰
  • 但需要注意的是, 当主节点关闭持久化功能时,如果主节点脱机要避免自动重启操作。 因为主节点之前没有开启持久化功能自动重启后数据集为空, 这时从节点如果继续复制主节点会导致从节点数据也被清空的情况, 丧失了持久化的意义。 安全的做法是在从节点上执行slaveof no one断开与主节点的复制关系, 再重启主节点从而避免这一问题

单机redis性能测试 单机redis qps_数据库_06

一主多从

  • 一主多从结构(又称为星形拓扑结构) 使得应用端可以利用多个从节点实现读写分离(见图) 。 对于读占比较大的场景, 可以把读命令发送到从节点来分担主节点压力
  • 同时在日常开发中如果需要执行一些比较耗时的读命令, 如: keys、 sort等, 可以在其中一台从节点上执行, 防止慢查询对主节点造成阻塞从而影响线上服务的稳定性。
  • 对于写并发量较高的场景, 多个从节点会导致主节点写命令的多次发送从而过度消耗网络带宽, 同时也加重了主节点的负载影响服务稳定性

树状主从结构

  • 树状主从结构(又称为树状拓扑结构) 使得从节点不但可以复制主节点数据, 同时可以作为其他从节点的主节点继续向下层复制
  • 通过引入复制中间层, 可以有效降低主节点负载和需要传送给从节点的数据量。
  • 如图所示, 数据写入节点A后会同步到B和C节点, B节点再把数据同步到D和E节点, 数据实现了一层一层的向下复制。
  • 当主节点需要挂载多个从节点时为了避免对主节点的性能干扰, 可以采用树状主从结构降低主节点压力

主从模式不足

主从模式并不完美,它也存在许多不足之处,下面做了简单地总结:

  • redis主从模式不具备自动容错和恢复功能,如果主节点宕机,redis集群将无法工作,此时需要人为干预,将从节点提升为主节点
  • 如果主机宕机前有一部分数据未能及时同步到从机,及时切换主机后也会造成数据不一致的问题,从而降低了系统的可用性
  • 因为只有一个主节点,所以其写入能力和存储能力都受到一定的限制
  • 在进行数据全量同步时,如果同步的数据量较大可能会造成卡顿的现象

虽然主从模式存在上述不足,但他仍然是实现分布式集群的基础。Sentinel哨兵模式同样是依赖于主从模式实现