常见并发服务器设计方案

1.循环式/迭代式(iterative)服务器

这种方案是一种单线程的应用程序,它只能使用短连接而不能使用长连接,缺点是无法利用多核CPU,不适合执行时间较长的服务。
在这里插入图片描述

2.并发式(concurrent)服务器

  • 一个连接一个进程/线程
  • 可以执行长连接服务

    以上为父进程,在父进程中要关闭创建连接的套接字。以下为子进程,在子进程中要关闭监听连接的套接字。如果是创建了一个线程则两个线程都不需要关闭套接字,因为线程之间不会复制资源(也就不会复制打开的套接字)。
    在这里插入图片描述

3.预先创建进程/线程

它处理连接的进程/线程都是预先创建好的,因此可以减小创建进程/线程的开销,能够提高响应速速。
在这里插入图片描述
每个进程/线程按以下模式运行
在这里插入图片描述
注意:这里有多个进程/线程都处于循环当中等待连接,当有连接到达时,可能有多个accept都返回,其中只有一个返回成功,其他都返回失败,这叫做惊群现象,因进行正确的处理而不能认为是出错了(如何处理惊群现象在UNP2e第二十七章有详细讲解)。

4.反应式(reactive)服务器(reactor模式)

  • 宏观上并发处理多个请求,实际上是在一个线程中完成,无法充分利用多核CPU。
  • 不适合执行时间比较长的服务,为了让客户感觉是在“并发处理”而不是在“循环处理”(实际上确实是在循环处理),每个请求必须在较短时间内完成。
    在这里插入图片描述
    典型用法是简单的select/poll/epoll使用。

5.Reactor + Worker Thread(过渡方案)

这种方案是每个请求过来就为它分配一个服务线程,这能充分利用多核CPU,也适合长连接,但性能很低。

6.Reactor + Thread Pool(能适应密集计算)

这种模式下,即使客户端的计算任务较大,也不会影响到IO线程,并且能充分利用多核CPU
在这里插入图片描述

7.Multiple Reactors(多Reactor模式,能适应更大的突发IO)

它有两种模式:

  • 一个线程一个Reactors(one loop per thread)
  • 一个进程一个Reactors
    在这里插入图片描述
    一般来说,一个Reactor能够适应一个千兆网口(mainReactor除外),常用的是one loop per thread,这些线程也可以看做线程池,一般情况下线程的个数固定。

8.Multiple Reactors + Thread Pool(适应突发IO和密集计算)

这是linux平台下比较完美和灵活的一种模型,也是muduo网络库很推荐的一种模型。

注意:这里的multiple reactors只能用多线程实现,因为多进程之间无法共享计算线程池,所以实际为 one loop per thread + thread pool。总共有两个线程池(一个IO线程池一个计算线程池)。
在这里插入图片描述

以上均为同步IO模型。

9.同步IO与异步IO讲解

(1). 同步IO的非阻塞模式

在同步IO中可以给文件描述符加上 O_NONBLOCK 属性,在调用read时没有数据也会立即返回(返回值是-1,error设置为EWOULDBLOCK),但它任然是同步模式。但这会带来一个严重的问题,如果是在一个循环中进行读,那么CPU会浪费在不停的去查询是否有数据可读上面,造成CPU的忙等待。

(2). 异步IO执行过程

在这里插入图片描述
当应用程序发起aio_read操作时,会先进入到内核初始化read IO操作,read IO操作会在内核中执行,aio_read立即返回而无需等待read IO操作完成,之后应用程序可以执行其他操作。一旦read IO操作完成,内核会将数据从内核空间拷贝到用户空间的buffer(调用aio_read时指定),并且通过信号或回调通知IO操作完成,应用程序即可通过信号处理函数或回调函数处理数据(信号处理函数或回调函数都是在调用aio_read时指定),它充分利用了硬件的DMA特性(直接存储访问而不需要CPU的干预),使得IO操作和其他操作可以重叠。

(3). 异步IO与IO复用的区别

在IO复用模型中,当(select/poll/epoll)返回时,只是应用程序得到通知内核中有数据可读(在相应的数据结构中记录哪些文件描述符有数据可读),但此时数据还存在于内核缓冲区中,任然需要调用read去把数据读取到用户空间,即此时IO尚未完成。但对于异步IO来说,一旦有数据到达,内核就会主动将数据推到用户空间缓冲区,然后再通知应用程序,此时IO操作已经完成。

10.Proactor异步IO模式

  • 理论上proactor模式比reactor模式效率高些
  • 异步IO能使IO操作和计算重叠,充分利用了DMA特性
  • linux下的异步io(linux下目前还没有比较成熟的异步io):
    glibc aio(aio_),有bug
    kernel native aio(io_
    ),也不完美。目前仅支持O_DIRECT方式来对磁盘读写,跳过系统缓存,如果自己实现系统缓存难度会比较大。
  • boost asio实现的proactor实际上不是真正的异步IO,其底层采用epoll模拟实现异步IO。

11.常见并发服务器方案总结

在这里插入图片描述

12.常见问题解答

(1).Linux能同时启动多少个线程?

对于 32-bit Linux,一个进程的地址空间是 4G,其中用户态能访问 3G 左右,而一个线程的默认栈 (stack) 大小是 8M,心算可知,一个进程大约最多能同时启动 300 个线程左右。

(2).多线程能提高并发度吗?

如果指的是“并发连接数”,不能。

假如单纯采用 thread per connection 的模型,那么并发连接数大约300,这远远低于基于事件的单线程程序所能轻松达到的并发连接数(几千上万,甚至几万)。所谓“基于事件”,指的是用 IO multiplexing event loop 的编程模型,又称 Reactor 模式。

(3).多线程能提高吞吐量吗?

对于计算密集型服务,不能。

如果要在一个8核的机器上压缩100个1G的文本文件,每个core的处理能力为200MB/s,那么“每次起8个进程,一个进程压缩一个文件”与“只启动一个进程(8个线程并发压缩一个文件)”,这两种方式总耗时相当,但是第二种方式能较快的拿到第一个压缩完的文件。

(4).多线程能提高响应时间吗?

可以。参考问题3

(5).多线程程序日志库要求

线程安全,即多个线程可以并发写日志,两个线程的日志消息不会出现交织。
用一个全局的mutex保护IO
每个线程单独写一个日志文件
前者造成全部线程抢占一个锁(串行写入)
后者有可能让业务线程阻塞在写磁盘操作上。(磁盘IO时间比较长)

解决办法:用一个logging线程负责收集日志消息,并写入日志文件,其他业务线程只管往这个“日志线程”发送日志消息(如通过BlockingQueue提供接口),这称为“异步日志”,也是一个经典的生产者消费者模型。

(6).线程池大小的选择

如果池中执行任务时,密集计算所占时间比重为P(0<P<=1),而系统一共有C个CPU,为了让C个CPU跑满而不过载,线程池大小的经验公式T=C/P,即T*P=C(让CPU刚好跑满 )
假设C=8,P=1.0,线程池的任务完全密集计算,只要8个活动线程就能让CPU饱和
假设C=8,P=0.5,线程池的任务有一半是计算,一半是IO,那么T=16,也就是16个“50%繁忙的线程”能让8个CPU忙个不停。
(当P < 0.2时这个公式不再适合)

(7).线程分类

I/O线程(这里特指网络I/O,每个reactor就是一个IO线程)
计算线程
第三方库所用线程,如logging,又比如database

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值