服务端通常须要反对高并发业务拜访,如何设计优良的服务端网络IO工作线程/过程模型对业务的高并发拜访需要起着至关重要的核心作用。
本文总结了了不同场景下的多种网络IO线程/过程模型,并给出了各种模型的优缺点及其性能优化办法,非常适合服务端开发、中间件开发、数据库开发等开发人员借鉴。
(顺便提一下,OPPO互联网文档数据库团队急缺mongodb内核及存储引擎研发人才,欢送退出oppo参加千万级峰值tps/万亿级数据量数据库研发。联系人:yangyazhou#oppo.com)
1. 线程模型一. 单线程网络IO复用模型
阐明:
- 所有网络IO事件(accept事件、读事件、写事件)注册到epoll事件集
- 主循环中通过epoll_wait一次性获取内核态收集到的epoll事件信息,而后轮询执行各个事件对应的回调。
- 事件注册、epoll_wait事件获取、事件回调执行全副由一个线程解决
1.1一个残缺申请组成
一个残缺的申请处理过程次要蕴含以下几个局部:
步骤1:通过epoll_wait一次性获取网络IO事件
步骤2:读取数据及协定解析
步骤3:解析胜利后进行业务逻辑解决,而后应答客户端
1.2 该网络线程模型缺点
- 所有工作都由一个线程执行,包含epoll事件获取、事件处理(数据读写)、只有任一一个申请的事件回调解决阻塞,其余申请都会阻塞。例如redis的hash构造,如果filed过多,假如一个hash key蕴含数百万filed,则该Hash key过期的时候,整个redis阻塞。
- 单线程工作模型,CPU会成为瓶颈,如果QPS过高,整个CPU负载会达到100%,时延抖动厉害。
1.3 典型案例
- redis缓存
- 推特缓存中间件twemproxy
1.4 主循环工作流程
while (1) { //epoll_wait期待网络事件,如果有网络事件则返回,或者超时范畴 size_t numevents= epoll_wait(); //遍历后面epoll获取到的网络事件,执行对应事件回调 for (j = 0; j < numevents; j++) { if(读事件) { //读数据 readData()//解析parseData() //读事件处理、读到数据后的业务逻辑解决 requestDeal() } else if(写事件) { //写事件处理,写数据逻辑解决 writeEentDeal() } else {//异样事件处理errorDeal() } }}
阐明:后续多线程/过程模型中,每个线程/过程的主流程和该while()流程统一。
1.5 redis源码剖析及异步网络IO复用精简版demo
因为之前工作须要,须要对redis内核做二次优化开发,因而对整个redis代码做了局部代码正文,同时把redis的网络模块独立进去做成了简略demo,该demo对了解epoll网络事件处理及Io复用实现会有帮忙,代码比拟简短,能够参考如下我的项目:
redis源码具体正文剖析
redis网络模块精简版demo
推特缓存中间件twemproxy源码剖析实现
2. 线程模型二. 单listener+固定worker线程
该线程模型图如下图所示:
阐明:
- listener线程负责承受所有的客户端链接
- listener线程每接管到一个新的客户端链接产生一个新的fd,而后通过散发器发送给对应的工作线程(hash形式)
- 工作线程获取到对应的新链接fd后,后续该链接上的所有网络IO读写都由该线程解决
- 假如有32个链接,则32个链接建设胜利后,每个线程均匀解决4个链接上的读写、报文解决、业务逻辑解决
2.1 该网络线程模型缺点
- 进行accept解决的listener线程只有一个,在霎时高并发场景容易成为瓶颈
- 一个线程通过IO复用形式解决多个链接fd的数据读写、报文解析及后续业务逻辑解决,这个过程会有重大的排队景象,例如某个链接的报文接管解析结束后的外部解决工夫过长,则其余链接的申请就会阻塞排队
2.2 典型案例
memcache缓存,实用于外部解决比拟快的缓存场景、代理两头场景。memcache源码实现中文剖析能够详见: memcache源码实现剖析
3. 线程模型三. 固定worker线程模型(reuseport)
该模型原型图如下:
阐明:
- Linux kernel 3.9开始反对reuseport性能,内核协定栈每获取到一个新链接主动平衡分发给用户态worker线程。
- 该模型解决了模型一的listener单点瓶颈问题,多个过程/线程同时做为listener,都能够accept客户端新链接。
3.1该网络过程/线程模型缺点
reuseport反对后,内核通过负载平衡的形式散发不同新链接到多个用户态worker过程/线程,每个过程/线程通过IO复用形式解决多个客户端新链接fd的数据读写、报文解析、解析后的业务逻辑解决。每个工作过程/线程同时解决多个链接的申请,如果某个链接的报文接管解析结束后的外部解决工夫过长,则其余链接的申请就会阻塞排队。
该模型尽管解决了listener单点瓶颈问题,然而工作线程外部的排队问题没有解决。
不过,Nginx作为七层转发代理,因为都是内存解决,所以外部解决工夫比拟短,所以实用于该模型。
3.2典型案例
- nginx(nginx用的是过程,模型原理一样),该模型实用于外部业务逻辑简略的场景,如nginx代理等
- reuseport反对性能晋升过程能够参考另一篇分享:Nginx多过程高并发、低时延、高牢靠机制在缓存(redis、memcache)twemproxy代理中的利用
另,参考nginx源码中文正文剖析
4. 线程模型四:一个链接一个线程模型
该线程模型图如下图:
阐明:
- listener线程负责承受所有的客户端链接
- listener线程每接管到一个新的客户端链接就创立一个线程,该线程只负责解决该链接上的数据读写、报文解析、业务逻辑解决。
4.1 该网络线程模型缺点:
- 一个链接创立一个线程,如果10万个链接,那么就须要10万个线程,线程数太多,零碎负责、内存耗费也会很多
- 当链接敞开的时候,线程也须要销毁,频繁的线程创立和耗费进一步减少零碎负载
4.2 典型案例:
- mysql默认形式、mongodb同步线程模型配置,实用于申请解决比拟耗时的场景,如数据库服务
- Apache web服务器,该模型限度了apache性能,nginx劣势会更加显著
5. 线程模型五:单listener+动静worker线程(单队列)
该线程模型图如下图所示:
阐明:
- listener线程接管到一个新链接fd后,把该fd交由线程池解决,后续该链接的所有读写、报文解析、业务解决都由线程池中多个线程解决。
- 该模型把一次申请转换为多个工作(网络数据读写、报文解析、报文解析后的业务逻辑解决)入队到全局队列,线程池中的线程从队列中获取工作执行。
- 同一个申请拜访被拆分为多个工作,一次申请可能由多个线程解决。
- 当工作太多,零碎压力大的时候,线程池中线程数动静减少
- 当工作缩小,零碎压力缩小的时候,线程池中线程数动静缩小
5.1 工作线程运行工夫相干的几个统计:
T1:调用底层asio库接管一个残缺mongodb报文的工夫
T2:接管到报文后的后续所有解决(含报文解析、认证、引擎层解决、发送数据给客户端等)
T3: 线程期待数据的工夫(例如:长时间没有流量,则当初期待读取数据)
5.2单个工作线程如何判断本人处于”闲暇”状态:
线程运行总工夫=T1 + T2 +T3,其中T3是无用等待时间。如果T3的无用等待时间占比很大,则阐明线程比拟闲暇。工作线程每一次循环解决后判断无效工夫占比,如果小于指定阀值,则本人间接exit退出销毁
5.3 如何判断线程池中工作线程“太忙”:
控制线程专门用于判断线程池中工作线程的压力状况,以此来决定是否在线程池中创立新的工作线程来晋升性能。
控制线程每过肯定工夫循环查看线程池中的线程压力状态,实现原理就是简略的实时记录线程池中的线程以后运行状况,为以下两类计数:总线程数_threadsRunning、以后正在运行task工作的线程数_threadsInUse。如果_threadsRunning=_threadsRunning,阐明所有工作线程以后都在解决task工作,线程池中线程压力大,这时候控制线程就开始减少线程池中线程数。
该模型具体源码实现过程更多细节咱们之前公布的文章:MongoDB网络传输解决源码实现及性能调优——体验内核性能极致设计
5.4 该网络线程模型缺点:
- 线程池获取工作执行,有锁竞争,这里就会成为零碎瓶颈
5.5 典型案例:
mongodb动静adaptive线程模型,实用于申请解决比拟耗时的场景,如数据库服务。
该模型具体源码优化剖析实现过程参考:
Mongodb网络传输解决源码实现及性能调优-体验内核性能极致设计
6. 线程模型六. 单listener+动静worker线程(多队列)-mongodb网络线程模型优化实际
该线程模型图如下:
阐明:
把一个全局队列拆分为多个队列,工作入队的时候依照hash散列到各自的队列,工作线程获取获取工作的时候,同理通过hash的形式去对应的队列获取工作,通过这种形式缩小锁竞争,同时晋升整体性能。
6.1典型案例:
OPPO自研mongodb内核多队列adaptive线程模型优化,性能有很好的晋升,实用于申请解决比拟耗时的场景,如数据库服务。
该模型具体源码优化剖析实现过程同样参考:Mongodb网络传输解决源码实现及性能调优-体验内核性能极致设计
6.2 疑难?为啥mysql、mongodb等数据库没有利用内核的reuseport非凡-多线程同时解决accept申请?
答:实际上所有服务都能够利用这一个性,包含数据库服务(mongodb、mysql等)。然而因为数据库服务拜访时延个别都是ms级别,如果reuseport个性利用起来,时延会有几十us的性能晋升,这相比数据库外部解决的ms级时延,这几十us的性能晋升,基本上能够疏忽掉,这也是大部分数据库服务没有反对该性能的起因。
缓存,代理等中间件,因为自身外部解决工夫就比拟小,也是us级别,所以须要充分利用该个性。