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

Redis线程那些源码细节,想搞懂其实没那么难,慢慢看就明白了

(引用来源:文章《Redis线程那些源码细节,想搞懂其实没那么难,慢慢看就明白了》)

这篇文章主要就是想掰开揉碎了讲清楚Redis到底是怎么处理网络请求的,为什么我们说它是单线程,但又好像不止一个线程,咱们就跟着一个客户端发来的命令,看看它在Redis内部是怎么被一步步执行的。

Redis线程那些源码细节,想搞懂其实没那么难,慢慢看就明白了

你得知道Redis的核心,也就是处理我们发的SET、GET这些命令的那个部分,确实只有一个线程在干活,这个核心线程被作者安排在一个无限循环里,这个循环就是Redis的“心脏”,这个循环不停地做一件事:检查有没有活干,它干的活主要来自两个地方,一个是网络连接上有客户端发来了命令,另一个是时间到了该执行一些后台任务了,比如检查过期键。

那它怎么知道有活干呢?这里就用到了一个叫I/O多路复用的技术,别被这个词吓到,你可以把它想象成一个特别有效率的“门卫”,这个门卫(在Linux里通常是epoll)帮核心线程盯着成千上万个网络连接,当某个连接有数据可读(比如客户端发来了命令)或者可以写入数据时,这个门卫就会通知核心线程:“喂,醒醒,XX号连接有活儿了!”这样,核心线程就不用傻傻地挨个去问每个连接“你有事吗?”,而是可以安心睡觉,等门卫通知它哪些连接真的需要处理,它再去处理,这就极大地提高了效率。

Redis线程那些源码细节,想搞懂其实没那么难,慢慢看就明白了

核心线程的主循环大概是这样跑的:先问问门卫“有没有已经准备好的连接?”,门卫会给它一个列表,核心线程就按照这个列表,一个一个地处理这些连接上的请求,处理的过程就是:从网络连接里把命令数据读出来,解析成Redis能懂的命令和参数,然后真正执行这个命令(比如去内存里查找数据),最后把结果写回给客户端。

说到这里,你可能会问,如果某个命令执行得很慢,比如一个复杂的Lua脚本,那不是会把后面所有的命令都堵住吗?没错,这就是单线程模型的特点,也是它的缺点,所以Redis官方一直强调,不要在Redis里执行耗时命令。

Redis线程那些源码细节,想搞懂其实没那么难,慢慢看就明白了

那为什么又说Redis不完全是单线程呢?这是因为从Redis 4.0版本开始,一些比较“脏活累活”的后台任务,比如删除一个非常大的键(我们叫它UNLINK命令,区别于DEL)、或者持久化时刷写数据到磁盘,这些操作会被核心线程扔给另外的“后台线程”去慢慢做,这样,核心线程就能很快地解脱出来,继续去处理新的命令请求,不会被慢操作拖累,这就是所谓的“惰性删除”和异步线程机制,准确地说,Redis是单线程处理命令请求,但多线程处理部分后台任务

到了Redis 6.0,线程模型又进了一步,除了上面说的后台线程,它还引入了“I/O线程”,注意,这个I/O线程不负责执行命令!它只负责两件事:读和写,就是当核心线程从门卫那里拿到一批准备好的连接后,它可以把“从网络socket上读取命令数据”和“把结果数据写回socket”这两个纯网络I/O的操作,交给多个I/O线程去并行处理,等I/O线程们都读完了,核心线程再统一执行这些命令;执行完后,再把写回操作交给I/O线程去完成。

这样做的好处是,当Redis面临大量并发连接时,网络读写这种耗时的I/O操作可以被分摊出去,核心线程就能更专注于它最擅长的命令执行,整体吞吐量就上去了,但最关键的命令解析和执行阶段,依然是单线程的,这保证了原子性,不需要复杂的锁。

总结一下线程的演变:最早是纯单线程;4.0引入了后台线程处理慢操作;6.0引入了I/O线程分担网络压力,你看,这样一层层看下来,Redis的线程模型是不是就清晰多了?源码虽然庞大,但抓住主循环、I/O多路复用、任务分发这几个关键点,慢慢捋,确实就能看明白了。