共计 4277 个字符,预计需要花费 11 分钟才能阅读完成。
09 切片集群
Redis 响应慢问题排查:应用 INFO 命令查看 latest_fork_usec(最近一次 fork)耗时
起因:fork 在执行时会阻塞主线程。数据量越大,fork 操作造成的主线程阻塞的工夫越长。
数据存储计划
Redis 应答数据量增多的两种计划:纵向扩大(scale up)和横向扩大(scale out)。
- 纵向扩大:降级单个 Redis 实例的资源配置,包含减少内存、磁盘、CPU。
- 横向扩大:减少以后 Redis 实例数。
分布式治理
- 数据切片后,在多个实例之间如何散布?
- 客户端怎么确定想要拜访的数据在哪个实例上?
切片和实例对应关系
Redis 3.0 开始,官网提供了一个名为 Redis Cluster 的计划,用于实现切片集群。
Redis Cluster 计划采纳哈希槽(Hash Slot,接下来我会间接称之为 Slot),来解决数据和实例之间的映射关系。
在 Redis Cluster 计划中,一个切片集群共有 16384 个哈希槽,这些哈希槽相似于数据分区,每个键值对都会依据它的 key,被映射到一个哈希槽中。
具体过程:
- 依据键值对的 key,依照 CRC16 算法计算一个 16 bit 的值;
- 用这个 16bit 值对 16384 取模,失去 0~16383 范畴内的模数,每个模数代表一个相应编号的哈希槽。
调配计划:
- 部署 Redis Cluster 计划时,能够应用 cluster create 命令创立集群,此时,Redis 会主动把这些槽均匀散布在集群实例上。例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。
-
也能够应用 cluster meet 命令手动建设实例间的连贯,造成集群,再应用
cluster addslots
命令,指定每个实例上的哈希槽个数。redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1 redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3 redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4
留神:手动调配哈希槽时,须要把 16384 个槽都调配完,否则 Redis 集群无奈失常工作。
客户端定位数据
客户端和集群实例建设连贯后,实例就会把哈希槽的调配信息发给客户端。
- 每个实例只晓得本人被调配了哪些哈希槽,然而会把本人的哈希槽信息发给和它相连接的其它实例。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。
- 客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端申请键值对时,会先计算键所对应的哈希槽,而后就能够给相应的实例发送申请了
实例哈希槽变动状况:
- 在集群中,实例有新增或删除,Redis 须要重新分配哈希槽;
- 为了负载平衡,Redis 须要把哈希槽在所有实例上从新散布一遍。
重定向机制
当客户端把一个键值对的操作申请发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就会给客户端返回上面的 MOVED 命令响应后果,这个后果中就蕴含了新实例的拜访地址。
GET hello:key
(error) MOVED 13320 172.16.19.5:6379
ASK 命令:当实例局部迁徙时,会返回 ASK
GET hello:key
(error) ASK 13320 172.16.19.5:6379
示例:Slot 2 正在从实例 2 往实例 3 迁徙,key1 和 key2 曾经迁徙过来,key3 和 key4 还在实例 2。客户端向实例 2 申请 key2 后,就会收到实例 2 返回的 ASK 命令。
ASK 命令示意两层含意:
第一,表明 Slot 数据还在迁徙中
第二,ASK 命令把客户端所申请数据的最新实例地址返回给客户端,此时,客户端须要给实例 3 发送 ASKING 命令,而后再发送操作命令。
和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽调配信息。
小结
集群的实例增减,或者是为了实现负载平衡而进行的数据从新散布,会导致哈希槽和实例的映射关系发生变化,客户端发送申请时,会收到命令执行报错信息。
问题:键值对的 key 先做 CRC 计算,再和哈希槽做映射的益处?
1、key 的数量十分多时,间接记录每个 key 对应的实例映射关系,占用空间。
2、Redis Cluster 采纳无中心化的模式(无 proxy,客户端与服务端直连),客户端在某个节点拜访一个 key,如果这个 key 不在这个节点上,这个节点须要有纠正客户端路由到正确节点的能力(MOVED 响应),这就须要节点之间互相交换路由表,每个节点领有整个集群残缺的路由关系。如果存储的都是 key 与实例的对应关系,节点之间替换信息也会变得十分宏大,耗费过多的网络资源,而且就算替换实现,相当于每个节点都须要额定存储其余节点的路由表,内存占用过大造成资源节约。
3、当集群在扩容、缩容、数据平衡时,节点之间会产生数据迁徙,迁徙时须要批改每个 key 的映射关系,保护老本高。
4、而在两头减少一层哈希槽,能够把数据和节点解耦,key 通过 Hash 计算,只须要关怀映射到了哪个哈希槽,而后再通过哈希槽和节点的映射表找到节点,相当于耗费了很少的 CPU 资源,岂但让数据分布更平均,还能够让这个映射表变得很小,利于客户端和服务端保留,节点之间替换信息时也变得轻量。5、当集群在扩容、缩容、数据平衡时,节点之间的操作例如数据迁徙,都以哈希槽为根本单位进行操作,简化了节点扩容、缩容的难度,便于集群的保护和治理。
10 问题解答
课后问题
整数数组和压缩列表作为底层数据结构的劣势是什么?
节俭空间。
整数数组和压缩列表都是在内存中调配一块地址间断的空间,而后把汇合中的元素一个接一个地放在这块空间内,十分紧凑。
Redis 之所以采纳不同的数据结构,其实是在性能和内存应用效率之间进行的均衡。
Redis 根本 IO 模型中还有哪些潜在的性能瓶颈?
在 Redis 根本 IO 模型中,次要是主线程在执行操作,任何耗时的操作,例如 bigkey、全量返回等操作,都是潜在的性能瓶颈。
AOF 重写过程中有没有其余潜在的阻塞危险?
1.fork 创立 bgrewriteaof 子过程时,会阻塞主线程。子过程要拷贝父过程的页表,页表越大,执行工夫越长。
2.bgrewriteaof 子过程和主线程共享内存。主线程新写或批改时会申请新的内存,操作系统在分配内存空间时,有查找和锁的开销,这就会导致阻塞。
AOF 重写为什么不共享应用 AOF 自身的日志?
如果都用 AOF 日志的话,主线程要写,bgrewriteaof 子过程也要写,这两者会竞争文件系统的锁,这就会对 Redis 主线程的性能造成影响。
为什么主从库间的复制不应用 AOF?
1.RDB 文件是二进制文件,无论是要把 RDB 写入磁盘,还是要通过网络传输 RDB,IO 效率都比记录和传输 AOF 的高。
2. 在从库端进行复原时,用 RDB 的复原效率要高于用 AOF。
在主从切换过程中,客户端是否失常地进行申请操作呢?
主从集群个别是采纳读写拆散模式,当主库故障后,客户端依然能够把读申请发送给从库,让从库服务。然而,对于写申请操作,客户端就无奈执行了。
如果想要应用程序不感知服务的中断,还须要哨兵或客户端再做些什么吗?
- 客户端须要能缓存利用发送的写申请。只有不是同步写操作(Redis 利用场景个别也没有同步写),写申请通常不会在应用程序的要害门路上,所以,客户端缓存写申请后,给应用程序返回一个确认就行。
- 主从切换实现后,客户端要能和新主库从新建设连贯,哨兵须要提供订阅频道,让客户端可能订阅到新主库的信息。同时,客户端也须要能被动和哨兵通信,询问新主库的信息。
哨兵实例是不是越多越好呢?如果同时调大 down-after-milliseconds 值,对缩小误判是不是也有益处?
哨兵实例越多,误判率会越低;
然而在断定主库下线和选举 Leader 时,实例须要拿到的赞成票数也越多,期待所有哨兵投完票的工夫可能也会相应减少,主从库切换的工夫也会变长,客户端容易沉积较多的申请操作,可能会导致客户端申请溢出,从而造成申请失落。如果业务层对 Redis 的操作有响应工夫要求,就可能会因为新主库始终没有选定,新操作无奈执行而产生超时报警。
调大 down-after-milliseconds 后,可能会导致这样的状况:主库理论曾经产生故障了,然而哨兵过了很长时间才判断进去,这就会影响到 Redis 对业务的可用性。
为什么 Redis 不间接用一个表,把键值对和实例的对应关系记录下来?
1. 如果应用表记录键值对和实例的对应关系,一旦键值对和实例的对应关系产生了变动(例如实例有增减或者数据从新散布),就要批改表。
2. 如果数据量十分大,应用表记录键值对和实例的对应关系,须要的额定存储空间也会减少。
典型问题
1. 什么时候做 rehash?
- Redis 会应用装载因子(load factor)来判断是否须要做 rehash。
装载因子的计算形式是,哈希表中所有 entry 的个数除以哈希表的哈希桶个数。
- 装载因子≥1,同时,哈希表被容许进行 rehash(没有进行 APF 和 RDB);
- 装载因子≥5。
留神:在进行 RDB 生成和 AOF 重写时,哈希表的 rehash 是被禁止的,这是为了防止对 RDB 和 AOF 重写造成影响。
2.Redis 会执行定时工作,定时工作中就蕴含了 rehash 操作。
2. 主线程、子过程和后盾线程的关系
- 主过程:一个过程启动后,没有再创立额定的线程,那么,这样的过程个别称为主过程或主线程。
-
子过程:在主线程中,应用 fork 创立子过程
- 创立 RDB 的后台子过程,同时由它负责在主从同步时传输 RDB 给从库;
- 通过无盘复制形式传输 RDB 的子过程;
- bgrewriteaof 子过程。
- 应用 pthread_create 创立后盾线程,自行执行一些工作,例如执行异步删除工作。
3. 写时复制的底层实现机制
Redis 在应用 RDB 形式进行长久化时,主线程 fork 出 bgsave 子过程后,bgsave 子过程理论是复制了主线程的页表。
此时主线程接管到了新写或批改操作,那么,主线程会应用写时复制机制。
写时复制就是指,主线程在有写操作时,才会把这个新写或批改后的数据写入到一个新的物理地址中,并批改本人的页表映射。
4. replication buffer 和 repl_backlog_buffer 的区别
- replication buffer 是主从库在进行全量复制时,记录主库在全量复制期间的写操作的 buffer;
replication buffer 不是共享的,而是每个从库都有一个对应的客户端。 - repl_backlog_buffer 是为了反对从库增量复制,主库上用于继续保留写操作的一块专用 buffer;
是所有从库共享的。