Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库
由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!
如图横轴是连接数,纵轴是QPS。
下图是一台普通4核服务器的get\set压测试结果:基本qps在12万左右
严格来说,Redis Server是多线程的,只是它的请求处理整个流程是单线程处理的。
这一点我们一定要清楚了解到,不要单纯地认为Redis Server是单线程的!
我们平时说的Redis单线程快是指它请求处理是用队列串行化处理的!
下面我们就来分析一下为什么请求处理使用单线程,依旧可以达到这么高的性能。
Redis的性能非常之高,每秒可以承受10W+的QPS,它如此优秀的性能主要取决于以下几个方面:
1).纯内存操作使用
2).IO多路复用技术
3).非CPU密集型任务
4).单线程的优势
5).多线程优化
1.纯内存操作
Redis是一个内存数据库,它的数据都存储在内存中,这意味着我们读写数据都是在内存中完成,这个速度是非常快的。
Redis是一个KV内存数据库,它内部构建了一个哈希表,根据指定的KEY访问时,只需要O(1)的时间复杂度就可以找到对应的数据。同时,Redis提供了丰富的数据类型,并使用高效的操作方式进行操作,这些操作都在内存中进行,并不会大量消耗CPU资源,所以速度极快。
2.使用IO多路复用技术
Redis采用单线程,那么它是如何处理多个客户端连接请求呢?
Redis采用了IO多路复用技术和非阻塞IO,这个技术由操作系统epoll实现,Redis可以方便地操作系统的API即可。Redis可以在单线程中监听多个Socket的请求,在任意一个Socket可读/可写时,Redis去读取客户端请求,在内存中操作对应的数据,然后再写回到Socket中。
整个过程非常高效,Redis利用了IO多路复用技术的事件驱动模型,保证在监听多个Socket连接的情况下,只针对有活动的Socket采取反应。
3.非CPU密集型任务
采用单线程的缺点很明显,无法使用多核CPU。Redis作者提到,由于Redis的大部分操作并不是CPU密集型任务,而Redis的瓶颈在于内存和网络带宽。
在高并发请求下,Redis需要更多的内存和更高的网络带宽,否则瓶颈很容易出现在内存不够用和网络延迟等待的情况。
当然,如果你觉得单个Redis实例的性能不足以支撑业务,Redis作者推荐部署多个Redis节点,组成集群的方式来利用多核CPU的能力,而不是在单个实例上使用多线程来处理。
4.单线程的优势
基于以上特性,Redis采用单线程已足够达到非常高的性能,官方认为单线程已经足够快了,而瓶颈更多是出在内存和高并发下带宽问题上,所以Redis没有采用多线程模型。
另外,单线程模型还带了以下好处:
没有了多线程上下文切换的性能损耗没有了访问共享资源加锁的性能损耗开发和调试非常友好,可维护性高所以Redis正是基于以上这些方面,所以采用了单线程模型来完成请求处理的工作。
5.多线程优化
在文章开头已经特别说明,Redis Server本身是多线程的,除了请求处理流程是单线程处理之外,Redis内部还有其他工作线程在后台执行,它负责异步执行某些比较耗时的任务,例如AOF每秒刷盘、AOF文件重写都是在另一个线程中完成的。
而在Redis 4.0之后,Redis引入了lazyfree的机制,提供了unlink、flushall aysc、flushdb async等命令和lazyfree-lazy-eviction、lazyfree-lazy-expire等机制来异步释放内存,它主要是为了解决在释放大内存数据导致整个redis阻塞的性能问题。
在删除大key时,释放内存往往都比较耗时,所以Redis提供异步释放内存的方式,让这些耗时的操作放到另一个线程中异步去处理,从而不影响主线程的执行,提高性能。
到了Redis 6.0,Redis又引入了多线程来完成请求数据的协议解析,进一步提升性能。它主要是解决高并发场景下,单线程解析请求数据协议带来的压力。请求数据的协议解析由多线程完成之后,后面的请求处理阶段依旧还是单线程排队处理。
可见,Redis并不是保守地认为单线程有多好,只是单线程也已经足够快了,瓶颈更多是会出现在内存和网络带宽上,而不是为了使用多线程而引入多线程,Redis作者很清楚单线程和多线程的使用场景,针对性地优化,这是非常值得我们学习的。
缺点
上面介绍了单线程可以达到如此高的性能,并不是说它就没有缺点了。
单线程处理最大的缺点,如果前一个请求发生耗时比较久的操作,那么整个Redis就会阻塞住,其他请求也无法进来,直到这个耗时久的操作处理完成并返回,其他请求才能被处理到。
我们平时遇到Redis变慢或长时间阻塞的问题,90%也都是因为Redis处理请求是单线程这个原因导致的。
所以,我们在使用Redis时,一定要避免非常耗时的操作,例如使用时间复杂度过高的方式获取数据、一次性获取过多的数据、大量key集中过期导致Redis淘汰key压力变大等等,这些场景都会阻塞住整个处理线程,直到它们处理完成,势必会影响业务的访问。
我会在后期的文章中专门介绍具体有哪些场景会引发Redis阻塞的问题,并提供规避问题的方法和优化方案。
总结
Redis使用单线程,配合IO多路复用技术,可以完成多个连接的请求处理。而且正是由于它的使用定位是内存数据库,这样几乎所有的操作都在内存中完成,它的性能可以达到非常之高。
同时,单线程没有了线程上下文切换和访问共享资源加锁的性能损耗,而且单线程模型对程序的开发和调试非常友好,因此Redis使用单线程模型也就在情理之中了。
Redis在最近的版本也对多线程进行了优化,用于解决释放大内存数据和请求数据协议解析对Redis产生的性能影响,进一步提升了Redis的性能。
单线程结合上述场景可以达到非常高的性能,同时也存在耗时操作阻塞整个线程的问题,我们在使用Redis时要避免耗时过长的操作,才能更好地发挥Redis的性能。