Redis作为一个基于内存的缓存系统,一直以高性能著称,因没有上下文切换以及无锁操作,即使在单线程处理情况下,读速度仍可达到11万次/s,写速度达到8.1万次/s。但是,单线程的设计也给Redis带来一些问题:
-
只能使用CPU一个核;
-
如果删除的键过大(比如Set类型中有上百万个对象),会导致服务端阻塞好几秒;
-
QPS难再提高。
针对上面问题,Redis在4.0版本以及6.0版本分别引入了Lazy Free
以及多线程IO
,逐步向多线程过渡,下面将会做详细介绍。
单线程原理
都说Redis是单线程的,那么单线程是如何体现的?如何支持客户端并发请求的?为了搞清这些问题,首先来了解下Redis是如何工作的。
Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:
-
文件事件
:Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象;服务器与客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作,比如连接accept
,read
,write
,close
等; -
时间事件
:Redis服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象,比如过期键清理,服务状态统计等。

如上图,Redis将文件事件和时间事件进行抽象,时间轮训器会监听I/O事件表,一旦有文件事件就绪,Redis就会优先处理文件事件,接着处理时间事件。在上述所有事件处理上,Redis都是以单线程
形式处理,所以说Redis是单线程的。此外,如下图,Redis基于Reactor模式开发了自己的I/O事件处理器,也就是文件事件处理器,Redis在I/O事件处理上,采用了I/O多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数,通过一个线程实现了多客户端并发处理。

正因为这样的设计,在数据处理上避免了加锁操作,既使得实现上足够简洁,也保证了其高性能。当然,Redis单线程只是指其在事件处理上,实际上,Redis也并不是单线程的,比如生成RDB文件,就会fork一个子进程来实现,当然,这不是本文要讨论的内容。
Lazy Free机制
如上所知,Redis在处理客户端命令时是以单线程形式运行,而且处理速度很快,期间不会响应其他客户端请求,但若客户端向Redis发送一条耗时较长的命令,比如删除一个含有上百万对象的Set键,或者执行flushdb,flushall操作,Redis服务器需要回收大量的内存空间,导致服务器卡住好几秒,对负载较高的缓存系统而言将会是个灾难。为了解决这个问题,在Redis 4.0版本引入了Lazy Free
,将慢操作
异步化,这也是在事件处理上向多线程迈进了一步。
如作者在其博客中所述,要解决慢操作
,可以采用渐进式处理,即增加一个时间事件,比如在删除一个具有上百万个对象的Set键时,每次只删除大键中的一部分数据,最终实现大键的删除。但是,该方案可能会导致回收速度赶不上创建速度,最终导致内存耗尽。因此,Redis最终实现上是将大键的删除操作异步化,采用非阻塞删除(对应命令UNLINK
),大键的空间回收交由单独线程实现,主线程只做关系解除,可以快速返回,继续处理其他事件,避免服务器长时间阻塞。
以删除(DEL
命令)为例,看看Redis是如何实现的,下面就是删除函数的入口,其中,lazyfree_lazy_user_del
是是否修改DEL
命令的默认行为,一旦开启,执行DEL
时将会以UNLINK
形式执行。
void *IOThreadMain(void *myid) {
...
while(1) {
...
// I/O线程执行读写操作
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
// io_threads_op判断是读还是写事件
if (io_threads_op == IO_THREADS_OP_WRITE) {
writeToClient(c,0);
} else if (io_threads_op == IO_THREADS_OP_READ) {
readQueryFromClient(c->conn);
} else {
serverPanic("io_threads_op value is unknown");
}
}
listEmpty(io_threads_list[id]);
io_threads_pending[id] = 0;
if (tio_debug) printf("[%ld] Donen", id);
}
}
局限性
从上面实现上看,6.0版本的多线程并非彻底的多线程,I/O线程
只能同时执行读或者同时执行写操作,期间事件处理线程
一直处于等待状态,并非流水线模型,有很多轮训等待开销。
Tair多线程实现原理
相较于6.0版本的多线程,Tair的多线程实现更加优雅。如下图,Tair的Main Thread
负责客户端连接建立等,IO Thread
负责请求读取、响应发送、命令解析等,Worker Thread
线程专门用于事件处理。IO Thread
读取用户的请求并进行解析,之后将解析结果以命令的形式放在队列中发送给Worker Thread
处理。Worker Thread
将命令处理完成后生成响应,通过另一条队列发送给IO Thread
。为了提高线程的并行度,IO Thread
和Worker Thread
之间采用无锁队列和管道进行数据交换,整体性能会更好。
小结
Redis 4.0引入Lazy Free
线程,解决了诸如大键删除导致服务器阻塞问题,在6.0版本引入了I/O Thread
线程,正式实现了多线程,但相较于Tair,并不太优雅,而且性能提升上并不多,压测看,多线程版本性能是单线程版本的2倍,Tair多线程版本则是单线程版本的3倍。在作者看来,Redis多线程无非两种思路,I/O threading
和Slow commands threading
,正如作者在其博客中所说:
I/O threading is not going to happen in Redis AFAIK, because after much consideration I think it’s a lot of complexity without a good reason. Many Redis setups are network or memory bound actually. Additionally I really believe in a share-nothing setup, so the way I want to scale Redis is by improving the support for multiple Redis instances to be executed in the same host, especially via Redis Cluster.
What instead I really want a lot is slow operations threading, and with the Redis modules system we already are in the right direction. However in the future (not sure if in Redis 6 or 7) we’ll get key-level locking in the module system so that threads can completely acquire control of a key to process slow operations. Now modules can implement commands and can create a reply for the client in a completely separated way, but still to access the shared data set a global lock is needed: this will go away.
Redis作者更倾向于采用集群方式来解决I/O threading
,尤其是在6.0版本发布的原生Redis Cluster Proxy背景下,使得集群更加易用。此外,作者更倾向于slow operations threading
(比如4.0版本发布的Lazy Free
)来解决多线程问题。后续版本,是否会将IO Thread
实现的更加完善,采用Module实现对慢操作的优化,着实值得期待。
参考资料
-
Lazy Redis is better Redis
-
An update about Redis developments in 2019
-
阿里云Redis多线程性能提升思路解析
作者:景同学
来源:juejin.cn/post/6928407842009546766
END
十期推荐
【223期】面试官:在MySQL查询中,为什么要用小表驱动大表
【225期】面试官:公司项目中Java的多线程一般用在哪些场景?
【227期】面试官:说说双重检查加锁单例模式为什么两次判断?
【229期】面试官:怎么解决Eureka某一个服务挂掉的问题?
与其在网上拼命找题? 不如马上关注我们~
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/7947.html