2020.07月更新:

在深入了解了Redis的底层之后,我发现redis对于底层数据结构的设计非常巧妙,这也在很大程度上加快了redis的速度,这部分可以参考:

Redis源码剖析和注释(七)— 快速列表(quicklist)
Redis详解(四)—— redis的底层数据结构

===========

Redis作为一种Key-Value形式的NoSQL,因其极高的读写速度深受开发者喜爱,在web、分布式等领域有非常广泛的应用。

根据runoob的介绍, Redis能读的速度是110000次/s,写的速度是81000次/s。

Redis的快只是因为它是基于内存的吗?这里有一篇详细的文章对比了目前最流行的两种NoSQL—— Redis和MongoDB的性能,Redis vs. MongoDB: Comparing In-Memory Databases with Percona Memory Engine,其中MongoDB使用了Percona内存引擎,因此可以认为实验中的MongoDB和Redis (3.2.8)都是基于内存读写的(不然就没有比较的意义了)。文中做了三组对比实验,分别是:

  • 1.数据插入 —— 100%写,数据量为250万
  • 2.任务A —— 重读写,50% 读 + 50% 写,250万次操作
  • 3.任务B —— 重读取,95%读 + 5% 写,250万次操作

实验结果分别如下:

可以发现,即使是利用了所有CPU核心的mongoDB,吞吐量在大多场景中仍然比不上单线程的Redis。

所以Redis为什么这么快呢?总结起来大约以下三点:

  • 1.基于内存
  • 2.单线程
  • 3.IO 多路复用

Redis 6.0引入了多线程机制,性能将会有进一步的提高,可参考Redis 6.0 多线程来袭。但本文不讨论这一点,主要讨论为什么在单线程的情况下,Redis为什么仍然能做到这么高的读写?

单线程的优势

多线程的效率一定比单线程更高吗?

熟悉Java的同学应该知道,Java中与Hash相关的集合有HashMap,HashTable,ConcurrentHashMap等,其中HashMap是线程不安全的,就是如果多线程环境中有多个线程同时操作该集合的话,可能导致数据污染的问题,因而有了HashTable的出现。但是在实际操作中,发现HashTable的性能比较低,因为HashTable靠一把锁来维护全部的数据,所有的线程在操作数据时只能去竞争那一把锁,造成线程等待,故而性能降低。为了克服这个问题,在JDK1.5~1.7版本,Java使用了分段锁机制实现ConcurrentHashMap。这就是多线程中锁机制造成的性能降低,此外,还有上下文切换的代价。

CPU的每个核在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换。由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。每一次切换都是需要花时间的,所以在计算密集型的任务中,多线程(或多进程)的数量不宜超过CPU的核心数,超过之后CPU只是会徒劳的花时间在上下文切换上。

而Redis使用单线程,就不需要考虑上述问题了。

IO多路复用

然而单线程的问题也很大,由于线程的执行过程是顺序执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务。为了解决这个问题,因此有了IO多路复用。

I/O多路复用的核心是用户进程调用select函数被阻塞,同时内核轮询监听所有select 负责的socket,当任何一个socket有数据准备好了,select就将控制权返回用户进程。多路复用的具体实现方式有select / poll / epoll等,Redis内部实现采用epoll,采用了epoll+自己实现的简单的事件框架,。相比select和poll,epoll有以下几个优势:

  • epoll没有最大连接数限制 (select 的最大限制为1024)
  • Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll,参考下图:
  • 内存拷贝, select 和poll 在数据准备好之后,需要把数据从内核拷贝到用户进程,Epoll使用了“共享内存”,避免了内存拷贝。
  • epoll线程安全

总结

Redis 使用了I/O多路复用,保证了redis在进行I/O操作时依然能处理socket请求,不会在I/O上浪费时间,单线程机制也避免了不必要的上下文切换和锁机制。而Redis的每一次I/O操作都是基于内存的,非常高效。可以说,redis的这三个特性相辅相成,共同造就了Redis的高并发。

参考

1.Redis vs. MongoDB: Comparing In-Memory Databases with Percona Memory Engine

2.知乎-IO 多路复用是什么意思

3.Redis到底是多线程还是单线程?线程安全吗,还需要加锁吗?

4.Redis IO多路复用技术以及epoll实现原理

5.并发编程

6.Redis单线程?别逗了,Redis6.0多线程重磅来袭!

点击量:1709

分类: 开发笔记

发表评论

电子邮件地址不会被公开。 必填项已用*标注