Socket Server的N种并发模型汇总

标签: socket server 并发 | 发表时间:2020-04-06 15:49 | 作者:aceld
出处:https://juejin.im/tag/%E6%9E%B6%E6%9E%84

原创声明 作者: 刘丹冰Aceld,微信公众号同名

本文主要介绍常见的Server的并发模型,这些模型与编程语言本身无关,有的编程语言可能在语法上直接透明了模型本质,所以开发者没必要一定要基于模型去编写,只是需要知道和了解并发模型的构成和特点即可。

那么在了解并发模型之前,我们需要两个必备的前置知识:

  • socket网络编程
  • 多路IO复用机制
  • 多线程/多进程等并发编程理论

模型一、单线程Accept(无IO复用)

(1) 模型结构图

(2) 模型分析

① 主线程 main thread执行阻塞Accept,每次客户端Connect链接过来, main thread中accept响应并建立连接

② 创建链接成功,得到 Connfd1套接字后, 依然在 main thread串行处理套接字读写,并处理业务。

③ 在②处理业务中,如果有新客户端 Connect过来, Server无响应,直到当前套接字全部业务处理完毕。

④ 当前客户端处理完后,完毕链接,处理下一个客户端请求。

(3) 优缺点

优点

  • socket编程流程清晰且简单,适合学习使用,了解socket基本编程流程。

缺点

  • 该模型并非并发模型,是串行的服务器,同一时刻,监听并响应最大的网络请求量为 1。 即并发量为 1

  • 仅适合学习基本socket编程,不适合任何服务器Server构建。


模型二、单线程Accept+多线程读写业务(无IO复用)

(1) 模型结构图

(2) 模型分析

① 主线程 main thread执行阻塞Accept,每次客户端Connect链接过来, main thread中accept响应并建立连接

② 创建链接成功,得到 Connfd1套接字后,创建一个新线程 thread1用来处理客户端的读写业务。 main thead依然回到 Accept阻塞等待新客户端。

thread1通过套接字 Connfd1与客户端进行通信读写。

④ server在②处理业务中,如果有新客户端 Connect过来, main threadAccept依然响应并建立连接,重复②过程。

(3) 优缺点

优点

  • 基于 模型一:单线程Accept(无IO复用) 支持了并发的特性。
  • 使用灵活,一个客户端对应一个线程单独处理, server处理业务内聚程度高,客户端无论如何写,服务端均会有一个线程做资源响应。

缺点

  • 随着客户端的数量增多,需要开辟的线程也增加,客户端与server线程数量 1:1正比关系,一次对于高并发场景,线程数量收到硬件上限瓶颈。
  • 对于长链接,客户端一旦无业务读写,只要不关闭,server的对应线程依然需要保持连接(心跳、健康监测等机制),占用连接资源和线程开销资源浪费。
  • 仅适合客户端数量不大,并且数量可控的场景使用。

仅适合学习基本socket编程,不适合任何服务器Server构建。


模型三、单线程多路IO复用

(1) 模型结构图

(2) 模型分析

① 主线程 main thread创建 listenFd之后,采用多路I/O复用机制(如:select、epoll)进行IO状态阻塞监控。有 Client1客户端 Connect请求,I/O复用机制检测到 ListenFd触发读事件,则进行 Accept建立连接,并将新生成的 connFd1加入到 监听I/O集合中。

Client1再次进行正常读写业务请求, main thread多路I/O复用机制阻塞返回,会触该套接字的读/写事件等。

③ 对于 Client1的读写业务,Server依然在 main thread执行流程提继续执行,此时如果有新的客户端 Connect链接请求过来,Server将没有即时响应。

④ 等到Server处理完一个连接的 Read+Write操作,继续回到 多路I/O复用机制阻塞,其他链接过来重复 ②、③流程。

(3) 优缺点

优点

  • 单流程解决了可以同时监听多个客户端读写状态的模型,不需要 1:1与客户端的线程数量关系。
  • 多路I/O复用阻塞,非忙询状态,不浪费CPU资源, CPU利用率较高。

缺点

  • 虽然可以监听多个客户端的读写状态,但是同一时间内,只能处理一个客户端的读写操作,实际上读写的业务并发为1。
  • 多客户端访问Server,业务为串行执行,大量请求会有排队延迟现象,如图中⑤所示,当 Client3占据 main thread流程时, Client1,Client2流程卡在 IO复用等待下次监听触发事件。

模型四、单线程多路IO复用+多线程读写业务(业务工作池)

(1) 模型结构图

(2) 模型分析

① 主线程 main thread创建 listenFd之后,采用多路I/O复用机制(如:select、epoll)进行IO状态阻塞监控。有 Client1客户端 Connect请求,I/O复用机制检测到 ListenFd触发读事件,则进行 Accept建立连接,并将新生成的 connFd1加入到 监听I/O集合中。

② 当 connFd1有可读消息,触发读事件,并且进行读写消息

main thread按照固定的协议读取消息,并且交给 worker pool工作线程池, 工作线程池在server启动之前就已经开启固定数量的 thread,里面的线程只处理消息业务,不进行套接字读写操作。

④ 工作池处理完业务,触发 connFd1写事件,将回执客户端的消息通过 main thead写给对方。

(3) 优缺点

优点

  • 对于 模型三, 将业务处理部分,通过工作池分离出来,减少多客户端访问Server,业务为串行执行,大量请求会有排队延迟时间。
  • 实际上读写的业务并发为1,但是业务流程并发为worker pool线程数量,加快了业务处理并行效率。

缺点

  • 读写依然为 main thread单独处理,最高读写并行通道依然为1.
  • 虽然多个worker线程处理业务,但是最后返回给客户端,依旧需要排队,因为出口还是 main threadRead + Write

模型五、单线程IO复用+多线程IO复用(链接线程池)

(1) 模型结构图

(2) 模型分析

① Server在启动监听之前,开辟固定数量(N)的线程,用 Thead Pool线程池管理

② 主线程 main thread创建 listenFd之后,采用多路I/O复用机制(如:select、epoll)进行IO状态阻塞监控。有 Client1客户端 Connect请求,I/O复用机制检测到 ListenFd触发读事件,则进行 Accept建立连接,并将新生成的 connFd1分发给 Thread Pool中的某个线程进行监听。

Thread Pool中的每个 thread都启动 多路I/O复用机制(select、epoll),用来监听 main thread建立成功并且分发下来的socket套接字。

④ 如图, thread监听 ConnFd1、ConnFd2, thread2监听 ConnFd3, thread3监听 ConnFd4. 当对应的 ConnFd有读写事件,对应的线程处理该套接字的读写及业务。

(3) 优缺点

优点

  • main thread的单流程读写,分散到多线程完成,这样增加了同一时刻的读写并行通道,并行通道数量 NN为线程池 Thread数量。
  • server同时监听的 ConnFd套接字数量几乎成倍增大,之前的全部监控数量取决于 main thread多路I/O复用机制的最大限制***(select 默认为1024, epoll默认与内存大小相关,约3~6w不等)***,所以理论单点Server最高响应并发数量为 N*(3~6W)( N为线程池 Thread数量,建议与CPU核心成比例1:1)。
  • 如果良好的线程池数量和CPU核心数适配,那么可以尝试CPU核心与Thread进行绑定,从而降低CPU的切换频率,提升每个 Thread处理合理业务的效率,降低CPU切换成本开销。

缺点

  • 虽然监听的并发数量提升,但是最高读写并行通道依然为 N,而且多个身处同一个Thread的客户端,会出现读写延迟现象,实际上每个 Thread的模型特征与 模型三:单线程多路IO复用一致。

模型五(进程版)、单进程多路I/O复用+多进程多路I/O复用(进程池)

(1) 模型结构图

(2) 模型分析

五、单线程IO复用+多线程IO复用(链接线程池)无大差异。

不同处

  • 进程和线程的内存布局不同导致, main process(主进程)不再进行 Accept操作,而是将 Accept过程分散到各个 子进程(process)中.
  • 进程的特性,资源独立,所以 main process如果Accept成功的fd,其他进程无法共享资源,所以需要各子进程自行Accept创建链接
  • main process只是监听 ListenFd状态,一旦触发读事件(有新连接请求). 通过一些IPC(进程间通信:如信号、共享内存、管道)等, 让各自子进程 Process竞争 Accept完成链接建立,并各自监听。
(3) 优缺点

五、单线程IO复用+多线程IO复用(链接线程池)无大差异。

不同处:

多进程内存资源空间占用稍微大一些

多进程模型安全稳定型较强,这也是因为各自进程互不干扰的特点导致。


模型六、单线程多路I/O复用+多线程多路I/O复用+多线程

(1) 模型结构图

(2) 模型分析

① Server在启动监听之前,开辟固定数量(N)的线程,用 Thead Pool线程池管理

② 主线程 main thread创建 listenFd之后,采用多路I/O复用机制(如:select、epoll)进行IO状态阻塞监控。有 Client1客户端 Connect请求,I/O复用机制检测到 ListenFd触发读事件,则进行 Accept建立连接,并将新生成的 connFd1分发给 Thread Pool中的某个线程进行监听。

Thread Pool中的每个 thread都启动 多路I/O复用机制(select、epoll),用来监听 main thread建立成功并且分发下来的socket套接字。一旦其中某个被监听的客户端套接字触发 I/O读写事件,那么,会立刻开辟一个新线程来处理 I/O读写业务。

④ 但某个读写线程完成当前读写业务,如果当前套接字没有被关闭,那么将当前客户端套接字 如:ConnFd3重新加回线程池的监控线程中,同时自身线程自我销毁。

(3) 优缺点

优点

  • 模型五、单线程IO复用+多线程IO复用(链接线程池)基础上,除了能够保证同时响应的 最高并发数,又能解决 读写并行通道局限的问题。

  • 同一时刻的读写并行通道,达到 最大化极限,一个客户端可以对应一个单独执行流程处理读写业务,读写并行通道与客户端数量 1:1关系。

缺点

  • 该模型过于理想化,因为要求CPU核心数量足够大。
  • 如果硬件CPU数量可数(目前的硬件情况),那么该模型将造成大量的CPU切换成本浪费。因为为了保证读写并行通道与客户端 1:1的关系,那么Server需要开辟的 Thread数量就与客户端一致,那么线程池中做 多路I/O复用的监听线程池绑定CPU数量将变得毫无意义。
  • 如果每个临时的读写 Thread都能够绑定一个单独的CPU,那么此模型将是最优模型。但是目前CPU的数量无法与客户端的数量达到一个量级,目前甚至差的不是几个量级的事。

总结

综上,我们整理了7中Server的服务器处理结构模型,每个模型都有各自的特点和优势,那么对于多少应付高并发和高CPU利用率的模型,目前多数采用的是模型五(或模型五进程版,如Nginx就是类似模型五进程版的改版)。

至于并发模型并非设计的约复杂越好,也不是线程开辟的越多越好,我们要考虑硬件的利用与和切换成本的开销。模型六设计就极为复杂,线程较多,但以当今的硬件能力无法支撑,反倒导致该模型性能极差。所以对于不同的业务场景也要选择适合的模型构建,并不是一定固定就要使用某个来应用。


###关于作者:

mail: [email protected] github: github.com/aceld 原创书籍gitbook: legacy.gitbook.com/@aceld

创作不易, 共同学习进步, 欢迎关注作者, 回复"zinx"有好礼

作者微信公众号


文章推荐

开源软件作品

(原创开源)Zinx-基于Golang轻量级服务器并发框架-完整版(附教程视频)

(原创开源)Lars-基于C++负载均衡远程调度系统-完整版

精选文章

典藏版-Golang调度器GMP原理与调度全分析

典藏版-Golang三色标记、混合写屏障GC模式图文全分析

最常用的调试 golang 的 bug 以及性能问题的实践方法?

Golang中的Defer必掌握的7知识点

Golang中的局部变量“何时栈?何时堆?”

使用Golang的interface接口设计原则

流?I/O操作?阻塞?epoll?

深入浅出Golang的协程池设计

Go语言构建微服务一站式解决方案


相关 [socket server 并发] 推荐:

Socket Server的N种并发模型汇总

- - 掘金架构
刘丹冰Aceld,微信公众号同名. 本文主要介绍常见的Server的并发模型,这些模型与编程语言本身无关,有的编程语言可能在语法上直接透明了模型本质,所以开发者没必要一定要基于模型去编写,只是需要知道和了解并发模型的构成和特点即可. 那么在了解并发模型之前,我们需要两个必备的前置知识:. 多线程/多进程等并发编程理论.

[转][转]Linux下高并发socket最大连接数所受的各种限制

- - heiyeluren的blog(黑夜路人的开源世界)
来源: http://blog.sae.sina.com.cn/archives/1988. 1、修改用户进程可打开文件数限制. 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄).

有关socket read

- - 五四陈科学院
以下内容由 [五四陈科学院]提供. 实际开发中,网络程序最可能遇到的就是数据没收到、收到错误数据这样诡异的问题.. 很多时候,都是由于对socket read的细节理解的不一致,导致了程序前后的矛盾. 下面详细阐述整个read的过程. read函数是负责从fd中读取内容.. 当读成功时, read返回实际所读的字节数.

Sql Server 高频,高并发访问中的键查找死锁解析 - shanks_gao

- - 博客园_首页
死锁对于DBA或是数据库开发人员而言并不陌生,它的引发多种多样,一般而言,数据库应用的开发者在设计时都会有一定的考量进而尽量避免死锁的产生.但有时因为一些特殊应用场景如高频查询,高并发查询下由于数据库设计的潜在问题,一些不易捕捉的死锁可能出现从而影响业务.这里为大家介绍由于设计问题引起的键查找死锁及相关的解决办法..

Socket的速率控制

- - CSDN博客互联网推荐文章
做一个以精确速率向外输出数据的数据源,要完成这个目标,最基础的是:. 1、找到一种精确的计时器,在精确的时间范围内控制数据源以指定的速度向外发送数据. 2、通过对套接字选项和线程优先级的设置减少网络因素对发送速度造成的影响,从而提高发送精度,保证数据的实际发送量尽可能的达到指定的理论发送量.      针对第一个要求,通过寻找到一种时间精度达到微秒级的精确计数器来保证,在硬件支持的情况下可以通过WindowsAPI获取时钟频率以及震荡次数,通过在事件两端分别调用函数得到震荡次数的差值并结合时钟频率可以计算出精确的时间间隔,通过指定的传输速度和精确的延时可以计算出需要发送的数据量.

ZeroMQ(java)中监控Socket

- - CSDN博客架构设计推荐文章
基本上ZeroMQ(java)中基本的代码都算是过了一遍了吧,不过觉得它在日志这一块貌似基本没有做什么工作,也就是我们通过日志来知道ZeroMQ都发生了什么事情. 而且由于ZeroMQ中将连接的建立和重连接都进行了隔离,用户不需要做什么事情来维护连接,当然这样做的好处是使程序员的编码工作变少了,但是当然也有不好的地方,那就是用户失去了对ZeroMQ整个运行阶段的控制.

java socket参数详解:BackLog

- - 开源软件 - ITeye博客
 java socket参数详解:BackLog. 输入连接指示(对连接的请求)的最大队列长度被设置为 backlog 参数. 如果队列满时收到连接指示,则拒绝该连接. backlog参数必须是大于 0 的正值. 如果传递的值等于或小于 0,则假定为默认值. 经过测试这个队列是按照 FIFO(先进先出)的原则.

HTML5 Web socket和socket.io - wishyouhappy

- - 博客园_首页
   HTML5的新特性,用于双向推送消息(例如网页聊天,手机推送消息等). client利用regular http请求webpage. 请求的webpage 执行javascript脚本,open a connection to server.. 有新的信息时服务器和客户端可以相互发送信息(Real-time traffic from the server to the client .

tcp/ip ,http,socket的关系

- - 行业应用 - ITeye博客
  物理层、数据链路层、网络层、传输层、会话层、表示层和应用层.   通过初步的了解,我知道IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,.   三者从本质上来说没有可比性,.   socket则是对TCP/IP协议的封装和应用(程序员层面上).   也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,.

SQL Server--索引

- - CSDN博客推荐文章
         1,概念:  数据库索引是对数据表中一个或多个列的值进行排序的结构,就像一本书的目录一样,索引提供了在行中快速查询特定行的能力..             2.1优点:  1,大大加快搜索数据的速度,这是引入索引的主要原因..                             2,创建唯一性索引,保证数据库表中每一行数据的唯一性..