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 配置文件
- 创建目录
mkdir -p /data/redis/master/data
mkdir -p /data/redis/slave1/data
mkdir -p /data/redis/slave2/data
- 主配置文件:
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"
- 两个从配置文件:
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 建立连接
这个阶段的主要作用是建立主从之间的连接
,连接成立之后,才能够做数据全量同步。主要包含如下步骤:
-
从节点的配置文件中的 replicaof 配置项中配置了主节点的 ip 和 port ,配置完成之后,从节点就知道要跟哪个主节点进行连接;
-
当连接成功之后,从库开启 replicaof 操作,同时发送
psync
指令告诉主库,我准备开始同步了。命令包含了主库的 runID 和 复制进度 offset 两个参数;- runID:每个 Redis 实例启动都会自动生成一个唯一标识 ID,第一次主从复制,还不知道主库 runID,所以参数会默认设置为:?;
- offset:因为第一次复制,没有偏移量,所以默认设置为 -1,这样就默认从第1条指令开始复制;
-
主库收到 psync 命令后根据参数启动复制,使用
FULLRESYNC
响应命令,同时带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库; -
从库收到响应后,记录下这两个参数。
3.1.2 主库同步数据给从库
-
master 执行
bgsave
命令生成 RDB 文件,并将文件发送给从库,从库收到 RDB 文件后保存到磁盘,清空当前 Redis 库中的数据,再将 RDB 文件数据加载到内存中。 -
同时主库为每一个 slave 开辟一块
replication buffer
缓冲区记录,用于记录主库生成 RDB 文件后那段时间(那段时间的产生的写命令没有被记录到 RDB 文件中,但是主库又会源源不断的接收到新的请求指令,记录缓冲区是为了保证数据不丢失)产生的所有写指令。
3.1.3 发送新写命令到从库
-
在 主库同步数据给从库 整个初始化工作完成之后,继续执行从
replication buffer
缓冲区发送过来的数据,避免数据断层。 -
主数据同步到从库的过程中,主库不会被阻塞,可以正常处理其他任意操作,这也是 Redis 保证高性能的必备条件。
-
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 基于长连接的命令传播
上面的工作都是为了完成完整复制,那在完成全量复制之后,主从开始进入正常有序的同步了,具体应该怎么做呢?
主从完成全量复制之后,他们之间需要保持连接。当主库收到操作指令的时候,通过这个连接同步给从库,这个过程称之为:基于长连接的命令传播
。
为了保证传播的有效性和稳定性,从节点采用心跳机制进行侦测,发送命令:PING
和 REPLCONF 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地址 所需工具/软件包 Amoeba centos7 192.168.133.10 jdk-6u14-linux-x64.bin、amoeba-mysql-binary-2.2.0.tar.gz Master centos7 192.168.133.20 ntp 、 mysql-boost-5.7.20.tar.gz slave1 centos7 192.168.133.30 ntp 、ntpdate 、 mysql-boost-5.7.20.tar.gz slave2 centos7 192.168.133.40 ntp 、ntpdate 、mysql-boost-5.7.20.tar.gz 客户端 centos7 192.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.40vim /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.5 或 1.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> #---------修改后的65或66修改------------------------------------- <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 主从复制 + 读写分离的主要内容,如果未能解决你的问题,请参考以下文章