Redis 多线程的 Redis

early · 2019年08月19日 · 最后由 aleafboat 回复于 2020年05月27日 · 13472 次阅读

今年年底将要发布的 Redis6.0 会支持“多线程”,消息已经通过官方渠道发出。

其实严格意义上来讲,Redis 并不是单线程。它也有后台线程在工作,处理一些较为缓慢的操作,例如无用连接的释放、大 key 的删除等等。

但是客户端命令的请求获取 (socket 读)、解析、执行、内容返回 (socket 写) 等等都是由一个线程处理,所有操作是一个个挨着串行执行的 (主线程),这也是 Redis 有“单线程”定义的来源。

单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等“线程不安全”的命令都可以无锁进行。

但是这套机制也使得 Redis 的 QPS 难以更上一层楼。Redis 本身的数据结构设计,内存管理已经做得接近尽善尽美。要 Redis 单机性能进一步提升,引入多线程并发处理任务是最直观的方案之一,和 memcached 对齐。

多线程的机制有两大直观优点:

  • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
  • 多线程任务可以分摊 Redis 同步 IO 读写负荷

第一点,多线程执行提高并发度,进而提升 QPS 比较好理解。第二点需要单独做下解释。

常规情况下,Redis 使用的是同步非阻塞 IO,通过多路复用机制 (linux 上用 epoll) 封装成事件驱动机制。非阻塞 IO 在调用时不会导致进程因为等待 IO 事件而阻塞,通过 epoll 的机制,在 IO 事件发生时通过异步的方式通知用户态进程处理,这点极大地提高了 IO 的处理效率,事件模型大概如下:

Redis 抽象了一套 AE 事件模型,将 IO 事件和时间事件融入一起,同时借助 epoll 的回调特性,使得 IO 读写都是非阻塞的,实现高性能的网络处理能力。加上 Redis 基于内存的数据处理,这便是“单线程,但却高性能”的核心原因。

但 IO 数据的读写依然是阻塞的,这也是 Redis 目前的主要性能瓶颈之一,特别是在数据吞吐量特别大的时候,具体情况如下:

上图的下半部分,当 socket 中有数据时,Redis 会通过系统调用将数据从内核态拷贝到用户态,供 Redis 解析用。这个拷贝过程是阻塞的,术语称作“同步 IO”,数据量越大拷贝的延迟越高,时间消耗也越大,糟糕的是这些操作都是单线程处理的。(写 reponse 时也是一样)

这是 Redis 目前的瓶颈之一,Redis6.0 引入的“多线程”机制就是对于上诉瓶颈的优化。

核心思路是,将主线程的 IO 读写任务拆分出来给一组独立的线程执行,使得多个 socket 的读写可以并行化。(命令的执行依然是主线程串行执行)

核心流程大概如下:

流程简述如下:

  • 主线程获取 socket 放入等待列表
  • 将 socket 分配给各个 IO 线程(并不会等列表满)

  • 主线程阻塞等待 IO 线程读取 socket 完毕

  • 主线程执行命令 - 单线程(如果命令没有接收完毕,会等 IO 下次继续)

  • 主线程阻塞等待 IO 线程将数据回写 socket 完毕(一次没写完,会等下次再写)

  • 解除绑定,清空等待队列

有如下特点:

  • IO 线程要么同时在读 socket,要么同时在写,不会同时读或写
  • IO 线程只负责读写 socket 解析命令,不负责命令处理(主线程串行执行命令)
  • IO 线程数可自行配置(目前代码限制上限为 512,默认为 1(关闭此功能))

经过有心人士的压测,目前性能能提高 1 倍以上

不过目前有些疑问:

  • IO 线程数的设置应该按照怎样的标准设置

  • 如果有慢 client 拖慢了整个读写过程怎么办?(主线程在阻塞)(搞清楚这个伪问题,才真正理解 reids 多线程 IO)

谈下你的观点?

@dsh0416 接受挑战!

IO 线程数的设置应该按照怎样的标准设置

要靠 benchmark,主要要保证主线程一直在跑着。主线程如果比较闲,就加点。IO 线程消耗内存,太高了,不知道会不会爆。

再就是要看看极端情况 redis 的表现。

如果有慢 client 拖慢了整个读写过程怎么办?(主线程在阻塞)

感觉是这里面 http://antirez.com/news/126 说的 slow command。要能提供 command 执行一版,切换出去的能力,要有锁,应该还要有 transaction。是否多线程,倒不是必须的。

等待列表不满 一直阻塞不处理吗

等待列表不满 一直阻塞不处理吗

dongbala8 回复

阻塞时检测的是,IO 线程是否还有任务。等处理完了才继续往下。

这些任务是在执行时添加的,如果 任务数< 线程数,那有些线程就拿不到任务,它的待处理任务就是 0。

分配了任务的线程,在处理好 IO 事件后,pending 就会清零,没拿到任务的线程 pending 本来就是 0,所以不会阻塞。

这个点,在计划写文章捋清楚。到时再交流哈。

如果有慢 client 拖慢了整个读写过程怎么办?(主线程在阻塞)(搞清楚这个伪问题,才真正理解 reids 多线程 IO)

这是滑动窗口的,问题,有操作系统解决,一个应用服务 不会花时间来解决这个问题。

early Redis 6.0 多线程 IO 处理过程详解 提及了此话题。 05月31日 02:01
需要 登录 后方可回复, 如果你还没有账号请 注册新账号