Redis-网路模型

目录

Redis有多快?

Redis为何选择单线程?

Redis真的是单线程?

单线程事件循环

多线程异步任务

Redis多线程网络模型

设计思路

读取请求

写回响应

I/O线程主逻辑

模型缺陷

总结

相关链接:Redis 多线程网络模型全面揭秘 - Strike Freedom

Redis从本质上讲是一个网络服务器,对于一个网络服务器来说,网络模型是它的精华,因此搞懂一个网络服务器的网络模型,也就搞懂了它的本质

Redis有多快?

Redis的高性能得益于以下几个基础:

  • C语言实现:虽然C对Redis的性能有助力,不是核心因素
    纯内存I/O:Redis的纯内存操作有着天然的性能优势
  • I/O多路复用:基于epoll/select等I/O多路复用技术,实现高吞吐的网络I/O
  • 单线程模型:单线程无法利用多核,但从另一个层面来说避免了多线程频繁上下文切换,以及同步机制如锁带来的开销

Redis为何选择单线程?

对于一个DB来说,CPU通常不会是瓶颈,因为大多数请求不会是CPU密集型的,而是IO密集型。Redis是完全的纯内存操作,执行速度是非常快的,因此这部分操作通常不会是性能瓶颈,Redis真正的性能瓶颈在于网络IO,也就是客户端和服务端之间的网络传输

  • 避免过多的上下文切换开销:单线程可以规避进程内频繁的线程切换开销,因为程序始终运行在进程中单个线程内,没有多线程切换的场景
  • 避免同步机制的开销:因为Redis是一个数据库,势必会涉到底层数据同步的问题,那么必然会引入某些同步机制,比如锁,而Redis不仅仅提供了简单的Key-value数据结构,还有list、set和hash等等,而不同是数据结构对于同步访问的加锁粒度不同,会导致操作数据过程中带来很多加锁解锁的开销,增加程序的复杂度的同时还会降低性能
  • 简单可维护:多线程也不是尽善尽美,多线程的引入会使得程序不再保持逻辑上的串行性,代码执行的顺序将变成不可预测,会出现各种并发编程问题

Redis真的是单线程?

  1. Redis v4.0(引入多线程处理异步任务)
  2. Redis v6.0(在网络模型中实现I/O多线程)

单线程事件循环

在Redis v6.0之前,Redis的核心网络模型一直都是一个单Reactor模型:利用epoll/select/kqueue等多路复用技术,在单线程的事件循环中不断去处理事件(客户端请求),最后回写响应数据到客户端:内部实现了一个高性能的事件库-AE,基于 epoll/select/kqueue/evport 四种事件驱动技术,实现 Linux/MacOS/FreeBSD/Solaris 多平台的高性能事件循环模型。Redis 的核心网络模型正式构筑在 AE 之上,包括 I/O 多路复用、各类处理器的注册绑定,都是基于此才得以运行。

多线程异步任务

在Redis v4.0版本引入了多线程来做一些异步操作,主要针对那些非常耗时的命令,通过这些命令的执行进行异步化,避免阻塞单线程的事件循环。

例如:
Redis 的 DEL 命令是用来删除掉一个或多个 key 储存的值,它是一个阻塞的命令,如果你要删的是一个超大的键值对,里面有几百万个对象,那么这条命令可能会阻塞至少好几秒,又因为事件循环是单线程的,所以会阻塞后面的其他事件,导致吞吐量下降。

在Redis v4.0之后增加了一些的非阻塞命令:UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC。

UNLINK 命令其实就是 DEL 的异步版本,它不会同步删除数据,而只是把 key 从 keyspace 中暂时移除掉,然后将任务添加到一个异步队列,最后由后台线程去删除,不过这里需要考虑一种情况是如果用 UNLINK 去删除一个很小的 key,用异步的方式去做反而开销更大,所以它会先计算一个开销的阀值,只有当这个值大于 64 才会使用异步的方式去删除 key,对于基本的数据类型如 List、Set、Hash 这些,阀值就是其中存储的对象数量。

Redis多线程网络模型

随着互联网的飞速发展,互联网业务系统所要处理的线上流量越来越大,Redis的单线程模式会导致系统消耗很多CPU时间在网络I/O上从而降低吞吐量。因此在6.0版本之后,Redis正式在核心网络模型中引入多线程,也就是所谓的I/O threading,至此Redis真正拥有了多线程模型。

设计思路

虽然实现了多线程,但不是标准的Multi-Reactors/Master-Workers模式

大部分逻辑和之前单线程模型是一致的,仅仅是把读取客户端请求命令和回写响应数据的逻辑异步化了,交给I/O线程去完成,需要注意一点:I/O 线程仅仅是读取和解析客户端命令而不会真正去执行命令,客户端命令的执行最终还是要在主线程上完成。 

读取请求

当客户端发送请求命令之后,会触发Redis主线程的事件循环,命令处理器 readQueryFromClient 被回调,在以前的单线程模型下,这个方法会直接读取解析客户端命令并执行,但是多线程模式下,则会把 client 加入到 clients_pending_read 任务队列中去,后面主线程再分配到 I/O 线程去读取客户端请求命令。

这里的核心工作是:

  • 遍历待读取的client队列clients_pending_read,通过RR策略把所有任务分配给I/O线程和主线程去读取和解析客户端命令
  • 忙轮询等待所有I/O线程完成任务
  • 最后再遍历clients_pending_read,执行所有client的命令
写回响应

完成命令的读取、解析以及执行之后,客户端命令的响应数据已经存入 client->buf 或者 client->reply 中了,接下来就需要把响应数据回写到客户端了,还是在 beforeSleep 中, 主线程调用 handleClientsWithPendingWritesUsingThreads

这里的核心工作是:

  • 检查当前任务负载,如果当前的任务数量不足以用多线程模式处理的话,则休眠 I/O 线程并且直接同步将响应数据回写到客户端
  • 唤醒正在休眠的 I/O 线程(如果有的话)
  • 遍历待写出的 client 队列 clients_pending_write,通过 RR 策略把所有任务分配给 I/O 线程和主线程去将响应数据写回到客户端
  • 忙轮询等待所有 I/O 线程完成任务
  • 最后再遍历 clients_pending_write,为那些还残留有响应数据的 client 注册命令回复处理器 sendReplyToClient,等待客户端可写之后在事件循环中继续回写残余的响应数据
I/O线程主逻辑

I/O线程启动之后,会先进入忙轮询,判断原子计数器中的任务数量,如果是非0则表示主线程已经给它分配了任务,开始执行任务,否则就一直忙轮询一百万次等待,忙轮询结束之后再看计数器,如果还是0,则尝试加本地锁,因为主线程在启动I/O线程之时就已经提前锁住了所有I/O线程的本地锁,因此I/O线程会进行休眠,等待主线程唤醒。

主线程会在每次事件循环中尝试调用 startThreadedIO 唤醒 I/O 线程去执行任务,如果接收到客户端请求命令,则 I/O 线程会被唤醒开始工作,根据主线程设置的 io_threads_op 标识去执行命令读取和解析或者回写响应数据的任务,I/O 线程在收到主线程通知之后,会遍历自己的本地任务队列 io_threads_list[id],取出一个个 client 执行任务:

  • 如果当前是写出操作,则调出writeToClient,通过socket把client->buf或者client→reply里的响应数据回写到客户端
  • 如果当前是读取操作,则调用readQueryFromClient,通过socket读取客户端命令,存入Client→querybuf,然后调用processInputBuffer去解析命令,这里最终只会解析到一条命令,然后就结束,不会去执行命令
  • 在全部任务执行完之后把自己的原子计数器置0,以告知主线程自己已经完成了工作

模型缺陷

Redis的多线程网络模型实际并不是一个标准的Multi-Reactors/Master-Workers模型,和其他主流的开源网络服务器的模式有所区别,最大的不同就是在标准的Multi-Reactors/Master-Workers模式下,Sub Reactors/Workers会完成 网络读-》数据解析-》命令执行-》网络写整套流程,Main Reactor/Master只负责分派任务,而在Redis的多线程方案中,I/O线程任务仅仅是通过socket读取客户端请求命令并解析,却没有真正去执行命令,所有客户端命令最后还需要回到主线程去执行,因此对多核的利用率并不算高,而且每次主线程都必须在分配完任务之后轮询等待所有I/O线程完成之后才能继续执行其他逻辑

Redis之所以如此设计它的多线程网络模型,原因是为了保持兼容性,因为以前Redis是单线程的,所有的客户端命令都是在单线程的事件循环里执行的,也因为Redis里面的所有数据结构都是非线程安全的,如按照标准来实现,则所有内置的数据结构都必须重构成线程安全的,工作量的巨大且麻烦的

总结

Redis多线程网络模型的设计方案:

  • 使用I/O线程实现网络I/O多线程化,I/O线程只负责网络I/O和命令解析,不执行客户端命令
  • 利用原子操作+交错访问实现无锁的多线程模型
  • 通过设置CPU亲和性,隔离主进程和其他子线程,让多线程模型能发挥最大的性能

  • 31
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值