《Redis深度历险:核心原理和应用实践》读书笔记

1:主从同步

很多企业没有使用redis集群,但是至少都做了主从。

CAP原理

现代分布式系统的理论基石——CAP原理:

  • C:Consistent,一致性
  • A:Availability,可用性
  • P:Partition tolerance,分区容错性

分布式系统的节点往往都是分布在不同机器上进行网络隔离开的,这意味着必然会有网络断开的风险,这个网络断开的场景专业词汇叫做网络分区
网络分区

在发生网络分区时,两个分布式节点之间无法进行通信,我们对一个节点进行的修改操作将无法同步到另一个节点,所以数据的一致性将无法满足,因为两个分布式节点的数据不再保持一致。
除非牺牲可用性,也就是暂停分布式节点服务,不再提供修改数据的功能直到网络状况完全恢复正常再继续对外提供服务。

当网络分区发生时,一致性和可用性两难全。

最终一致性

redis保证最终一致性,从节点努力追赶主节点,最终从节点的状态会和主节点的状态保持一致。如果网络断开,主从出现不一致,一旦网络恢复,从节点采用多种策略努力追赶,继续尽力保持和主节点一致。

主从同步与从从同步

redis支持主从同步与从从同步,从从同步减轻主节点的同步负担。
主从同步与从从同步

增量同步

redis同步的是指令流,主节点会将那些对自己状态产生修改影响的指令记录在本地内存buffer中,然后异步将buffer中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一致的状态,一边向主节点反馈自己同步到哪里了(偏移量)。
环形数组
内存buffer有限,主节点不能将所有指令都记录在内存buffer。redis复制内存buffer是一个定长的环形数组,如果数组满了,就会从头开始覆盖前面的内容。

如果因为网络,从节点在短时间内无法同步,那么网络恢复后,主节点中那些没有同步的指令在buffer中有可能已经被后续的指令覆盖掉了,从节点将无法直接通过指令流来进行同步,需要用到更加复杂的同步机制——快照同步。

快照同步

快照同步非常耗资源,首先在主节点进行一次bgsave,将当前内存数据全部快照到磁盘文件中,然后将快照文件的内容全部传送到从节点。从节点接受快照后,立即执行一次全量加载,加载之前先将当前的内存数据清空,加载完毕后通知主节点继续进行增量同步。快照同步
在快照同步中,主节点的复制buffer还在不停地向前移动,如果快照同步时间过长或者复制buffer太小,都会导致同步期间的增量指令在复制buffer中被覆盖,导致快照同步完成后无法进行增量复制,然后再次发起快照同步,如此极有可能陷入快照同步的死循环。

务必配置一个合适的复制buffer大小参数。

增加从节点

当节点刚加入集群,先快照同步后增量同步

无盘复制

主节点在快照同步时,会进行很耗时的文件IO。特别当系统进行AOF的fsync,如果发生快照同步,fsync将会推迟执行,这就很影响主节点的服务效率。

redis支持无盘复制,主服务器直接通过socket将快照内容发送到从节点,生成快照是一个遍历过程,主节点一边遍历内存,一边将序列化内容发给从节点,从节点现将接受内容存到磁盘,再进行一次性加载。

wait指令

redis复制是异步进行的,wait指令可以让异步变身同步,确保系统的强一致(不严格)

小结

主从复制是redis分布式基础。
不过复制也不是必须的,如果只是用redis做缓存,跟memcache一样对待,也就不需要从节点备份,挂掉了重启一下就好,若使用redis的持久化功能,就必须认真对待主从复制。

2:Sentinel

抵抗节点故障,当故障发生时可以自动进行主从切换。
Sentinel
将redis sentinel集群看成是一个zookeeper集群,它是集群高可用的心脏,一般由3~5个节点组成,这样即使个别节点挂了,集群还可以正常运转。

Sentinel负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换成主节点。客户端来连接集群时,会首先连接sentinel,通过sentinel查询主节点的地址,然后再连接主节点进行数据交互。当主节点发生故障时,客户端会重新向sentinel要地址,sentinel将最新的主节点地址告诉客户端。如此应用程序无需重启即可完成节点切换。

主节点挂掉
sentinel会持续监控已经挂掉的主节点,待它恢复后,集群调整为:

在这里插入图片描述
原先的主节点现在变成从节点,从新的主节点那里建立复制关系。

消息丢失

当主节点挂掉,从节点可能没收到全部的同步消息,这部分未同步的消息就丢失了。sentinel无法保证消息完全不丢失,但是尽量保证消息少丢失。有两个选项可以限制主从延迟过大。

#主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性
min-slaves-to-write 1
#如果在10s内没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开,要么一直没有反馈
min-slaves-max-lag 10
sentinel基本用法

sentinel基本用法
当sentinel进行主从切换时,客户端如何知道地址变更了?
redis-py在建立连接时进行主节点地址变更判断。
连接池新建连接时,会查询主地址,然后跟内存中的主地址比对,如果变更了,就断开所有连接,重新使用新连接。如果旧节点挂掉了,那么所有正在使用的连接都被关闭。
如果sentinel主动进行主从切换,但主节点并没有挂掉,而之前的主节点连接已经建立且在使用中,没有新连接建立,那么如何切换?
redis-py在处理命令时捕获了一个特殊异常ReadOnlyError,在这个异常里将所有旧连接关闭,后续指令就会重连。主从切换后,之前主节点的修改指令都会抛出ReadOnlyError。如果没有修改指令,虽然连接不会切换,但是数据不会被破坏,即使不切换也没关系。

3:Codis

单个redis内存不宜过大,内存太大导致rdb过大,进一步导致主从全量同步时间长,实例重启加载时间也长。

redis集群,将众多小内存的redis实例整合起来,将分布在多台机器上的众多CPU核心计算能力聚集到一起,完成海量数据存储和高并发读写。

Codis是redis集群方案之一,使用go开发,是一个代理中间件,和redis一样也使用redis协议对外提供服务。当客户端想Codis发送指令,codis负责将指令转发到后面的redis实例执行,并将返回结果再转回客户端。
codis

codis是无状态的,只是一个转发代理中间件,可以启动多个codis实例。
多个codis

Codis分片原理

默认将所有key划分为1024个slot,先将key进行crc32运算计算hash值,再将hash整数值对1024取模得到余数,这个余数就是key的slot。

hash = crc32(command.key)
slot_index = hash % 1024
redis = slots[slot_index].redis
redis.do(command)

slot
每个slot都会唯一映射到后面的多个redis实例之一,codis会在内存中维护slot和redis实例映射的关系。

slot数量可以设置,默认是1024

不同Codis实例之间槽位关系如何同步

Codis需要一个分布式配置存储数据库持久化slot关系。
分布式存储
Codis将slot关系存储到zookeeper中,并提供DashBoard观察和修改槽位关系。当槽位关系发生变化,Codis Proxy监听变化并同步槽位关系,从而实现多个Codis Proxy共享槽位关系配置。

扩容

Codis只有一个redis实例,1024个slot都指向同一个redis。
增加一个redis实例,需要调整槽位关系,将一半的slot划分给新节点。也就是对这一半的slot对应的所有key进行迁移。

Codis如何找到slot对应的所有key?
Codis对redis进行了改造,增加了SLOTSSCAN指令,可以遍历指定slot下所有key。
Codis通过SLOTSSCAN扫描出待迁移槽位的所有key,然后挨个迁移到新的redis实例。

codis接收到位于迁移slot的key后,会立即强制对当前的单个key进行迁移,迁移完成后,再将请求转发到新的redis实例。

自动均衡

自动均衡会在系统比较空闲时观察每个redis实例对应的slot数量,如果不平衡,就会自动迁移

codis的代价

所有key分散在不同的redis实例中,不能支持事务
rename操作,参数是两个key,若在不同redis实例中,无法正确完成。
为了支持扩容,单个key对应的value不宜过大。官方建议单个集合结构的总字节数不要超过1MB。
因为增加了Proxy作为中转层,网络开销上要比单个redis大。
集群配置中心使用zookeeper,意味着在部署上增加了zookeeper运维代价

codis的优点

在设计上比Redis Cluster官方集群方案简单很多,因为将分布式问题转交给第三方。Redis Cluster为了实现去中心化,混合使用复杂的Raft协议和Gossip协议,还有大量需要调优的配置参数。

mget指令的操作过程

批量获取多个key的值
mget指令

架构变迁
Codis的尴尬

不是Redis官方项目

Codis的后台管理

界面非常友好

4:Cluster

Redis Cluster是去中心化的。
每个节点负责整个集群的一部分数据,每个节点负责的多少可能不一样。节点相互连接组成一个对等的集群,他们之间通过一种特殊的二进制协议交互集群信息。
Redis Cluster
Redis Cluster将所有数据划分为16384个slot,slot信息存储于每个节点中,不需要另外的分布式存储空间。每个节点将集群配置信息持久化到配置文件,必须保证配置文件是可写的。
当客户端连接集群时,也会得到一份集群的slot信息配置信息。这样当客户端要查找某个key时,可以直接定位到目标节点。
客户端为了直接定位到某个key的目标节点,需要缓存slot相关信息。因为可能存在客户端与服务器信息不一致,需要纠正机制来实现slot信息的校验调整。

槽位定位算法

默认对key值使用crc16进行hash,得到一个整数值,然后对16384进行取模来得到具体slot。
Redis Cluster允许用户强制把某个key挂在特定slot上。在key字符串里加上tag标记

跳转

当client向一个错误节点发出指令后,该节点发现key所在slot不归自己管理,会向客户端发送跳转指令携带目标操作地址,告诉客户端去这个节点获取数据

GET X
-MOVED 3999 127.0.0.1:6381

MOVED前的减号,表示该指令是一个错误消息
3999:key对应的slot编号
127.0.0.1:6381:目标地址
客户端收到MOVED后,立即纠正本地槽位映射表

迁移

Redis Cluster提供redis-trib让运维人员手动调整slot分配

redis迁移的单位是slot,redis一个slot一个slot地进行迁移,当一个slot进行迁移时,这个slot就处于中间过渡状态。
在这里插入图片描述

redis-trib会先在原节点和目标节点设置好过渡状态,然后一次性获取源节点slot的所有key列表(keysinslot指令,可以部分获取),再挨个key进行迁移。每个key迁移过程是原节点作为目标节点的“客户端”,原节点对当前key执行dump指令得到序列化内容,然后通过“客户端”向目标节点发送restore指令携带序列化的内容作为参数,目标节点再进行反序列化就可以将内容恢复到目标节点内存中,然后返回“客户端”OK,原节点再把当前节点的key删除。
大致流程:从原节点获取内容——存到目标节点——从原节点删除
这里的迁移是同步的,同步时原节点的主线程处于阻塞状态。

迁移过程中,客户端访问流程:
首先新旧两个节点都存在部分数据,客户端先尝试访问旧节点,如果数据还在旧节点,那么旧节点正常处理。
如果数据不在旧节点,那么两种可能,要么在新节点,要么根本就不存在。旧节点会向客户端返回一个-ASK targetNodeAddr的重定向指令。客户端收到这个指令,先去目标节点执行一个不带任何参数的ASKING指令,然后在目标节点重新执行原先的操作指令。

为什么执行ASKING指令?
在迁移没有完成前,这个slot还不归新节点管理,如果直接访问新节点,节点不认,它会返回-MOVED重定向。如此就造成重定向循环。ASKING指令就是打开目标节点的选项,告诉它下一条指令不能不理,要当成自己的slot来处理。

迁移会影响服务效率,同样的指令正常情况下一个ttl就能完成,而在迁移下需要3个ttl。

容错

Redis Cluster可以为每个主节点设置若干从节点,当主节点发生故障,集群会自动将其中某个从节点提升为主节点。如果某个主节点没有从节点,那么当它发生故障时,集群将处于完全不可用状态。
redis提供参数cluster-require-full-coverage允许部分节点发生故障,其他节点还可以继续提供对外访问。

网络抖动

Redis Cluster提供选项cluster-node-timeout,当某个节点持续timeout的时间失联时,才可以认定该节点出现故障,需要主从切换。如果没有这个选项,网络抖动会导致主从频繁切换(数据的重新复制)。

还有另外一个选项cluster-slave-validity-factor作为倍数放大这个超时时间。

可能下线(PFail)与确定下线(Fail)

Redis Cluster去中心化,一个节点认为某个节点失联了(PFail,Possibly Fail),不代表所有节点都认为它失联。Redis集群节点采用Gossip协议广播自己的状态以及改变对整个集群的认知。
节点认为某节点PFail,将这个消息向集群广播,如果收到某个节点失联的节点数量PFail Count达到集群大多数,就可以标记该失联节点为下线状态Fail,然后向集群广播,并立即对该失联节点进行主从切换。

Cluster基本用法
槽位迁移感知

MOVED和ASKING命令
会存在多次重试的情况,因此客户端的源码里执行指令时都会有一个循环,然后设置最大重试次数。

集群变更感知
  1. 目标节点挂了,客户端抛出ConnectionError,紧接着随机挑一个节点重试,这首被重试节点通过MOVED告诉目标槽位被分配到新的地址
  2. 运维手动修改集群信息,将主节点迁移到其他节点,并将旧的主节点移出集群。这时打在旧主节点的指令会收到ClusterDown错误,告知当前节点所在集群不可用。这时客户端关闭所有连接,清空slot映射表,向上抛错。待下一条指令过来时,重新尝试初始化节点信息。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值