Redis深度历险分为两个部分,单机Redis和分布式Redis。java
本文为分布式Redis深度历险系列的第一篇,主要内容为Redis的复制功能。redis
Redis的复制功能的做用和大多数分布式存储系统同样,就是为了支持主从设计,主从设计的好处有如下几点:安全
Redis的复制主要分为同步和命令传播两个步骤:服务器
同步能够理解为全量,是将主服务器某一时刻的全部数据所有同步到从服务器。网络
命令传播能够理解为增量,当主服务器数据被修改时,主服务器向从服务器发送对应的数据修改命令。架构
同步分为如下几个步骤:并发
1.从服务器向主服务器发送SYNC
命令(执行SLAVE OF
命令的第一步也会执行SYNC
)分布式
2.主服务器在收到从服务器命令时,会执行BGSAVE
,也就是新开一个子进程将内存中的数据保存到RDB文件中。同时使用一个内存缓冲区记录从如今开始执行的写命令,该内存缓冲区的做用就是记录RDB文件生成期间的增量。高并发
3.向从服务器发送RDB文件性能
4.将缓冲区中的写命令发送给从服务器
同步能够分为两种状况,一种是从服务器第一次链接主服务器,另外一种是从服务与主服务器的网络连接断开了,从新连上主服务器并从新同步。
命令传播实现逻辑比较简单,当主服务器执行了写命令后,为了保证从服务器与主服务器数据的一致性,主服务器会将写命令发送给从服务器,从服务器执行完收到的写命令后其数据就能和主服务器保持一致了(固然会有延时),注意,从服务器对于客户端来讲是只读的,所以从服务器的全部数据都是来自于主服务器的同步or命令传播。
假设Redis主从服务器之间的网络环境不太可靠,咱们来看看上述复制方法会出现什么问题。假设有主服务器A和从服务器B,主服务器中目前存在1-10000共一万条数据。
1.初始链接,从服务器第一次从主服务器同步数据,同步完成后,从服务器也有1-10000共一万条数据。
2.主服务器新增10001,10002两条数据
3.经过命令传播,从服务器也新增10001,10002两条数据
4.这时候主从服务器之间的网络断开
5.主服务器新增数据10003,由于网络断开,因此从服务器感觉不到数据变化
6.网络恢复,从服务器从新链接上主服务器,并发送SYNC命令,进行同步操做
7.主服务器将全部数据发送给从服务器(1-10003)
从上述步骤中能够看到,当从服务器从新链接上主服务器时,会从新进行全量同步,形成大量没必要要的IO开销,若是网络环境不稳定时,会致使主服务器一直将内存中的数据写到磁盘再发送给从服务器。
为了解决老版复制问题,Redis2.8对于复制功能进行了优化。实现以下:
1.主服务器会维护一个偏移量,每次向服务器传播N个字节的数据时,该偏移量就会加上N,好比说一开始是0,接受到一条set key1 value1
后,其偏移量就为13(真实偏移可能不是13,只是举个例子)。//这里可能要看下代码确认
2.从服务器也维护一个偏移量,当从服务器收到到主服务器的N个字节数据时,该偏移量会加上N。
3.主服务器维护一个固定大小的缓冲区,每次接受到客户端写命令后,都会将对应命令
往这个缓冲区写入。当写入内容超出固定大小后,会覆盖原来的数据。
4.主服务器有一个惟一id
5.从服务器链接上主服务时,会向主服务器发送上一次链接的主服务器的id以及偏移量,这里又分几种状况:
以上就是新版复制的大体思路,要注意的是,主服务器缓冲区的大小设置很关键,若是设置的太大会致使空间浪费,若是过小会致使网络环境很差时,其退化为老版复制。
以前我就踩过这样的坑:在上云时,redis集群在两个不一样机房,主从以前网络环境不太稳定,而redis机器上存储的value比较大,很容易就将缓冲区占满致使每次全量同步,造成恶性循环,从服务器落后不可读,主服务器不可写(当从Redis落后太多时,主Redis将拒绝写入,具体参数能够配置的,下文还会提到)
因此建议将缓冲区大小设置为平均重连间隔*每秒写入数据量*2
从服务器默认会每秒一次的频率向主服务器发送心跳:
REPLCONF AÇK <replication_offset>
,
replication_offset表明从服务器当前的复制偏移量。
心跳有三个做用:
1.检测主从服务器的网络链接
2.实现min-slaves功能
3.检测命令丢失
主服务器会记录从服务器上次发送心跳是什么时间,根据这个时间,咱们能知道主从服务器之间的链接是否是出现了故障
Redis为了保证数据的安全性,能够配置当从服务器小于min-slaves-to-write
个或者min-slaves-to-write
个从服务器的延迟都大于等于min-slaves-max-lag
时,主服务器拒绝写。
主从之间的复制,实际上是以主服务器做为从服务器的客户端来实现的(在Redis中,全部服务器之间的数据传递都是以该种方式)。假设主服务器向从服务器发送一条写命令,但网络出现异常,从服务器并无收到该命令。
这就会致使数据不一致的状态(你可能想主服务器发送命令时,若是从没返回失败,进行重发不就行了吗?若是说从成功执行了命令,可是再回复主的时候出现了问题,那主若是重发就会形成数据异常了)。因此主服务器会根据心跳信息来决定要发送的数据。看个例子:
初始,主服务器和从服务器偏移量都是100。
主服务器收到客户端的写命令,将偏移量改为110,同时向从服务器发送写命令,但因网络缘由,从服务器并无收到,其偏移量仍然是100。主服务器根据心跳发现从服务器的偏移量是100落后于本身,因此会将100-110的数据进行重发。
看到这里,你可能对于上述方案的正确性感到质疑:在从服务器接收到100-110的数据前,它发送心跳包告诉主服务器本身当前偏移为100,而后接收到了100-110的数据。这时下个心跳还没发出,主服务器认为从服务器落后于本身,再次发送100-110的数据,致使从服务器再次写入100-110的数据,致使数据异常!
若是你有想到这个问题,说明你是有在认真思考了~
实际上是不存在这种状况的,缘由是redis是单线程的!记住单线程三个字,再回头看一遍问题描述,相信你能想明白~
原文:Java架构笔记
免费Java高级资料须要本身领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q