俗话说得好,系统慢不用怕,没有什么是加一层缓存解决不了的,一层不行的话,就加两层。
但加缓存也并不是一劳永逸的,就哪 Redis 来说,还是有很多因素会影响 Redis 性能的,本文就来说一说其中的一些阻塞点,以及如何解决。一键获取最新java文档。
说到 Redis,大家自然而然的会想到基于内存、单线程执行等。话说回来,Redis 真的只有单线程吗?这篇文章来说说 Redis
的异步机制。
我们从 Redis 内部及外部因素总结一下,主要有:
首先说说 Redis 实例的阻塞点:
因 Redis 使用了 IO 多路复用机制,能避免主线程一直处于等待状态,网络 IO 不是导致 Redis 阻塞的因素。
而键值对的增删改查是主线程的主要工作,复杂度高的操作当然会阻塞 Redis 了。我们去判断复杂度高不高的标准就是看操作的复杂度是否为 O(N),也就是否要全表扫描。比如 hgetall, smembers 等操作就属于复杂度高的了。
然后还要注意的一个点就是数据的删除。
删除本质上来说就是对键值对的内存空间进行释放。在释放内存时,操作系统需要将释放掉的内存块插入一个空闲内存块的链表,以便后续管理和再分配。这个过程会阻塞当前释放内存的应用程序。
如果这个键值对数据很大,比如一个 zset 包含大量元素,就会释放大量的内存。有测试过删除 100 万个元素的集合时,删除时间会达到 2s,要知道 Redis 的响应是毫秒级别的。所以这种 bigkey 的删除也会成为 Redis 的阻塞点。
清空数据库(flushdb、flushall)也涉及到删除和释放所有的键值对,也是 Redis 的阻塞点。
AOF 重写和 RDB 快照,Redis 都用了子进程的方式操作,所以不会阻塞主线程。但 Redis 直接记录 AOF 日志,若有大量的写操作,并且配置的是同步写回的话,就会阻塞主线程了。
在主从集群中,主库生成 RDB 文件,并传输给从库。主从复制过程的创建和传输 RDB 都是子进程处理的,不会阻塞主线程。
但是从库在接收了 RDB 文件后,需要使用 flushdb 命令清空当前数据库,这又是一个阻塞点。而且,在从库清空数据库后,需要将 RDB 文件加载到内存,快慢和 RDB 文件大小相关。加载 RDB 文件又是一个阻塞点。
切片集群的实例在负载均衡或者实例增加删除时,数据迁移是渐进式操作的,所以不会阻塞主线程。
总结一下,Redis 就有 5 个阻塞点:
我们通过异步的方式,去解决可能阻塞的场景。但也不是每个操作都能用异步的方式去解决。如果一个操作能够异步执行,说明客户端不需要马上得到具体值,在 Redis 中描述为若一个操作能异步执行,就意味着它不是主线程的关键路径的操作。
对于第一个阻塞点,因为读操作需要等待数据的返回,所以第一个阻塞点不能异步执行。
第二个阻塞点和第三个阻塞点,因为删除不需要返回具体的结果,因此都可以用子线程去异步执行。
第四个阻塞点“AOF 日志同步写”,也可启动子线程操作,不用让主线程等待 AOF 日志的写完成。
第五个阻塞点“从库加载 RDB 文件”,从库要想对客户端提供数据存取服务,就必须把 RDB 文件加载完成,不能启用子进程。
Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。
lazy-free 机制是 Redis 收到删除指令后,主线程会将这个操作放入队列,然后马上给客户端返回一个完成信息。实际上删除还没执行呢。
lazy-free 是 Redis4.0 之后才有的功能,需要手动开启。需要注意的是,即使开启了 lazy-free,如果直接使用 DEL 命令还是会同步删除 key,只有使用 UNLINK 命令才会可能异步删除 key。
而且 Redis 在删除一个 key 时,首先会评估删除的时间成本,如果成本小,也不会异步执行,直接用主线程就完成返回了。
本文总结 Redis 有哪些阻塞点,以及这些阻塞点是否可用异步机制去解决。但我们在使用 Redis 时,还是要避免 bigkey 的使用。