专栏首页DDDsocket&io高性能

socket&io高性能

最近看到篇好文章《IO多路复用》,记得早期学习时,也去探索过select、poll、epoll的区别,但后来也是没有及时记录总结,也忘记了,学习似乎就是在记忆与忘记中徘徊,最后在心中留下的火种,是熄灭还是燎原就看记忆与忘记间的博弈

socket与io一对兄弟,有socket地方必然有io,io数据也大多来源于socket,回顾这两方面的知识点,大致梳理一下

socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议

除了TCP协议(三次握手、四次挥手)知识点外,再就是各阶段与java api对应的方法

三次握手关联到两个方法:服务端的listen()与客户端的connect()

两个方法的共通点:TCP三次握手都不是他们本身完成的,握手都是内核完成的,他们只是通知内核,让内核自动完成三次握手连接

不同点:connect()是阻塞的,listen()是非阻塞的

三次握手的过程细节:

•第一次握手:客户端发送 SYN 报文,并进入 SYN_SENT 状态,等待服务器的确认;•第二次握手:服务器收到 SYN 报文,需要给客户端发送 ACK 确认报文,同时服务器也要向客户端发送一个 SYN 报文,所以也就是向客户端发送 SYN + ACK 报文,此时服务器进入 SYN_RCVD 状态;•第三次握手:客户端收到 SYN + ACK 报文,向服务器发送确认包,客户端进入ESTABLISHED 状态。待服务器收到客户端发送的 ACK 包也会进入ESTABLISHED 状态,完成三次握手

io

IO中常听到的就是同步阻塞IO,同步非阻塞IO,异步非阻塞IO;也就是同步、异步、阻塞、非阻塞四个词组合体,可从名字上看就不大对,既然同步,应该都是阻塞,怎么会有同步非阻塞?不知道哪位先贤的学习总结却流传深远

还有些把non-blocking IO与NIO都混淆了

对于IO模型,最正统的应该来自Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models”

•Blocking I/O•Non-Blocking I/O•I/O Multiplexing•Asynchronous I/O

在理解这四种常见模型前,先简单说下linux的机制,可以更方便理解IO,在《堆外内存》[1]中提到linux的处理IO流程以及Zero-Copy技术,算是IO模型更深入的知识点

应用程序发起的一次IO操作实际包含两个阶段:

•1.IO调用阶段:应用程序进程向内核发起系统调用•2.IO执行阶段:内核执行IO操作并返回

•2.1. 准备数据阶段:内核等待I/O设备准备好数据•2.2. 拷贝数据阶段:将数据从内核缓冲区拷贝到用户空间缓冲区

对于阻塞与非阻塞,讲的是用户进程/线程与内核之间的切换;当内核数据没有准备好时,用户进程就得挂起

对于同步与异步,重点在于执行结果是否一起返回,IO就是指read,send是否及时获取到结果

大致分析一下,同步异步、阻塞非阻塞的两两组合其实是把宏观与微观进行了穿插,从应该程序角度获取结果是同步或异步,而IO内部再细分了阻塞与非阻塞

由上文所述:IO操作分两个阶段 1、等待数据准备好(读到内核缓存) ,2、将数据从内核读到用户空间(进程空间)。一般来说1花费的时间远远大于2。1上阻塞2上也阻塞的是同步阻塞IO;1上非阻塞2阻塞的是同步非阻塞IO,NIO,Reactor就是这种模型;1上非阻塞2上非阻塞是异步非阻塞IO,AIO,Proactor就是这种模型。

同步阻塞IO(Blocking IO)

因为用户态被阻塞,等待内核数据的完成,所以需要同步等待结果

同步非阻塞IO(Non-blocking IO)

用户态与内核不再阻塞了,但需要不停地轮询获取结果,浪费CPU,这方式还不如BIO来得痛快

IO多路复用

Reactor模式,意为事件反应,操作系统的回调/通知可以理解为一个事件,当事件发生时,进程/线程对该事件作出反应。Reactor模式也称作Dispatcher模式,即I/O多路复用统一监听事件,收到事件后分配(Dispatch)给某个进程/线程

对于IO多路复用,里面再有的细节就是一个优化过程,select,poll,epoll

AIO

Proactor模式,Reactor可理解为“来了事件我通知你,你来处理”,而Proactor是“来了事件我处理,处理完了我通知你”。这里“我”是指操作系统,“你”就是用户进程/线程

四种模型对比

对于IO模型的优化进程,一是操作系统的支持,减少系统调用,用户态与内核的切换;二是机制的变换,从命令式到响应性的转变


高性能架构

只温习Socket/IO知识太无趣了,我们要温故知新,升华一下,从架构角度谈一谈

从常规服务处理业务流程讲:request -> process -> response

站在架构师的角度,当然需要特别关注高性能架构的设计。高性能架构设计主要集中在两方面:

1.尽量提升单服务器的性能,将单服务器的性能发挥到极致。2.如果单服务器无法支撑性能,设计服务器集群方案。

除了以上两点,最终系统能否实现高性能,还和具体的实现及编码相关。但架构设计是高性能的基础,如果架构设计没有做到高性能,则后面的具体实现和编码能提升的空间是有限的。形象地说,架构设计决定了系统性能的上限,实现细节决定了系统性能的下限。

单服务器高性能的关键之一就是服务器采取的并发模型,并发模型有如下两个关键设计点:

•服务器如何管理连接•服务器如何处理请求

以上两个设计点最终都和操作系统的 I/O 模型及进程模型相关。

•I/O 模型:阻塞、非阻塞、同步、异步•进程模型:单进程、多进程、多线程

传统模式PPC&TPC

PPC,即Process Per Connection,为每个连接都创建一个进程去处理。此模式实现简单,适合服务器连接不多的场景,如数据库服务器

TPC,即Thread Per Connection,为每个连接都创建一个线程去处理。线程创建消耗少,线程间通信简单

这两种都是传统的并发模式,使用于常量连接的场景,如数据库(常量连接海量请求),企业内部(常量连接常量请求)

至于是进程还是线程,大多与语言特性相关,Java语言由于JVM是一个进程,管理线程方便,故多使用线程,如Netty。C语言进程和线程均可使用,如Nginx使用进程,Memcached使用线程。

不同并发模式的选择,还要考察三个指标,分别是响应时间(RT),并发数(Concurrency),吞吐量(TPS)。三者关系,吞吐量=并发数/平均响应时间。不同类型的系统,对这三个指标的要求不一样。

三高系统,比如秒杀、即时通信,不能使用

三低系统,比如ToB系统,运营类、管理类系统,一般可以使用

高吞吐系统,如果是内存计算为主的,一般可以使用,如果是网络IO为主的,一般不能使用。

Reactor&Proactor

对于传统方式,显示只能适合常量连接常量请求,不能适应互联网场景

如双十一场景下的海量连接海量请求;门户网站的海量连接常量请求;

引入线程池也是一种手段,但也不能根本解决,如常量连接海量请求的中间件场景,线程虽然轻量但也有得消耗资源,终有上限

Reactor,意为事件反应,操作系统的回调/通知可以理解为一个事件,当事件发生时,进程/线程对该事件作出反应。Reactor模式也称作Dispatcher模式,即I/O多路复用统一监听事件,收到事件后分配(Dispatch)给某个进程/线程。

可以看到,I/O多路复用技术是Reactor的核心,本质是将I/O操作给剥离出具体的业务进程/线程,从而能够进行统一管理,使用select/epoll去同步管理I/O连接。

Reactor模式的核心分为Reactor和处理资源池。Reactor负责监听和分配事件,池负责处理事件

如何高性能呢?就得IO多路复用,配合上进程、线程组合,就有:

•单Reactor 单进程 / 线程•单Reactor 多线程•多Reactor 单进程 / 线程(此实现方案相比“单 Reactor单进程”方案,既复杂又没有性能优势,所以很少实际应用)•多Reactor 多进程 / 线程

单Reactor单线程

在这种模式中,Reactor、Acceptor和Handler都运行在一个线程中

单 Reactor 单进程的模式优点就是很简单,没有进程间通信,没有进程竞争,全部都在同一个进程内完成。

但其缺点也是非常明显,具体表现有:

•只有一个进程,无法发挥多核 CPU 的性能;只能采取部署多个系统来利用多核 CPU,但这样会带来运维复杂度,本来只要维护一个系统,用这种方式需要在一台机器上维护多套系统。•Handler 在处理某个连接上的业务时,整个进程无法处理其他连接的事件,很容易导致性能瓶颈

因此,单 Reactor 单进程的方案在实践中应用场景不多,只适用于业务处理非常快速的场景,目前比较著名的开源软件中使用单 Reactor 单进程的是 Redis

在redis中如果value比较大,redis的QPS会下降得很厉害,有时一个大key就可以拖垮

现在redis6.0版本后,已经变成多线程模型,对于大value的删除性能就提高了

单Reactor多线程

在这种模式中,Reactor和Acceptor运行在同一个线程,而Handler只有在读和写阶段与Reactor和Acceptor运行在同一个线程,读写之间对数据的处理会被Reactor分发到线程池中

单Reator多线程方案能够充分利用多核多 CPU的处理能力,但同时也存在下面的问题:

•多线程数据共享和访问比较复杂。例如,子线程完成业务处理后,要把结果传递给主线程的 Reactor 进行发送,这里涉及共享数据的互斥和保护机制。以 Java 的 NIO 为例,Selector 是线程安全的,但是通过 Selector.selectKeys() 返回的键的集合是非线程安全的,对 selected keys 的处理必须单线程处理或者采取同步措施进行保护。•Reactor 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈

多Reactor多线程

为了解决单 Reactor 多线程的问题,最直观的方法就是将单Reactor改为多Reactor

目前著名的开源系统 Nginx 采用的是多Reactor多进程,采用多Reactor多线程的实现有 Memcache 和 Netty

使用5W根因分析法(它又叫 5Why 分析法或者丰田五问法,具体是重复问五次“为什么”)检查一下对这块知识的学习程度

问题 1:为什么 Netty 网络处理性能高?

答:因为 Netty 采用了 Reactor 模式

问题 2:为什么用了 Reactor 模式性能就高?

答:因为 Reactor 模式是基于 IO 多路复用的事件驱动模式。

问题 3:为什么 IO 多路复用性能高?

答:因为 IO 多路复用既不会像阻塞 IO 那样没有数据的时候挂起工作线程,也不需要像非阻塞 IO 那样轮询判断是否有数据。

问题 4:为什么 IO 多路复用既不需要挂起工作线程,也不需要轮询?

答:因为 IO 多路复用可以在一个监控线程里面监控很多的连接,没有 IO 操作的时候只要挂起监控线程;只要其中有连接可以进行 IO 操作的时候,操作系统就会唤起监控线程进行处理。

问题 5:那还是会挂起监控线程啊,为什么这样做就性能高呢?

答:首先,如果采取阻塞工作线程的方式,对于 Web 这样的系统,并发的连接可能几万十几万,如果每个连接开一个线程的话,系统性能支撑不了;而如果用线程池的话,因为线程被阻塞的时候是不能用来处理其他连接,会出现等待线程的问题。其次,线上单个系统的工作线程数配置可以达到几百上千,这样数量的线程频繁切换会有性能问题,而单个监控线程切换的性能影响可以忽略不计。第三,工作线程没有 IO 操作的时候可以做其他事情,能够大大提升系统的整体性能。

References

[1] 《堆外内存》: http://www.zhuxingsheng.com/blog/common-sense-four-heaps-of-external-memory.html [2] 五种IO模型透彻分析: https://www.cnblogs.com/f-ck-need-u/p/7624733.html [3] IO模型: https://www.cnblogs.com/sheng-jie/p/how-much-you-know-about-io-models.html [4] Scalable IO in Java: http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf [5] 《从零开始学架构》: http://www.zhuxingsheng.com/blog

本文分享自微信公众号 - 码农戏码(coder-game),作者:朱先生

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 提高 Linux 上 socket 性能

    在开发 socket 应用程序时,首要任务通常是确保可靠性并满足一些特定的需求。利用本文中给出的 4 个提示,您就可以从头开始为实现最佳性能来设计并开发 soc...

    一见
  • 高性能IO模型浅析

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型。 (2)同步非阻塞IO(Non...

    李海彬
  • 高性能IO模型浅析

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型。 (2)同步非阻塞IO(Non...

    李海彬
  • 高性能IO编程设计

    首先,在讲述高性能IO编程设计的时候,我们先思考一下何为“高性能”呢,如果自己来设计一个web体系服务,选择BIO还是NIO的编程方式呢?其次,我们可以了解下构...

    keithl
  • JAVA高性能I/O设计模式

    同步阻塞模式。在JDK1.4以前,使用Java建立网络连接时,只能采用BIO方式,在服务器端启动一个ServerSocket,然后使用accept等待客户端请求...

    cloudskyme
  • 彻底搞懂高性能I/O之道

    本文介绍操作系统I/O工作原理,Java I/O设计,基本使用,开源项目中实现高性能I/O常见方法和实现,彻底搞懂高性能I/O之道

    用户1260737
  • 搞懂I/O多路复用及其技术

    高性能是每个程序员的追求,无论写一行代码还是做一个系统,都希望能够达到高性能的效果。高性能架构设计主要集中在两方面:

    java乐园
  • 深度解析Redis线程模型设计原理

    所以虽然FEH是单线程运行,但通过I/O多路复用监听多个socket,不仅实现高性能的网络通信模型,又能和 Redis 服务器中其它同样单线程运行的模块交互,保...

    JavaEdge
  • I/O多路复用,从来没遇到过这么明白的文章

    很多对技术有追求的读者朋友,做到一定阶段后都希望技术有所精进。有些读者朋友可能会研究一些中间件的技术架构和实现原理。比如,Nginx为什么能同时支撑数万乃至数十...

    用户7927337
  • 《【面试突击】— Redis篇》--Redis的线程模型了解吗?为啥单线程效率还这么高?

    《【面试突击】— Redis篇》-- Redis的线程模型了解吗?为啥单线程效率还这么高?

    编程大道
  • Java核心(五)深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别;BIO、NIO、AIO 的区别;理解和实现 NIO 操作 Socket 时的多路复用;同时掌握 IO ...

    Java中文社群-磊哥
  • Java核心(五)深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别;BIO、NIO、AIO 的区别;理解和实现 NIO 操作 Socket 时的多路复用;同时掌握 IO ...

    Java中文社群-磊哥
  • Redis 新特性篇:多线程模型解读

    Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以备受关注。

    码哥字节
  • 高性能服务器程序框架

    http://blog.csdn.net/zs634134578/article/details/19806429

    bear_fish
  • Redis 新特性篇:多线程模型解读

    Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以备受关注。

    乔戈里
  • Web APP编程模型和IO策略

    现代大型高性能网站诸如淘宝,京东,微博,FB,知乎等等,网站架构涉及很多知识。像业务分层,软件分割模块化,分布式部署,集群服务器,负载均衡等技术可以帮助架构师将...

    小小科
  • 一文读懂Redis中的多路复用模型

    首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返...

    用户2781897
  • 图解 | epoll怎么实现的

    epoll 可以说是编写高性能服务端程序必不可少的技术,在介绍 epoll 之前,我们先来了解一下 多路复用I/O 吧。

    用户7686797
  • Thrift 服务模型和序列化机制深入学习

    http://www.liuqianfei.com/article/065b0f1ee59a4cf0b94a84c4e33af127

    bear_fish

扫码关注云+社区

领取腾讯云代金券