一、读写拆散的问题
1. 数据复制的提早
读写拆散时,master 会异步的将数据复制到 slave,如果这是 slave 产生阻塞,则会提早 master 数据的写命令,造成数据不统一的状况
解决办法:能够对 slave 的偏移量值进行监控,如果发现某台 slave 的偏移量有问题,则将数据读取操作切换到 master,但自身这个监控开销比拟高,所以对于这个问题,大部分的状况是能够间接应用而不去思考的。
2. 读到过期的数据
产生起因:
redis 的从库是无奈被动的删除曾经过期的 key 的,所以如果做了读写拆散,就很有可能在从库读到脏数据
例子重现:
主 Redis
setex test 20 1
+OK
get test
$1
1
ttl test
:18
从 Redis
get test
$1
1
ttl test
:7
以上都没问题,然而过几秒再看从 Redis
ttl test
:-1
get test
$1
1
test 这个 key 曾经过期了,然而还是能够获取到 test 的值。
在应用 Redis 做锁的时候,如果间接取读从库的值,这就有大问题了。
二、为什么从库不删除数据?
redis 删除过期数据有以下几个策略:
- 惰性删除:当读 / 写一个曾经过期的 key 时,会触发惰性删除策略,间接删除掉这个过期 key,很显著,这是被动的!
- 定期删除:因为惰性删除策略无奈保障冷数据被及时删掉,所以 redis 会定期被动淘汰一批已过期的 key。(在第二节中会具体阐明)
-
被动删除:以后已用内存超过 maxMemory 限定时,触发被动清理策略。被动设置的前提是设置了 maxMemory 的值
int expireIfNeeded(redisDb *db, robj *key) {time_t when = getExpire(db,key); if (when < 0) return 0; /* No expire for this key */ /* Don't expire anything while loading. It will be done later. */ if (server.loading) return 0; /* If we are running in the context of a slave, return ASAP: * the slave key expiration is controlled by the master that will * send us synthesized DEL operations for expired keys. * * Still we try to return the right information to the caller, * that is, 0 if we think the key should be still valid, 1 if * we think the key is expired at this time. */ if (server.masterhost != NULL) {return time(NULL) > when; } /* Return when this key has not expired */ if (time(NULL) <= when) return 0; /* Delete the key */ server.stat_expiredkeys++; propagateExpire(db,key); return dbDelete(db,key); }
通过以上源码发现,4 行:没有设置超时工夫,则不删;7 行:在”loading”时不删;16 行:非主库不删;21 行未到期不删。25 行同步从库和文件。
所以说,在从库执行被动删除操作,或者通过惰性删除的形式触发删除 key 的操作,最终都不会执行胜利。起因就在下面的第 16 行代码。
解决办法:
1)通过一个程序循环便当所有的 key,例如 scan
2)通过 ttl 判断
3)降级到 reidis3.2
主从配置不统一
这个问题个别很少见,但如果有,就会产生很多诡异的问题
例如:
1.maxmemory 配置不统一:这个会导致数据的失落
起因:例如 master 配置 4G,slave 配置 2G,这个时候主从复制能够胜利,但,如果在进行某一次全量复制的时候,slave 拿到 master 的 RDB 加载数据时发现本身的 2G 内存不够用,这时就会触发 slave 的 maxmemory 策略,将数据进行淘汰。更可怕的是,在高可用的集群环境下,如果咱们将这台 slave 升级成 master 的时候,就会发现数据曾经失落了。
2. 数据结构优化参数不统一(例如 hash-max-ziplist-entries):这个就会导致内存不统一
起因:例如在 master 上对这个参数进行了优化,而在 slave 没有配置,就会造成主从节点内存不统一的诡异问题。
三、躲避全量复制
redis 复制有全量复制和局部复制两种, 而全量复制的开销是很大的。那么咱们如何尽量去躲避全量复制。
1. 第一次全量复制
某一台 slave 第一次去挂到 master 上时,是不可避免要进行一次全量复制的,那么,咱们如何去想方法升高开销呢?
计划 1:小主节点,例如咱们把 redis 分成 2G 一个节点,这样一来,会减速 RDB 的生成和同步,同时还能够升高咱们 fork 子过程的开销(master 会 fork 一个子过程来生成同步须要的 RDB 文件,而 fork 是要拷贝内存快的,如果主节点内存太大,fork 的开销就大)。
计划 2:既然第一次不能够防止,那咱们能够选在集群低峰的工夫(凌晨)进行 slave 的挂载。
2. 节点 RunID 不匹配
例如咱们主节点重启(RunID 发生变化),对于 slave 来说,它会保留之前 master 节点的 RunID,如果它发现了此时 master 的 RunID 发生变化,那它会认为这是 master 过去的数据可能是不平安的,就会采取一次全量复制
解决办法:对于这类问题,咱们只有是做一些故障转移的伎俩,例如 master 产生故障宕掉,咱们选举一台 slave 晋升为 master(哨兵或集群)
3. 复制积压缓冲区有余
master 生成 RDB 同步到 slave,slave 加载 RDB 这段时间里,master 的所有写命令都会保留到一个复制缓冲队列里(如果主从间接网络抖动,进行局部复制也是走这个逻辑),待 slave 加载完 RDB 后,拿 offset 的值到这个队列里判断,如果在这个队列中,则把这个队列从 offset 到开端全副同步过去,这个队列的默认值为 1M。而如果发现 offset 不在这个队列,就会产生全量复制。
解决办法:增大复制缓冲区的配置 rel_backlog_size 默认 1M,咱们能够设置大一些,从而来加大咱们 offset 的命中率。这个值,咱们能够假如,个别咱们网络故障工夫个别是分钟级别,那咱们能够依据咱们以后的 QPS 来算一下每分钟能够写入多少字节,再乘以咱们可能产生故障的分钟就能够失去咱们这个现实的值。
四、躲避复制风暴
什么是复制风暴?举例:咱们 master 重启,其 master 下的所有 slave 检测到 RunID 发生变化,导致所有从节点向主节点做全量复制。只管 redis 对这个问题做了优化,即只生成一份 RDB 文件,但须要屡次传输,依然开销很大。
1. 单主节点复制风暴:主节点重启,多从节点全量复制
解决:更换复制拓扑如下图:
1. 咱们将原来 master 与 slave 两头加一个或多个 slave,再在 slave 上加若干个 slave,这样能够分担所有 slave 对 master 复制的压力。(这种架构还是有问题:读写拆散的时候,slave1 也产生了故障,怎么去解决?)
2. 如果只是实现高可用,而不做读写拆散,那当 master 宕机,间接降职一台 slave 即可。
2. 单机器复制风暴:机器宕机后的大量全量复制,
如下图:
当 machine- A 这个机器宕机重启,会导致该机器所有 master 下的所有 slave 同时产生复制。(劫难)
解决:
1. 主节点扩散多机器(将 master 扩散到不同机器上部署)
2. 还有咱们能够采纳高可用伎俩(slave 降职 master)就不会有相似问题了。
常见的数据库集群架构如何?
答:一主多从,主从同步,读写拆散。
如上图:
(1)一个主库提供写服务
(2)多个从库提供读服务,能够减少从库晋升读性能
(3)主从之间同步数据
画外音:任何计划不要忘了本心,加从库的本心,是晋升读性能。
为什么会呈现不统一?
答:主从同步有时延,这个时延期间读从库,可能读到不统一的数据。
如上图:
(1)服务发动了一个写申请
(2)服务又发动了一个读申请,此时同步未实现,读到一个不统一的脏数据(3)数据库主从同步最初才实现
画外音:任何数据冗余,必将引发一致性问题。
如何防止这种主从延时导致的不统一
?答:常见的办法有这么几种。
计划一:疏忽任何脱离业务的架构设计都是耍流氓,绝大部分业务,例如:百度搜寻,淘宝订单,QQ 音讯,58 帖子都容许短时间不统一。
画外音:如果业务能承受,最推崇此法。如果业务可能承受,别把零碎架构搞得太简单。
计划二:强制读主
如上图:
(1)应用一个高可用主库提供数据库服务
(2)读和写都落到主库上
(3)采纳缓存来晋升零碎读性能
这是很常见的微服务架构,能够防止数据库主从一致性问题。
计划三:选择性读主
强制读主过于粗犷,毕竟只有大量写申请,很短时间,可能读取到脏数据。
有没有可能实现,只有这一段时间,可能读到从库脏数据的读申请读主,平时读从呢?
能够利用一个缓存记录必须读主的数据。
如上图,当写申请产生时:
(1)写主库
(2)将哪个库,哪个表,哪个主键三个信息拼装一个 key 设置到 cache 里,这条记录的超时工夫,设置为“主从同步时延”
画外音:key 的格局为“db:table:PK”,假如主从延时为 1s,这个 key 的 cache 超时工夫也为 1s。
如上图,当读申请产生时:
这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个 key,到 cache 里去查问,如果,
(1)cache 里有这个 key,阐明 1s 内刚产生过写申请,数据库主从同步可能还没有实现,此时就应该去主库查问
(2)cache 里没有这个 key,阐明最近没有产生过写申请,此时就能够去从库查问以此,保障读到的肯定不是不统一的脏数据。
总结数据库主库和从库不统一,常见有这么几种优化计划:
(1)业务能够承受,零碎不优化
(2)强制读主,高可用主库,用缓存进步读性能
(3)在 cache 里记录哪些记录产生过写申请,来路由读主还是读从
哨兵是什么?
哨兵是 Redis 高可用 (HA) 的解决方案. 它是一种非凡的模式. 只不过它不对外提供服务.
组成 :
Sentinel 是一个零碎(或者说是集群) 它是由一个 (多个实例) 组成,采纳哨兵集群的起因是避免哨兵的误判.(单个哨兵的话会因为主节点过大或者因为网络起因造成误判)
为什么须要 Redis 哨兵?
Redis 有主从模式(客户端写操作发送到主节点, 读操作发送到从节点),如果这时候主节点呈现故障,不能对外提供服务了. 这个时候咱们须要做哪几个操作能力失常的对外提供不间断服务.
1. 咱们不知什么主节点挂了(最次要的问题)
2. 手工把从库设置为主库
3. 客户端还须要批改新的主库地址
Redis 如何提供无间断对外提供服务?
首先咱们理解下 Redis 哨兵三个定时工作
1. 监控(主节点是否下线)
2. 选主(抉择一个新的主节点)
3. 告诉(告诉客户端和其余从节点,哨兵)
监控(三个定时工作)
INFO 命令
第一个定时工作: 每 10s 每个哨兵对 master 和 slave 节点发送 INFO 命令(获取最新的拓扑构造)
订阅频道
第二个定时工作 每 2 秒每个 sentinel 通过 master 节点的 channel 替换信息
每个哨兵节点岁会向频道发送对于 (__sentinel__:hello) 判断信息
也会订阅其余哨兵节点对于主节点的判断
PING 命令(检测)
每隔 1 秒,每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条 ping 命令做一次心跳检测,来确认这些节点以后是否可达
Redis 判断主节点下线(主观 主观)
Redis 主节点下线的过程分为 主观下线,主观下线
在这里如果你的哨兵只有一个会间接下线就行故障转移,如果是多个会进行询问后 (少数哨兵就决定下线)
才会就行故障转移。失常状况下都会有多个哨兵。
主节点下线步骤:
1. 当哨兵 PING 命令发现,有节点响应超时, 会把节点标记为主观下线.
2. 如果主观下线节点为主节点时,会询问其余哨兵,当其余哨兵也断定主观下线(超过半数哨兵认为主观下线)
3. 哨兵零碎会抉择一个哨兵把其中的一个从节点降级为主节点,并告诉客户端.