RedisRedis 主从复制 + 读写分离

Posted 没对象的指针

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RedisRedis 主从复制 + 读写分离相关的知识,希望对你有一定的参考价值。

Redis 主从复制 + 读写分离

1. Redis 主从复制 + 读写分离介绍

1.1 从数据持久化到服务高可用

Redis 的数据持久化技术,可以解决机器宕机,数据丢失的问题,并没有从根本上解决 Redis 的可用性。我们需要的是解决 Redis 的高可用,减少甚至避免 Redis 服务发生宕机的可能,来从根本上解决数据丢失问题。

如果对 Redis 如何进行数据持久化不是很了解的同学,可以读一下:Redis 数据持久化:AOF和RDB

1.2 主从复制

目前实现Redis高可用的模式主要有三种: 主从模式、哨兵模式、集群模式。这篇文章我们来一起学习主从模式。

Redis 提供的主从模式,是通过复制的方式,将主服务器上的 Redis 的数据同步复制一份到从 Redis 服务器,这种做法很常见,mysql 的主从也是这么做的。

主节点的 Redis 我们称之为 master,从节点的 Redis 我们称之为 slave,主从复制为单向复制,只能由主到从,不能由从到主。可以有多个从节点,比如1主2从甚至n从,从节点的多少根据实际的业务需求来判断。

1.3 如何保证主从数据一致性?

采取读写分离的模式:

  • 读操作:主、从库都可以执行,一般是在从库上读数据,对实时性和准确性有100%高真要求的部分业务,可以谨慎评估之后读主库;

  • 写操作:只在主库上写数据,写完之后将写操作指令同步到从库。

1.4 为何采用读写分离模式?

你可以设想一下,不管是主库还是从库,都能接收客户端的写操作。那么,一个直接的问题就是:如果客户端对同一个数据(例如 k1)前后修改了三次,每一次的修改请求都发送到不同的实例上,在不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了(分别是 v1、v2 和 v3)。在读取这个数据的时候,就可能读取到旧的值

如果我们非要保持这个数据在三个实例上一致,就要涉及到加锁、实例间协商是否完成修改等一系列操作,但这会带来巨额的开销,当然是不太能接受的。

而主从库模式一旦采用了读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。

那么,主从库同步是如何完成的呢?主库数据是一次性传给从库,还是分批同步?要是主从库间的网络断连了,数据还能保持一致吗?下面我们来一起学习。

2. 一主两从环境准备

2.1 配置文件

  1. 创建目录
mkdir -p /data/redis/master/data
mkdir -p /data/redis/slave1/data
mkdir -p /data/redis/slave2/data
  1. 主配置文件:
bind 0.0.0.0
port 6379
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis1.log"
dbfilename "pointer1.rdb"
dir "/usr/local/redis/data"
appendonly yes
appendfilename "appendonly1.aof"
masterauth "123456"
  1. 两个从配置文件:
bind 0.0.0.0
port 6380
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis2.log"
dbfilename "pointer2.rdb"
dir "/usr/local/redis/data"
appendonly yes
appendfilename "appendonly2.aof"

# 从本机 6379 的 redis 实例复制数据,Redis 5.0 之前使用 slaveof
replicaof 8.129.113.233 6379
# 从节点开启只读模式(默认)
replica‐read‐only yes
# 从节点访问主节点的密码,和 requirepass ⼀样
masterauth "123456"
bind 0.0.0.0
port 6381
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis3.log"
dbfilename "pointer3.rdb"
dir "/usr/local/redis/data"
appendonly yes
appendfilename "appendonly3.aof"

# 从本机 6379 的 redis 实例复制数据,Redis 5.0 之前使用 slaveof
replicaof 8.129.113.233 6379
# 从节点开启只读模式(默认)
replica‐read‐only yes
# 从节点访问主节点的密码,和 requirepass ⼀样
masterauth "123456"

2.2 启动 Redis

# 启动主
./redis-server /data/redis/master/data/redis.conf
# 启动从
./redis-server /data/redis/slave1/data/redis.conf
# 启动从
./redis-server /data/redis/slave2/data/redis.conf

3. 主从复制原理

主从复制分两种:主从刚连接的时候,进⾏全量同步;全量同步结束后,进⾏增量同步

  • 全量同步 - 首次配置完成,主从库连接之后;

  • 增量同步 - 全量同步结束后

    • 准实时同步 - 主从正常运行期间;
    • Append增量数据 + 准实时同步 - 主从库间网络断开重连。

3.1 全量同步

主从库第一次复制过程大体可以分为 3 个阶段:准备阶段(即建立连接准备)、主库同步数据到从库阶段、发送同步期间增量指令到从库的阶段。

3.1.1 建立连接

这个阶段的主要作用是建立主从之间的连接,连接成立之后,才能够做数据全量同步。主要包含如下步骤:

  1. 从节点的配置文件中的 replicaof 配置项中配置了主节点的 ip 和 port ,配置完成之后,从节点就知道要跟哪个主节点进行连接;

  2. 当连接成功之后,从库开启 replicaof 操作,同时发送 psync 指令告诉主库,我准备开始同步了。命令包含了主库的 runID 和 复制进度 offset 两个参数;

    • runID:每个 Redis 实例启动都会自动生成一个唯一标识 ID,第一次主从复制,还不知道主库 runID,所以参数会默认设置为:?;
    • offset:因为第一次复制,没有偏移量,所以默认设置为 -1,这样就默认从第1条指令开始复制;
  3. 主库收到 psync 命令后根据参数启动复制,使用 FULLRESYNC 响应命令,同时带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库;

  4. 从库收到响应后,记录下这两个参数。

3.1.2 主库同步数据给从库

  1. master 执行 bgsave 命令生成 RDB 文件,并将文件发送给从库,从库收到 RDB 文件后保存到磁盘,清空当前 Redis 库中的数据,再将 RDB 文件数据加载到内存中。

  2. 同时主库为每一个 slave 开辟一块 replication buffer 缓冲区记录,用于记录主库生成 RDB 文件后那段时间(那段时间的产生的写命令没有被记录到 RDB 文件中,但是主库又会源源不断的接收到新的请求指令,记录缓冲区是为了保证数据不丢失)产生的所有写指令。

3.1.3 发送新写命令到从库

  1. 主库同步数据给从库 整个初始化工作完成之后,继续执行从 replication buffer 缓冲区发送过来的数据,避免数据断层。

  2. 主数据同步到从库的过程中,主库不会被阻塞,可以正常处理其他任意操作,这也是 Redis 保证高性能的必备条件。

  3. replication buffer 缓冲区创建在 master 主库上,存放的数据是下面三个时间内 master 数据的所有写操作。

    • master 执行 bgsave 生产 rdb 的期间的写操作;
    • master 传输 rdb 文件到 slave 期间的写操作;
    • slave 加载 rdb 文件将数据初始化到内存期间的写操作。

三个步骤完成了 Redis 主从的全量复制。这边需要注意的是,Redis 中的通信,无论是主库跟从库之间,还是与客户端之间的数据交互。本质上都是通过分配内存 buffer 来进行的,Master 会先把数据写到 buffer 中,再通过网络发送出去,从而完成数据交互。

RDB 文件作为二进制文件,无论是网络传输还是写入时的磁盘IO,效率都要比 AOF 高很多。同样的,从库进行数据恢复的时候,效率也会高一些。所以我们会选择 RDB 文件做同步而不是 AOF 模式。

3.2 增量同步

3.2.1 主从网络断开之后的同步方式

在网络断开之后或者从实例服务故障恢复之后,主从库会采用增量复制的方式继续同步,而不是全量同步的模式,这样会大大降低开销,提升效率。

增量复制: 就是指网络中断或者从库重启等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。

3.2.2 repl_backlog_buffer

主从库重新连接之后可以实现增量复制。关键就在 repl_backlog_buffer 缓冲区上面。

因为 master 会将写指令操作记录在 repl_backlog_buffer 缓冲区中,并使用 master_repl_offset 记录 master 写入的位置偏移量,slave 则使用 slave_repl_offset 记录读的偏移量。master 新增写操作的时候,偏移量则会增加。从库持续执行同步的写指令后,slave_repl_offset 也会不断增加。一般情况下,这两个偏移量会保持同步,如下图左。

但是网络断开或者从库故障期间,主实例 Redis 一般会收到新的写操作命令,但从实例则暂停执行,所以 master_repl_offset 会大于 slave_repl_offset。如下图右。

需要注意的是, repl_backlog_buffer 并不是如图中显示的貌似无限队列的模式,而是一个类似环形数组,如果数组内容满了,就会从头开始覆盖前面的内容,因为给到的内存空间是有限的。

在主从之间重新连接之后,slave 会先发送 psync 命令给 master,同时将自己的 runID,slave_repl_offset 两个参数发送给 master。master 只需要把 master_repl_offset slave_repl_offset 之间的命令同步给从库即可。增量复制的流程类似如下:


在配置repl_backlog_buffer 的时候,需要综合考虑各种因素,太大了会导致增量执行周期比较长,还不如RDB全量覆盖。太小了有可能从库还没读取到就被 Master 的新写操作覆盖了,那样也只能执行全量复制。

所以我们需要给出一个合理缓冲区size。一般有如下的计算公式共参考:repl_backlog_buffer_size = seconds * write_size_per_second

  • seconds:正常情况下从库断开,到重连主库所需的平均时间,秒为单位。

  • write_size_per_second:主库平均每秒产生的写命令数据量大小。

如:主服务器大约每秒产生 0.5MB 的写指令数据,而断开到重连一般需要 30s,那么缓冲区的大小就是 0.5 * 30s = 15 MB。

但是我们一般会保留一点buffer,比如预留 0.5 倍,那就是 : 1.5 * 15 MB = 22.5 MB 。

3.2.3 基于长连接的命令传播

上面的工作都是为了完成完整复制,那在完成全量复制之后,主从开始进入正常有序的同步了,具体应该怎么做呢?

主从完成全量复制之后,他们之间需要保持连接。当主库收到操作指令的时候,通过这个连接同步给从库,这个过程称之为:基于长连接的命令传播

为了保证传播的有效性和稳定性,从节点采用心跳机制进行侦测,发送命令:PINGREPLCONF ACK

(1)主 -> 从:PING

每隔指定的时间(比如 1 分钟,可配置),主节点会向从节点发送 PING 命令,侦测从节点有无超时来判断从节点的健康情况。

(2)从 -> 主:REPLCONF ACK

命令执行传播的阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令,将复制的偏移量发送过去。

  • REPLCONF ACK <replication_offset>

replication_offset 的属性指的是当前从实例服务器的复制偏移量。

  • 从实例发送 REPLCONF ACK 命令对于主要实例,主要有以下作用:

    • 检测主从服务器的网络通路是否正常。

    • 辅助实现 min-slaves 选项,使用 Redis 的 min-slaves-to-write(少于n个从实例时,拒绝执行写命令) 和 min-slaves-max-lag(主从延迟大于等于n秒时,拒绝执行写命令)两个选项可以防止主服务器在不安全的情况下执行写命令。

    • 检测命令丢失, 从节点发送了 slave_replication_offset,主节点会对比 master_replication_offset ,如果不一致,说明从节点数据缺失,主节点会从 repl_backlog_buffer 缓冲区中找到并推送缺失的数据。

4. 总结

主从复制的作用一个是为分担读写压力,均衡负载,另一个是为了保证部分实例宕机之后服务的持续可用性,所以 Redis 演变出主从架构读写分离

主从复制的步骤包括:建立连接的阶段、数据同步的阶段、基于长连接的命令传播阶段

数据同步可以分为全量复制部分复制,全量复制一般为第一次全量或者长时间主从连接断开。

命令传播阶段主从节点之间有 PING(主到从的的探测) 和 `REPLCONF ACK(从到主的ack应答) 命令,这种互相确认心跳的模式保证数据同步的稳定性。

主从模式是比较低级的可用性优化,要做到故障自动转移,异常预警,高保活,还需要更为复杂的哨兵或者集群模式。

主从复制和读写分离

主从复制和读写分离

一.概述

 在企业应用中,成熟的业务通常数据量都比较大
 单台mysql在安全性、高可用性和高并发方面都无法满足实际需求
 配置多台主从数据库服务器以实现读写分离

二.主从复制原理

1.mysql复制类型

  • 基于语句的复制(sql)
  • 基于行的复制(记录)
  • 混合类型的复制

2.mysql主从复制的工作过程

  • 两个日志文件

  • 三个线程

  • 三个线程如何工作

 master:二进制日志文件
 dump线程

 slave:中继日志文件
 io线程
 sql线程

  • 主从复制主要是通过日志文件进行恢复
    master上的dump线程,把数据同步到二进制日志中

  • slave上的io线程,请求与master的二进制文件数据同步,从而进行同步到自己的中继日志中

  • slave上的sql线程,把自己中继日志中的文件,同步到slave数据库中

  • 三.Mysql读写分离

    1.读写分离原理

    • 只在主服务器上写,只在从服务器上读
    • 主数据库处理事务性查询,从数据库处理select查询
    • 数据库复制用于将事务性查询的变更同步到集群中的从数据库

    2.读写分离方案

    • 基于程序代码实现

    • 基于中间代理层实现

    • Mysql-Proxy

    • Amoeba

     Amoeba(中间代理层):
     控制着mysql集群中读和写的分配
     间接对接客户端
    

    3.读写分离存在的意义

     数据库的“写”操作比较耗时的
     数据库的“读”比较快
     读写分离解决的是:数据库的写入,影响了查询的效率
    

    4.什么时候要读写分离

    数据库不一定要读写分离,
    如果程序使用数据库比较多,更新少,查询多的情况下会考虑使用
    利用数据库主从复制,读写分离
    可分担数据库压力,提高性能
    

    四.Mysql主从复制和读写分离实验

    案例拓扑图:

    思路:

    读操作直接访问从服务器
    起到降低负载,负载均衡作用
    
    • 第一步:客户端client访问代理服务器amoeba

    • 第二步:代理服务器分配读写任务给后端集群

    • master负责写

    • slave负责读

    • 第三步:主服务器将增删改查写入自己的二进制日志

    • 第四步:从服务器将主服务器的二进制日志同步到自己中继日志

    • 第五步:从服务器将中继日志数据同步到自己数据库中

    主机操作系统ip地址所需工具/软件包
    Amoebacentos7192.168.133.10jdk-6u14-linux-x64.bin、amoeba-mysql-binary-2.2.0.tar.gz
    Mastercentos7192.168.133.20ntp 、 mysql-boost-5.7.20.tar.gz
    slave1centos7192.168.133.30ntp 、ntpdate 、 mysql-boost-5.7.20.tar.gz
    slave2centos7192.168.133.40ntp 、ntpdate 、mysql-boost-5.7.20.tar.gz
    客户端centos7192.168.133.50
    • 将所有主机的防火墙和安全机制关闭
    systemctl stop firewalld
    systemctl disable firewalld
    setenforce 0
    

    1.搭建Mysql主从复制

    1)Mysql主从服务器时间同步

    • Master服务器:192.168.133.20
    yum -y install ntp
    vim /etc/ntp.conf
    
    server 127.127.133.0					#设置本地是时钟源,注意修改网段
    fudge 127.127.133.0 stratum 8			#设置时间层级为8(限制在15内)
    
    service ntpd start
    

    末行添加:

    • slave1服务器:192.168.133.30
    • slave2服务器:192.168.133.40
    yum -y install ntp ntpdate
    
    service ntpd start
    
    /usr/sbin/ntpdate 192.168.163.11  #进行时间同步,指向Master服务器IP
    
    crontab -e
    */30 * * * * /usr/sbin/ntpdate 192.168.133.20
    


    2)主服务器的mysql配置

    Master服务器:192.168.133.20

    vim /etc/my.cnf
    server-id = 1
    log-bin=master-bin      #添加,主服务器开启二进制日志
    log-slave-updates=true  #添加,允许从服务器更新二进制日志
    
    systemctl restart mysqld
    


    mysql -u root -p
    #给从服务器授权
    grant replication slave on *.* to 'myslave'@'192.168.133.%' identified by '123456';
    flush privileges;
    
    show master status;
    #File 列显示日志名,Fosition 列显示偏移量
    

    3)从服务器的mysql配置

    slave1服务器:192.168.133.30
    slave2服务器:192.168.133.40

    vim /etc/my.cnf
    #修改,注意id与Master的不同,两个Slave的id也要不同
    server-id = 2
    #添加,开启中继日志,从主服务器上同步日志文件记录到本地
    relay-log=relay-log-bin
    #添加,定义中继日志文件的位置和名称
    relay-log-index=slave-relay-bin.index
    
    systemctl restart mysqld
    
    • 两台都要操作:
    mysql -u root -p
    #配置同步,注意 master_log_file 和 master_log_pos 的值要与Master查询的一致,
    change master to master_host='192.168.133.20' , master_user='myslave',master_password='123456',master_log_file='master-bin.000001',master_log_pos=604;
    
    start slave;					#启动同步,如有报错执行 reset slave;
    show slave status\\G				#查看 Slave 状态
    /确保 IO 和 SQL 线程都是 Yes,代表同步正常。
    Slave_IO_Running: Yes			#负责与主机的io通信
    Slave_SQL_Running: Yes			#负责自己的slave mysql进程
    
    

    • slave_Io_running:no的可能性:
    • 网络不通
    • my.cnf配置有问题
    • 密码、file文件名、pos偏移量不对
    • 防火墙没有关闭

    4)验证主从复制效果

    主服务器上进行执行: 
    create database test;
    


    2.搭建Mysql读写分离

    1)Amoeba服务器配置

    Amoeba服务器:192.168.133.10

    安装java环境

    因为 Amoeba 基于是 jdk1.5 开发的,所以官方推荐使用 jdk1.51.6 版本,高版本不建议使用。
    将jdk-6u14-linux-x64.bin 和 amoeba-mysql-binary-2.2.0.tar.gz.0 上传到/opt目录下。
    
    cd /opt/
    cp jdk-6u14-linux-x64.bin /usr/local/
    
    cd /usr/local/
    chmod +x jdk-6u14-linux-x64.bin
    ./jdk-6u14-linux-x64.bin
    按空格到最后一行
    按yes,按enter
    
    mv jdk1.6.0_14/ /usr/local/jdk1.6
    
    vim /etc/profile
    export JAVA_HOME=/usr/local/jdk1.6
    export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
    export PATH=$JAVA_HOME/lib:$JAVA_HOME/jre/bin/:$PATH:$HOME/bin
    export AMOEBA_HOME=/usr/local/amoeba
    export PATH=$PATH:$AMOEBA_HOME/bin
    
    source /etc/profile
    java -version
    






    安装Amoeba软件

    mkdir /usr/local/amoeba
    tar zxvf /opt/amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/
    chmod -R 755 /usr/local/amoeba/
    /usr/local/amoeba/bin/amoeba
    #如显示amoeba start|stop 说明安装成功
    

    2)主从服务器的mysql上授权

    • Master服务器:192.168.133.20
    • Slave服务器:192.168.133.30
    • Slave服务器:192.168.133.40

    先在Master、Slave1、Slave2 的mysql上开放权限给 Amoeba 访问

    grant all on *.* to 'test'@'192.168.133.%' identified by 'abc123';
    


    3)配置Amoeba读写分离,两个slave负载均衡

    Amoeba服务器:192.168.133.10

    ①修改amoeba配置文件

    cd /usr/local/amoeba/conf/
    
    cp amoeba.xml amoeba.xml.bak
    vim amoeba.xml           #修改amoeba配置文件
    #---------30修改------------------------------
    <property name="user">amoeba</property>
    #---------32修改------------------------------
    <property name="password">123456</property>
    #---------115修改-----------------------------
    <property name="defaultPool">master</property>
    #---------117去掉注释–------------------------
    <property name="writePool">master</property>
    <property name="readPool">slaves</property>
    

    ②修改数据库配置文件

    cp dbServers.xml dbServers.xml.bak
    
    vim dbServers.xml
    #---------23注释掉--------------------------------------
    作用:默认进入test库 以防mysql中没有test库时,会报错
    <!-- mysql schema
    <property name="schema">test</property>
    -->
    #---------26修改-----------------------------------------
    <!-- mysql user -->
    <property name="user">test</property>
    #---------28-30去掉注释----------------------------------
    <property name="password">abc123</property>
    #---------45修改,设置主服务器的名Master------------------
    <dbServer name="master"  parent="abstractServer">
    #---------48修改,设置主服务器的地址----------------------
    <property name="ipAddress">192.168.163.11</property>
    #---------52修改,设置从服务器的名slave1-----------------
    <dbServer name="slave1"  parent="abstractServer">
    #---------55修改,设置从服务器1的地址---------------------
    <property name="ipAddress">192.168.163.12</property>
    #---------58复制上面6行粘贴,设置从服务器2的名slave2和地址---
    <dbServer name="slave2"  parent="abstractServer">
    <property name="ipAddress">192.168.163.13</property>
    #---------修改后的6566修改-------------------------------------
    <dbServer name="slaves" virtual="true">
    #---------71修改----------------------------------------
    <property name="poolNames">slave1,slave2</property>
    
    /usr/local/amoeba/bin/amoeba start&	 #启动Amoeba软件,按ctrl+c 返回
    netstat -anpt | grep java		   	 #查看8066端口是否开启,默认端口为TCP 8066
    


    4)测试读写分离

    客户端:192.168.133.50
    在客户端服务器上进行测试:

    yum install -y mysql mysql-server
    
    mysql -u amoeba -p123456 -h 192.168.163.10 -P8066
    

    • 通过amoeba服务器代理访问mysql ,在通过客户端连接mysql后写入的数据只有主服务会记录,然后同步给从服务器在主服务器上
    use test;
    create table test (id int(10),name varchar(10),address varchar(20));
    

    • 两台从服务器:
    stop slave;			#关闭同步
    use test;
    


    在slave1:

    insert into test values('1','slave1','this_is_slave1');
    

    在slaves2上:

    insert into test values('2','slave2','this_is_slave2');
    

    在主服务器上:

    insert into test values('3','master','this_is_master');
    

    在客户端服务器上

    use test;
    select * from test;		
    #客户端会分别向slave1和slave2读取数据,显示的只有在两个从服务器上添加的数据,没有在主服务器上添加的数据
    
    insert into test values('4','client','this_is_client');		//只有主服务器上有此数据
    




    在两个服务器上执行 stat slave,可实现同步在主服务器上添加的数据:

    start slave;
    

    总结:

    主从复制:

    单台mysql无法满足需求
    配置多台主从数据库实现读写分离
    

    mysql复制类型:基于语句、基于行、混合复制

    主从复制原理:通过日志文件恢复的,
    master:二进制日志文件和dump线程
    slave:中继日志文件和IO线程、sql线程

    master通过dump线程将数据同步到二进制日志文件中
    slave通过io线程将数据与二进制日志文件同步,并同步到自己的中继日志文件中,slave通过sql线程将数据同步到自己的数据库中

    读写分离:

    基于程序代码、中间代理层实现 amoeba
    

    amoeba:控制mysql集群中读写分配
    解决:数据库的写入,查询的效率
    作用:分担数据库压力,提高性能
    原理:主服务器上写,从服务器上读

    • 客户端访问代理服务器amoeba,amoeba分配读写任务给后端集群
    • master复制写,slave负责读

以上是关于RedisRedis 主从复制 + 读写分离的主要内容,如果未能解决你的问题,请参考以下文章

redisredis复制(replica)

Redis-----初识Redis-----主从复制.读写分离,主从切换(哨兵机制)

Redis——主从做读写分离原理与优化

主从复制和读写分离

主从复制和读写分离

主从复制和读写分离