思路:
1.建立一张redis主从切片集群图 ->->
2.以一个切片节点为中心考虑其与周围节点会造成阻塞的情况 ->->
3.对每一个阻塞情况考虑其是否是客户端需要的返回数据决定是否可以用异步子线程处理
一、Redis实例内部阻塞的四大原因
- 客户端:网络 IO,键值对增删改查操作,数据库操作;
- 磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
- 主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB 文件;
- 切片集群实例:向其他实例传输哈希槽信息,数据迁移。
对应如下图所示:
下面解释一下为什么会造成阻塞:
一:与客户端交互
- 复杂的sql语句,全量查询、聚合操作等(由主线程执行);
- bigkey删除(Redis除了释放内存外,还会将空闲内存进行整理,涉及内存空间的移动);
- 清空数据库。
二:与磁盘交互
AOF和RDB处理均有子进程执行,不会影响主线程,但是需要先fork子进程,当AOF和RDB操作频繁时,频繁的fork操作会影响主线程。
三:与从库交互
主库向从库传输RDB文件由子进程完成,主库主线程不受影响,主要会导致从库阻塞,从库接收RDB文件后要
- 清空数据库导致bigkey删除操作阻塞;
- 加载过大的RDB数据到内存阻塞。
四:切片集群交互(不是bigkey操作的话不容易导致阻塞)
- 彼此通过gossip协议传输哈希槽信息(数据量小不易阻塞);
- 增删实例时哈希槽重新分配(渐进式重分配不易阻塞)。
二、如何解决Redis实例内部阻塞问题
- 对于客户端无关请求(客户端不需要Redis返回具体数据)—采用异步子线程执行机制(可以参考这个来思考redis的主线程、主线程的子线程、子进程等建立情况)
- redis启动时默认会建立一个主线程以及三个异步子线程,只要是客户端对redis发送的请求不需要得到具体的返回数据,那么redis就采用一个异步子线程来处理该请求(可以想象成放入到一个消息队列中,主线程不去管它的执行情况),客户端直接收到一个redis返回的ok响应,比如lazy free惰性删除。
- 对于客户端相关请求(客户端需要Redis返回具体数据)
- A) 全量查询以及聚合操作:分批次返回数据到客户端,然后在客户端进行聚合操作;
- B) 从库加载RDB文件:实时控制主库的RDB文件不要过大,这样每次从库加载RDB文件就不会需要过多时间。