Redis的自白:我为何在单线程的这条路上越走越远?

我是 Redis,今年 11 岁了~redis

曾几什么时候我是辣么的单纯,辣么的可爱,而现在我竟背叛了当初“誓言”,决心在多线程这条路上义无反顾的一路狂奔,没错我就是大家口中那个既可爱又迷人的 Redis,你能够叫我小 R...R 😊。安全

一波骚操做结束,咱们开始今天的正文。bash

咱们知道在 Redis 4.0 以后就陆陆续续添加了一些多线程的功能,难道单线程不香了吗?网络

单线程慢吗?

Redis 的单线程曾几什么时候仍是咱们炫耀的资本,优雅又不失高效的设计,让无数的追求者为之着迷。数据结构

你要问我排第几?Nginx 是我大哥,NodeJS 是我小弟,我在家中排名老二多线程

img

咱们兄弟仨可谓单线程的杰出表明,不只演示了咱们的优雅更加展示了咱们的高效。并发

🙋‍♂️有人可能会问:为何单线程的我,居然如此嚣张?app

家中有矿呗,Redis 单线程但性能依旧很快的主要缘由有如下几点:异步

  1. 基于内存操做:Redis 的全部数据都存在内存中,所以全部的运算都是内存级别的,因此他的性能比较高;
  2. 数据结构简单:Redis 的数据结构比较简单,是为 Redis 专门设计的,而这些简单的数据结构的查找和操做的时间复杂度都是 O(1),所以性能比较高;
  3. 多路复用和非阻塞 I/O:Redis 使用 I/O 多路复用功能来监听多个 socket 链接客户端,这样就可使用一个线程链接来处理多个请求,减小线程切换带来的开销,同时也避免了 I/O 阻塞操做,从而大大提升了 Redis 的性能;
  4. 避免上下文切换:由于是单线程模型,所以就避免了没必要要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗,并且单线程不会致使死锁问题的发生。

来看一下个人父亲大大是如何评价个人,Redis 的 FAQ(Frequently Asked Questions,常见问题)回答了单线程的这个问题,具体内容以下:socket

Redis is single threaded. How can I exploit multiple CPU / cores?

It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.

However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier.

You can find more information about using multiple Redis instances in the Partitioning page.

However with Redis 4.0 we started to make Redis more threaded. For now this is limited to deleting objects in the background, and to blocking commands implemented via Redis modules. For future releases, the plan is to make Redis more and more threaded.

详见:redis.io/topics/faq

他的大致意思是说 Redis 是基于内存操做的,所以他的瓶颈多是机器的内存或者网络带宽而并不是 CPU,既然 CPU 不是瓶颈,那么天然就采用单线程的解决方案了,何况使用多线程比较麻烦。可是在 Redis 4.0 中开始支持多线程了,例如后台删除等功能。

简单来讲,Redis 4.0 以前一直采用单线程的主要缘由有如下三个:

  1. 使用单线程模型是 Redis 的开发和维护更简单,由于单线程模型方便开发和调试;
  2. 即便使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用和非阻塞 IO;
  3. 对于 Redis 系统来讲,主要的性能瓶颈是内存或者网络带宽而并不是 CPU。

img

为何须要多线程?

可是单线程也有单线程的苦恼,好比当我(Redis)须要删除一个很大的数据时,由于是单线程同步操做,这就会致使 Redis 服务卡顿,因而在 Redis 4.0 中就新增了多线程的模块,固然此版本中的多线程主要是为了解决删除数据效率比较低的问题的,他的相关指令有如下三个:

  1. unlink key
  2. flushdb async
  3. flushall async

执行示例以下所示:

> unlink key # 后台删除某个 key
> OK # 执行成功
> flushall async # 清空全部数据
> OK # 执行成功
复制代码

这样我就能够把这些坏人“瞬间”拉黑(删除)了。

img

所谓的“瞬间”删除其实有些夸张,只是从返回的结果来看是删除成功了,可是这只是把删除工做交给了后台的小弟(子线程)异步来删除数据了

小贴士:正常状况下使用 del 指令能够很快的删除数据,而当被删除的 key 是一个很是大的对象时,例如时包含了成千上万个元素的 hash 集合时,那么 del 指令就会形成 Redis 主线程卡顿,所以使用惰性删除能够有效的避免 Redis 卡顿的问题。

Redis 6 中的多线程

以前在 Redis 4.0 中你说删除比较慢,骗我开大(多线程)来处理也就罢了,为毛 Redis 6.0 还要多线程嘞?

实际上是这样的在 Redis 4.0 版本中虽然引入了多线程,但此版本中的多线程只能用于大数据量的异步删除,然而对于非删除操做的意义并非很大。

但若是咱们使用咱们在非删除的环境下使用多线程的话就能够分摊 Redis 同步读写 I/O 的压力,以及充分的利用多核 CPU 的资源了,这样就能够有效的提高 Redis 的 QPS(Query Per Second,每秒查询率)了。

在 Redis 中虽然使用了 I/O 多路复用,而且是基于非阻塞 I/O 进行操做的,但 I/O 的读和写自己是堵塞的,好比当 socket 中有数据时,Redis 会经过调用先将数据从内核态空间拷贝到用户态空间,再交给 Redis 调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所须要的时间就越多,而这些操做都是基于单线程完成的。

img

I/O 多路复用,简单来讲就是经过监测文件的读写事件,再通知线程执行相关操做,保证 Redis 的非阻塞 I/O 可以顺利执行完成的机制。

所以在 Redis 6.0 中新增了多线程的功能来提升 I/O 的读写性能,他的主要实现思路是将主线程的 IO 读写任务拆分给一组独立的线程去执行,这样就可使多个 socket 的读写能够并行化了,但 Redis 的命令依旧是由主线程串行执行的

须要注意的是 Redis 6.0 默认是禁用多线程的,能够经过修改 Redis 的配置文件 redis.conf 中的 io-threads-do-reads 等于 true 来开启多线程,完整配置为 io-threads-do-reads true,除此以外咱们还须要设置线程的数量才能正确的开启多线程的功能,一样是修改 Redis 的配置,例如设置 io-threads 4 表示开启 4 个线程。

小贴士:关于线程数的设置,官方的建议是若是为 4 核的 CPU,建议线程数设置为 2 或 3,若是为 8 核 CPU 建议线程数设置为 6,线程数必定要小于机器核数,线程数并非越大越好。

关于 Redis 的性能,个人父王 antirez(Redis 做者)在 RedisConf 2019 分享时曾提到,Redis 6 引入的多线程 I/O 特性对性能提高至少是一倍以上。国内也有人在阿里云使用 4 个线程的 Redis 版本和单线程的 Redis 进行比较测试,发现测试的结果和 antirez 给出的结论基本吻合,性能基本能够提升一倍

img

总结

Redis 虽然依靠本身的:基于内存操做、数据结构简单、多路复用和非阻塞 I/O、避免了没必要要的线程上下文切换等特性,在单线程的环境下依然很快;但对于大数据的 key 删除仍是卡的飞起,所以在 Redis 4.0 引入了多线程:unlink key/flushall async 等命令,主要用于 Redis 数据的删除,而在 Redis 6.0 中引入了 I/O 多线程的读写,这样就能够更加高效的处理更多的任务****了,Redis 只是将 I/O 读写变成了多线程,而命令的执行依旧是由主线程串行执行的,所以在多线程下操做 Redis 不会出现线程安全的问题。

Redis 不管是当初的单线程设计,仍是现在与当初设计相背的多线程,目的只有一个:让 Redis 变得愈来愈快。

因此 Redis 依旧没变,他仍是那个曾经的追风少年~

最后的话

原创不易,若是以为本文对你有用,请随手点击一个「」,这是对做者最大的支持与鼓励,谢谢你。

关注公众号「Java中文社群」回复“干货”,获取 50 篇原创干货 Top 榜

相关文章
相关标签/搜索