前言:
服务端一般须要支持高并发业务访问,如何设计优秀的服务端网络IO工做线程/进程模型对业务的高并发访问需求起着相当重要的核心做用。mysql
本文总结了了不一样场景下的多种网络IO线程/进程模型,并给出了各类模型的优缺点及其性能优化方法,很是适合服务端开发、中间件开发、数据库开发等开发人员借鉴。nginx
经常使用高并发网络线程模型设计及mongodb线程 模型优化实践(最全高并发网络IO线程模型设计)git
1. 线程模型一. 单线程网络IO复用模型github
1.1 说明:web
1. 全部网络IO事件(accept事件、读事件、写事件)注册到epoll事件集redis
2. 主循环中经过epoll_wait一次性获取内核态收集到的epoll事件信息,而后轮询执行各个事件对应的回调。sql
3. 事件注册、epoll_wait事件获取、事件回调执行所有由一个线程执行mongodb
1.2 该网络线程模型缺陷数据库
1. 全部工做都由一个线程执行,只要任一一个请求的事件回调处理阻塞,其余请求都会阻塞。例如redis的hash结构,若是filed过多,例如一个hash key包含数百万filed,则该Hash key过时的时候,整个redis阻塞。apache
2. 单线程工做模型,CPU会成为瓶颈,若是QPS超过10万,整个CPU负载会达到100%。
1.3 典型案例
1. redis缓存
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()流程一致。
2. 线程模型二. 单listener+固定worker线程
1. listener线程负责接受全部的客户端连接
2. listener线程每接收到一个新的客户端连接产生一个新的fd,而后经过分发器发送给对应的工做线程(hash方式)
3. 工做线程获取到对应的新连接fd后,后续该连接上的全部网络IO读写都由该线程处理
4. 假设有32个连接,则32个连接创建成功后,每一个线程平均处理4个连接上的读写、报文处理、业务逻辑处理
1.5 redis源码分析及异步网络IO复用精简版demo
因为以前工做须要,须要对redis内核作二次优化开发,所以对整个redis代码作了部分代码注释,同时把redis的网络模块独立出来作成了简单demo,该demo对理解epoll网络事件处理及Io复用实现会有帮助,代码比较简短,能够参考以下地址:
2.1 该网络线程模型缺陷
1. 进行accept处理的listener线程只有一个,在瞬间高并发场景容易成为瓶颈
2. 一个线程经过IO复用方式处理多个连接fd的数据读写、报文解析及后续业务逻辑处理,这个过程会有严重的排队现象,例如某个连接的报文接收解析完毕后的内部处理时间过长,则其余连接的请求就会阻塞排队
2.2 典型案例
memcache缓存,适用于内部处理比较快的缓存场景、代理中间场景。memcache源码实现中文分析能够详见: memcache源码实现分析
3. 线程模型三. 固定worker线程模型
该模型原型图以下:
说明:
1. Linux kernel 3.9开始支持reuseport功能,内核协议栈每获取到一个新连接自动均衡分发给用户态worker线程。
2. 该模型解决了模型一的listener单点瓶颈问题
3.1 该网络线程模型缺陷
reuseport支持后,内核经过负载均衡的方式分发不一样新连接到多个用户态worker进程/线程,每一个进程/线程经过IO复用方式处理多个客户端新连接fd的数据读写、报文解析、解析后的业务逻辑处理。每一个工做进程/线程同时处理多个连接的请求,若是某个连接的报文接收解析完毕后的内部处理时间过长,则其余连接的请求就会阻塞排队。
该模型虽然解决了listener单点瓶颈问题,可是工做线程内部的排队问题没有解决。
不过,Nginx做为七层转发代理,因为都是内存处理,因此内部处理时间比较短,因此适用于该模型。
3.2 典型案例
1. nginx(nginx用的是进程,模型原理同样),该模型适用于内部业务逻辑简单的场景,如nginx代理等
2. reuseport支持性能提高过程能够参考我另外一篇分享: http://www.javashuo.com/article/p-ofgalmlt-eh.html
Nginx多进程高并发、低时延、高可靠机制在缓存(redis、memcache)twemproxy代理中的应用
4. 线程模型四. 一个连接一个线程模型
该线程模型图以下图:
说明:
1. listener线程负责接受全部的客户端连接
2. listener线程每接收到一个新的客户端连接就建立一个线程,该线程只负责处理该连接上的数据读写、报文解析、业务逻辑处理。
4.1 该网络线程模型缺陷:
1. 一个连接建立一个线程,若是10万个连接,那么就须要10万个线程,线程数太多,系统负责、内存消耗也会不少
2. 当连接关闭的时候,线程也须要销毁,频繁的线程建立和消耗进一步增长系统负载
4.2 典型案例:
1. mysql默认方式、mongodb同步线程模型配置,适用于请求处理比较耗时的场景,如数据库服务
2. Apache web服务器,该模型限制了apache性能,nginx优点会更加明显
5. 线程模型五. 单listener+动态worker线程(单队列)
该线程模型图以下图所示:
说明:
1. listener线程接收到一个新连接fd后,把该fd交由线程池处理,后续该连接的全部读写、报文解析、业务处理都由线程池中多个线程处理。
2. 该模型把一次请求转换为多个任务(网络数据读写、报文解析、报文解析后的业务逻辑处理)入队到全局队列,线程池中的线程从队列中获取任务执行。
3. 同一个请求访问被拆分为多个任务,一次请求可能由多个线程处理。
4. 当任务太多,系统压力大的时候,线程池中线程数动态增长
5. 当任务减小,系统压力减小的时候,线程池中线程数动态减小
5.1 工做线程运行时间相关的几个统计:
T1:调用底层asio库接收一个完整mongodb报文的时间
T2:接收到报文后的后续全部处理(含报文解析、认证、引擎层处理、发送数据给客户端等)
T3: 线程等待数据的时间(例如:长时间没有流量,则如今等待读取数据)
5.2单个工做线程如何判断本身处于”空闲”状态:
线程运行总时间=T1 + T2 +T3,其中T3是无用等待时间。若是T3的无用等待时间占比很大,则说明线程比较空闲。工做线程每一次循环处理后判断有效时间占比,若是小于指定阀值,则本身直接exit退出销毁
5.3 如何判断线程池中工做线程“太忙”:
控制线程专门用于判断线程池中工做线程的压力状况,以此来决定是否在线程池中建立新的工做线程来提高性能。
控制线程每过必定时间循环检查线程池中的线程压力状态,实现原理就是简单的实时记录线程池中的线程当前运行状况,为如下两类计数:总线程数_threadsRunning、当前正在运行task任务的线程数_threadsInUse。若是_threadsRunning=_threadsRunning,说明全部工做线程当前都在处理task任务,线程池中线程压力大,这时候控制线程就开始增长线程池中线程数。该模型详细源码实现过程更多细节详见:https://my.oschina.net/u/4087916/blog/4295038
5.4 该网络线程模型缺陷:
1. 线程池获取任务执行,有锁竞争,这里就会成为系统瓶颈
5.5 典型案例:
5.5 典型案例:
mongodb动态adaptive线程模型,适用于请求处理比较耗时的场景,如数据库服务
该模型详细源码优化分析实现过程参考:
http://www.javashuo.com/article/p-obucbjcr-nu.html
Mongodb网络传输处理源码实现及性能调优-体验内核性能极致设计
6. 线程模型六. 单listener+动态worker线程(多队列)
该线程模型图以下:
说明:
把一个全局队列拆分为多个队列,任务入队的时候按照hash散列到各自的队列,工做线程获取获取任务的时候,同理经过hash的方式去对应的队列获取任务,经过这种方式减小锁竞争,同时提高总体性能。
6.1 典型案例:
OPPO自研mongodb内核多队列adaptive线程模型优化,性能有很好的提高,适用于请求处理比较耗时的场景,如数据库服务。该模型详细源码优化分析实现过程参考:https://my.oschina.net/u/4087916/blog/4295038