当前位置:首页 > 问答 > 正文

Redis过期问题用多线程咋整,性能能不能稳住还得试试看

关于Redis过期键的清理问题,用多线程来处理这个想法,其实Redis官方在后续版本中已经这么做了,这个问题的核心在于,Redis是单线程处理命令的,这意味着所有操作,包括读写数据、过期键的检查和删除,都在一个主线程里排队执行。

(来源:基于对Redis单线程Reactor模型的基本理解)

想象一下,如果Redis数据库里一下子有成千上万个键同时过期,主线程就得停下手头所有的读写请求,专心致志地去一个个检查并删除这些过期键,这个清理过程如果非常耗时,就会导致整个Redis服务器卡住,所有后续的客户端请求都得等着,性能就会急剧下降,甚至出现超时,这就是所谓的“延迟毛刺”。

(来源:描述的是Redis早期版本在主动过期清理时可能引发的性能问题场景)

为了解决这个单线程清理可能带来的阻塞问题,Redis从4.0版本开始引入了所谓的“惰性删除”和“定期删除”之外的多线程机制,不过这里要敲一下黑板,它并不是简单粗暴地开几个线程专门去删过期键,Redis的核心处理模型依然是单线程的,它引入多线程主要是为了处理一些耗时的异步任务,而过期键的清理恰恰被归为这类任务之一。

Redis过期问题用多线程咋整,性能能不能稳住还得试试看

(来源:Redis 4.0引入的Lazy Free机制官方文档与相关解读)

具体是怎么“多线程”整的呢?它叫做“惰性释放”(Lazy Free),当一个大键(比如一个包含百万元素的Hash键)需要被删除时,无论是它自然过期,还是用户主动执行DEL命令,主线程都不会再亲自动手去一点点释放这个庞大键所占用的内存了。 Instead,主线程会把这个“删除苦差事”封装成一个任务,扔到一个任务队列里去,Redis会启动一些额外的后台线程(这些线程是独立的,不处理命令)专门去从这个队列里取任务,默默地在后台执行实际的释放内存操作,这样一来,主线程就迅速解脱了,可以继续飞快地处理接下来的读写请求,不会被一个大的删除操作拖累。

(来源:Redis官方关于lazyfree-lazy-expire等配置参数的说明)

Redis过期问题用多线程咋整,性能能不能稳住还得试试看

性能能不能稳住呢?从原理和实际测试来看,这个方法对于稳定性能,尤其是降低尾延迟,效果是非常显著的,因为最耗时的内存释放工作被剥离出去了,主线程的响应时间变得非常平滑,即使突然有一大批大键过期,用户感受到的也只是后台线程在努力工作可能导致的系统负载略有升高,但Redis服务本身对外提供的读写响应速度不会出现那种长时间的停顿。

(来源:基于对Lazy Free机制原理的分析以及社区的性能测试反馈)

天下没有免费的午餐,这么干也得注意几点,不然也可能翻车:

  1. 内存压力:虽然主线程响应快了,但过期键占用的内存并不会立刻释放,得等后台线程慢慢消化,如果过期键产生的速度远远超过后台线程清理的速度,内存队列会堆积,最终可能导致内存耗尽,这就需要监控后台任务队列的长度。
  2. CPU压力:后台线程也是要消耗CPU资源的,如果服务器本身CPU就很紧张,再开多个后台线程疯狂清理,可能会和主线程(以及其他进程)争抢CPU,反而影响整体性能,所以后台线程的数量需要根据实际情况调整。
  3. 配置得当:这个多线程的惰性删除功能在Redis里是需要配置开启的(比如设置lazyfree-lazy-expire yes),并且可以调整后台线程的数量,用不用、用几个,都得根据自己业务的实际情况(比如是否经常有大键过期)来测试决定。

(来源:总结自Redis性能调优相关的实践讨论)

结论就是:用多线程异步处理Redis过期(特别是大键的删除)是一个非常有效的思路,是官方认可的优化方向,确实能极大地帮助稳住性能,避免阻塞,但能不能完全稳住,光靠这个机制还不够,还得结合实际业务场景、服务器资源状况进行合理的配置和测试,说白了,理论很美好,但具体行不行,还得亲自试试看”,要关注内存和CPU的消耗平衡,做好监控和调优。