Redis 多线程的 Redis

early · August 19, 2019 · Last by aleafboat replied at May 27, 2020 · 13239 hits

今年年底将要发布的 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。是否多线程,倒不是必须的。

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

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

Reply to dongbala8

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

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

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

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

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

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

early in Redis 6.0 多线程 IO 处理过程详解 mention this topic. 31 May 02:01
You need to Sign in before reply, if you don't have an account, please Sign up first.