CloudNativeEra
  • Introduction
  • 名词解释
  • Computer Science
    • Computer Organization
      • CPU
      • 二进制、电路、加法器、乘法器
      • 编译、链接、装载
      • 存储器
      • IO
    • Operating System
      • 操作系统基础知识
      • 系统初始化
      • 进程管理
      • Everything about Memory
      • 文件系统
      • 并行编程
      • Linux
        • CPU
        • IO 多路复用
        • DMA IO and Linux Zero Copy
    • Computer Network
      • 网络相关命令
      • 评估系统的网络性能
      • 网络抓包
      • Linux 最多支撑的 TCP 连接
      • 网络虚拟化
      • DHCP 工作原理
    • Data Structure and Algorithm
      • 题目列表
      • Summarize
        • 方法总结
        • 二分思想
        • 树的演化
        • 算法思想总结
      • Data Structure
        • Data Struct - Array
        • Tree
        • Heap
        • Hash
        • 字符串
      • Algorithm
        • Sorting Algorithm
        • 查找
        • 贪心算法
        • 动态规划
        • 位运算
      • Practice Topics
        • Data Struct in SDK
        • Topic - Tree
        • Topic - Graph
        • Topic - 滑动窗口
        • 剑指 Offer 题解
    • 并发编程
      • 并发模式
      • 并发模型
  • 系统设计
    • 软件设计
      • 软件架构
      • 编程范式
      • 系统设计题
      • 设计原则
      • 计算机程序的构造和解释 SICP
    • 领域驱动设计
      • 应用:在线请假考勤管理
      • 应用: library
    • 微服务与云原生
      • Designing and deploying microservices
      • 容器技术
      • Docker
      • Etcd
      • Kubernetes
        • Kubernetes - Mapping External Services
      • Istio
      • 监控
    • 分布式系统
      • 分布式理论
      • 分布式事务
    • 后端存储设计
      • 缓存设计
      • 数据库架构设计
    • CI/CD
    • 设计最佳实践
    • 测试
    • 安全
    • 综合
      • 开发实践
      • 分布式锁
      • 分布式计数服务
      • 弹幕系统设计
      • 消息队列设计
      • 分布式ID生成算法
      • 限流设计
      • 网关设计
      • 通用的幂等设计
      • 分布式任务调度
        • Timer
        • ScheduledExecutorService
        • Spring Task
        • Quartz
      • 交易系统
      • 权限设计
  • 编程语言
    • 编程语言
    • C & C++
    • Java
      • JVM
        • JVM Bytecode
      • Java 核心技术
      • Java 8 新特性
      • Java 集合框架
      • Java NIO
      • 并发编程
        • 线程生命周期与线程中断
        • 三个线程交替打印
        • 两个线程交替打印奇偶
        • 优雅终止线程
        • 等待通知机制
        • 万能钥匙:管程
        • 限流器
        • 无锁方案 CAS
    • Java 源码阅读
      • Unsafe
      • 异步计算 Future
      • Java Queue
      • CoalescingRingBuffer 分析
      • Java Collections
        • PriorityQueue 分析
        • HashMap 分析
        • TreeMap
    • Golang
    • Python
  • 框架/组件/类库
    • Guava
      • Guava Cache
      • Guava EventBus
    • RxJava
    • Apache MINA
    • Netty
      • 网络 IO 模型
      • Netty 生产问题
    • Apache Tomcat
    • MyBatis
    • 限流框架
    • Spring Framework
      • Spring Core
      • Spring 事务管理
    • Spring Boot
    • Spring Cloud
      • Feign & OpenFeign
      • Ribbon
      • Eurake
      • Spring Cloud Config
    • FixJ
    • Metrics
    • Vert.x
  • 中间件
    • Redis
      • Redis 基础
        • Redis 数据结构设计与实现
        • Redis 高性能网络模型
      • Redis checklist
      • 应用案例 - Redis 数据结构
      • 应用案例 - Redis 缓存应用
      • 应用案例 - Redis 集群
      • Redis 客户端
      • Redis 生产案例
        • [译] 在 Redis 中存储数亿个简单键值对
    • MySQL
      • MySQL 基础
      • MySQL Index
      • MySQL Transaction
      • MySQL 优化
      • MySQL 内核
      • MySQL Command
      • MySQL Checklist
      • MySQL Analysis Tool
      • 实现 MySQL
    • State Machine
    • 数据库连接池
    • MQ
      • 高性能内存队列 Disruptor
      • Kafka
      • Pulsar
      • RocketMQ
        • Broker 的设计与实现
      • NSQ
  • 实际案例
    • 线上 Case
      • Request Aborted
      • MySQL - Specified key was too long
      • Java 应用 CPU 100% 排查优化
      • 频繁 GC 导致的 Java 服务不响应
      • 导出优化
  • 大数据
    • 流计算
    • Flink
  • 其他
    • 工具
    • 读书
      • 设计数据密集型应用
      • 实现领域驱动设计
      • 精通比特币
      • 提问的智慧
    • 论文
    • 工程博客
    • 阅读源码
    • 面试
      • 如何在最短的时间里对对方有个全面的了解
    • 分享
    • 软技能
    • Todo
  • Blog
    • #算法
      • 查找
      • 位运算
      • 树
    • #架构
      • 1- 通信
    • Design & Dev & Opt
      • High Performance Data structure Design
  • Tiny Project
    • A Simple WeChat-like Instant Messaging Platform
由 GitBook 提供支持
在本页
  • 基础的 IO 模型
  • 高性能网络 IO 模型

这有帮助吗?

  1. 框架/组件/类库
  2. Netty

网络 IO 模型

上一页Netty下一页Netty 生产问题

最后更新于4年前

这有帮助吗?

IO 是输入输出,对于计算机来讲,IO 是基本组成,通过 IO 来和外界进行交互,比如通过网卡收发数据,使用磁盘持久化数据,使用显示器观看视频,使用键盘输入信息,使用打印机打印东西,使用麦克风讲话等,都属于 IO 的范畴;在计算机中,CPU 是指挥者,它是一个高速运算的部件,和 IO 设备之间通过 IO 接口进行连接,CPU 向 IO 接口下达指令来控制 IO 外设,以磁盘为例,CPU 操作磁盘读写数据,在 Linux 系统中的大致做法是数据从磁盘加载到内核的高速缓冲区,然后内核高速缓冲区拷贝到用户空间,用户空间处理完数据后再拷贝回内核缓冲区,然后有 OS 将脏页刷回磁盘。

而对于网络,网络的 IO 过程还是比较复杂的,网络处理程序处理网络请求方式的不同可以分为几种 IO 模型,其中 IO 模型需要借助 OS 的能力。

基础的 IO 模型

  • 阻塞 IO

阻塞 IO 的工作模式是应用程序线程需要从网络中读取数据,会发起 read 系统调用,进入内核态,试图从内核缓冲区(有可能是 Socket 缓冲区)中读取数据,如果缓冲区空或者数据没有 Ready(有可能还没有构成一个完整的数据包),这时应用程序线程就会进入等待状态,直到数据准备好后,由内核唤醒应用程序线程,应用程序线程会进入就绪队列竞争 CPU,获得 CPU 使用权之后,应用程序线程工作在内核态,然后 Copy 内核缓冲区的数据到用户空间的内存中,然后应用程序线程切换回用户态,继续执行后续的指令。

可见,整个读取数据的过程,线程一直被占用,不能去处理其他的事情,只有等待、等待...,可见阻塞 IO 模型只适用于简单、并发低等的场景中,高性能高并发的场景是无法应对的,因为高并发的场景需要处理大量的连接,使用阻塞 IO 就需要大量的线程去处理,不仅大量线程会占用大量资源,而且线程上下文切换也会非常频繁,最终导致 CPU 利用率下降,整体资源的利用率也下降。

阻塞 IO 在编码时,最常用的两种模式是:

  1. 利用一个线程处理所有的事情,用户连接处理和连接的读写处理

  2. 利用一个线程来接收连接,和一个线程池来处理连接的读写,一般这种是比较常用的

用 Java 程序来理解一下阻塞 IO

public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        
        while (true) {
            System.out.println("Waiting...");
            // accept 是一个阻塞方法
            Socket clientSocket = serverSocket.accept();
            System.out.println("Accept a connection.");
            
            handler(clientSocket);

            // new Thread 是使用另外一个线程来处理连接的读写
            /*new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();*/
        }
    }

    private static void handler(Socket clientSocket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("Start to read");
        // 接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        int read = clientSocket.getInputStream().read(bytes);
        System.out.println("Read completed");
        if (read != -1) {
            System.out.println("Data sent by client:" + new String(bytes, 0, read));
        }
        
        clientSocket.getOutputStream().write("HelloClient".getBytes());
        clientSocket.getOutputStream().flush();
    }
}
  • 非阻塞 IO

和阻塞 IO 相比,非阻塞 IO 在数据准备阶段,不需要一直等待,而是不断的去轮询数据的可用状态,但是在数据准备好之后(所谓的数据准备好可以理解为网络中的数据帧被网卡接收到后,经过网络协议栈的处理形成数据包放入 Socket 缓冲区或者内核缓冲区,等待被处理),由应用程序线程将数据从内核缓冲区中取走,放入自己的内存缓冲区中,Copy 完成后,线程切换回用户态,继续执行后续的指令。

非阻塞 IO 虽然不用一直阻塞,但是需要不断的去轮询数据状态,CPU 大部分时间可能在空转,一定程度上也降低了 CPU 的利用率,在数据 Copy 阶段同样是阻塞的。

想象一下,如果我们由成千上万的 Socket 要处理,我们要对每个 Socket 都去轮询一遍,还是很耗资源的,可能 CPU 啥事也做不了了,也有可能 Socket 中的数据得不到及时处理,造成延迟,还会白白的占用系统资源得不到释放。所以说这种方式还是无法处理大量连接的情况。

如果连接数太多的话,会有大量的无效遍历,假如有10000个连接,其中只有1000个连接有写数据,但是由于其他9000个连接并没有断开,我们还是要每次轮询遍历一万次,其中有十分之九的遍历都是无效的,这显然不是一个让人很满意的状态。

用 Java 程序来理解一下非阻塞 IO:

public class NioServer {

    // Save client channels
    static List<SocketChannel> channelList = new ArrayList<>();

    public static void main(String[] args) throws IOException, InterruptedException {
        // Create NIO ServerSocketChannel, same as BIO serverSocket
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        
        // 设置 ServerSocketChannel 为非阻塞, 非常关键
        serverSocket.configureBlocking(false);
        System.out.println("Server started.");

        while (true) {
            // 非阻塞模式 accept 方法不会阻塞,否则会阻塞
            // NIO 的非阻塞是由操作系统内部实现的,底层调用了 linux 内核的 accept 函数
            SocketChannel socketChannel = serverSocket.accept();
            
            // 如果有客户端进行连接
            if (socketChannel != null) { 
                System.out.println("Connection established.");
                // 设置 SocketChannel 为非阻塞
                socketChannel.configureBlocking(false);
                channelList.add(socketChannel);
            }
            
            // 遍历连接进行数据读取, 这里就是非阻塞 IO 的轮询遍历,一直检查是否有数据准备好
            Iterator<SocketChannel> iterator = channelList.iterator();
            while (iterator.hasNext()) {
                SocketChannel sc = iterator.next();
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                
                // 非阻塞模式 read 方法不会阻塞,否则会阻塞
                int len = sc.read(byteBuffer);
                // 如果有数据,把数据打印出来
                if (len > 0) {
                    System.out.println("Received:" + new String(byteBuffer.array()));
                } 
                // 如果客户端断开,把 socket 从集合中去掉
                else if (len == -1) { 
                    iterator.remove();
                    System.out.println("Client connection closed.");
                }
            }
        }
    }
}
  • IO 复用

用 Java 程序来实现 IO 多路复用:

  • 信号驱动 IO

这种 IO 方式在实际应用中非常少见。

  • 异步 IO

高性能网络 IO 模型

我们需要更加高性能的 IO 模型,可能意味着更加复杂;比如经常听说的 Reactor 模型、Proactor 模型。

Reactor 模型

Reactor 模式由 Reactor 反应器线程、Handlers处理器两大角色组成:

  1. Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器。

  2. Handlers处理器的职责:非阻塞的执行业务处理逻辑。

C 代码可以参考:

https://github.com/eliben/code-for-blog/blob/master/2017/async-socket-server/epoll-server.c
阻塞 IO
非阻塞 IO