【面试突击】深挖网络 IO 面试实战

2024-01-22 12:28

本文主要是介绍【面试突击】深挖网络 IO 面试实战,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🌈🌈🌈🌈🌈🌈🌈🌈
欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

🍁🍁🍁🍁🍁🍁🍁🍁

深挖网络 IO 面试实战

学前须知:

这个模块对网络 IO 这块进行深挖,深入理解了网络 IO 之后,可以跟面试官聊的有来有回,通过深入讨论,你可以展示你对网络 I/O 了解的很深入,以及你如何将这些知识应用到实际的服务器架构和性能优化中,那面试的结果一定是非常不错的

(切记不要对每一块的内容都浅尝辄止,没有技术深度是无法让面试官刮目相看的)

这个模块对网络 IO 这块进行深挖,深入理解了网络 IO 之后,可以跟面试官聊的有来有回,那面试的结果一定是非常不错的

如果每次面试官问一个问题,你都是回答了解一下,那样只会显出你对这块内容的理解太表层!

这个模块对网络 IO 这块进行深挖,深入理解了网络 IO 之后,可以跟面试官聊的有来有回,那面试的结果一定是非常不错的

如果每次面试官问一个问题,你都是回答了解一下,那样只会显出你对这块内容的理解太表层!

只要问到网络 IO 方面,那么一定会去问 Netty 的内容,因为几乎所有的网络通信,只要是用 Java 做的,都是使用 Netty 框架来进行网络通信的,所以 Netty 的东西一定要了解,如果不了解 Netty ,就像你学习 Java 不了解 SpringBoot 一样

Netty 架构原理图

问到 Netty 了,那么 NIO、BIO、AIO 肯定是要了解的,由于面试突击里之前已经写过这块的内容了,这里就不重复说了

下边说一下 Netty 的架构原理图,从整体架构学习:

Netty 处理流程:

1、BossGroup 和 WorkerGroup 都是线程组,BossGroup 负责接收客户端发送来的连接请求,NioEventLoop 是真正工作的线程,用来响应客户端的 accept 事件

2、当接收到连接建立 Accept 事件,获取到对应的 SocketChannel,封装成 NIOSocketChannel,并注册到 Worker 线程池中的某个 NioEventLoop 线程的 selector 中

3、当 Worker 线程监听到 selector 中发生自己感兴趣的事件后,就由 handler 进行处理
在这里插入图片描述

Netty 为什么这么快?

那么 Netty 作为高性能的网络 IO 框架,一定要了解 Netty 在哪些方面保证了高性能:

  1. 传输:用什么样的通道将数据发送给对方,BIO、NIO 或者 AIO,IO 模型在很大程度上决定了框架的性能。IO模型的选择

    • Netty 使用 NIO 进行网络传输,可以提供非阻塞的 IO 操作,极大提升了性能
  2. 协议:采用什么样的通信协议,HTTP 或者内部私有协议。协议的选择不同,性能模型也不同。相比于公有协议,内部私有协议的性能通常可以被设计的更优。协议的选择

    • Netty 支持丰富的网络协议,如 TCP、UDP、HTTP、HTTP/2、WebSocket 等,既保证了灵活性,又可以实现高性能
    • 并且 Netty 可以定制私有协议,避免传输不必要的数据,进一步提升性能
  3. 线程模型:数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,Reactor 线程模型的不同,对性能的影响也非常大。线程模型的选择

    • Netty 使用主从 Reactor 多线程模型,进一步提升性能
  4. 零拷贝:Netty 中使用了零拷贝,来提升数据传输速度

Netty 中的零拷贝了解吗?

Netty 通过零拷贝技术减少数据复制次数,提升性能!

Netty 的 零拷贝 主要在以下三个方面:

  • Netty 的接收和发送使用堆外内存(直接内存)进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。

  • Netty 提供 CompositeByteBuf 组合缓冲区类,可以将多个 ByteBuf 合并为一个逻辑上的 ByteBufer,避免了各个 ByteBufer 之间的拷贝,将几个小 buffer 合并成一个大buffer的繁琐操作。

  • Netty 的文件传输使用了 FileChannel 的 transferTo 方法,该方法底层使用了 sendfile 函数实现了 cpu 零拷贝。

    sendfile 函数通过网络发送数据的流程为:

    (3次拷贝、2次上下文切换,这里上下文切换是在用户空间发起write操作,此时用户态切换为内核态,write调用完毕后又会从内核态切换回用户态)

    在这里插入图片描述

    下边画一个直观一些的图片,从 应用程序的角度 来看 Netty 的零拷贝:

    在这里插入图片描述

但是不能只了解零拷贝是怎样的,因为技术是过渡的,还要知道不使用零拷贝时,传统 IO 是怎么传输数据的:

  • 传统的 IO 操作会有 4 次拷贝,4 次用户态和内核态之间的切换,因此性能是比较低的
  • 这里说一下是 哪 4 次内核态的切换
    • 首先,应用程序去磁盘读取数据进行发送时,此时会从用户态切换到内核态,通过 DMA 拷贝将数据放到文件读取缓冲区中
    • 再从内核态切换到用户态,将文件读取缓冲区的数据通过 CPU 拷贝读取到应用进程缓冲区中
    • 再从用户态切换到内核态,将数据从应用进程缓冲区通过 CPU 拷贝放到 Socket 发送缓冲区中,之后的数据会从 Socket 发送缓冲区中通过 DMA 拷贝发送到网络设备缓冲区中
    • 操作完成之后,再从内核态切换到用户态

在这里插入图片描述

直接内存了解吗?

直接内存(也称为堆外内存)并不是虚拟机运行时数据区的一部分,直接内存的大小受限于系统的内存

在 JDK1.4 引入了 NIO 类,在 NIO 中可以通过使用 native 函数库直接分配堆外内存,然后通过存储在堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作

使用直接内存,可以避免了 Java 堆和 Native 堆中来回复制数据

在上边提到了 Netty 的零拷贝,其中有一种就是使用了 直接内存来实现零拷贝 的,直接内存的特点就是快,接下来看看为什么使用直接内存更快呢?

直接内存使用场景:

  • 有很大的数据需要存储,且数据生命周期长
  • 频繁的 IO 操作,如网络并发场景

直接内存与堆内存比较:

  • 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
  • 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显

直接内存相比于堆内存,避免了数据的二次拷贝。

  • 我们先来分析不使用直接内存的情况,我们在网络发送数据需要将数据先写入 Socket 的缓冲区内,那么如果数据存储在 JVM 的堆内存中的话,会先将堆内存中的数据复制一份到直接内存中,再将直接内存中的数据写入到 Socket 缓冲区中,之后进行数据的发送

    • 为什么不能直接将 JVM 堆内存中的数据写入 Socket 缓冲区中呢?

      在 JVM 堆内存中有 GC 机制,GC 后可能会导致堆内存中数据位置发生变化,那么如果直接将 JVM 堆内存中的数据写入 Socket 缓冲区中,如果写入过程中发生 GC,导致我们需要写入的数据位置发生变化,就会将错误的数据写入 Socket 缓冲区

  • 那么如果使用直接内存的时候,我们将数据直接存放在直接内存中,在堆内存中只存放了对直接内存中数据的引用,这样在发送数据时,直接将数据从直接内存取出,放入 Socket 缓冲区中即可,减少了一次堆内存到直接内存的拷贝

在这里插入图片描述

直接内存与非直接内存性能比较:

public class ByteBufferCompare {public static void main(String[] args) {//allocateCompare(); //分配比较operateCompare(); //读写比较}/*** 直接内存 和 堆内存的 分配空间比较* 结论: 在数据量提升时,直接内存相比非直接内的申请,有很严重的性能问题*/public static void allocateCompare() {int time = 1000 * 10000; //操作次数,1千万long st = System.currentTimeMillis();for (int i = 0; i < time; i++) {//ByteBuffer.allocate(int capacity) 分配一个新的字节缓冲区。ByteBuffer buffer = ByteBuffer.allocate(2); //非直接内存分配申请}long et = System.currentTimeMillis();System.out.println("在进行" + time + "次分配操作时,堆内存 分配耗时:" +(et - st) + "ms");long st_heap = System.currentTimeMillis();for (int i = 0; i < time; i++) {//ByteBuffer.allocateDirect(int capacity) 分配新的直接字节缓冲区。ByteBuffer buffer = ByteBuffer.allocateDirect(2); //直接内存分配申请}long et_direct = System.currentTimeMillis();System.out.println("在进行" + time + "次分配操作时,直接内存 分配耗时:" +(et_direct - st_heap) + "ms");}/*** 直接内存 和 堆内存的 读写性能比较* 结论:直接内存在直接的IO 操作上,在频繁的读写时 会有显著的性能提升*/public static void operateCompare() {int time = 10 * 10000 * 10000; //操作次数,10亿ByteBuffer buffer = ByteBuffer.allocate(2 * time);long st = System.currentTimeMillis();for (int i = 0; i < time; i++) {// putChar(char value) 用来写入 char 值的相对 put 方法buffer.putChar('a');}buffer.flip();for (int i = 0; i < time; i++) {buffer.getChar();}long et = System.currentTimeMillis();System.out.println("在进行" + time + "次读写操作时,非直接内存读写耗时:" +(et - st) + "ms");ByteBuffer buffer_d = ByteBuffer.allocateDirect(2 * time);long st_direct = System.currentTimeMillis();for (int i = 0; i < time; i++) {// putChar(char value) 用来写入 char 值的相对 put 方法buffer_d.putChar('a');}buffer_d.flip();for (int i = 0; i < time; i++) {buffer_d.getChar();}long et_direct = System.currentTimeMillis();System.out.println("在进行" + time + "次读写操作时,直接内存读写耗时:" +(et_direct - st_direct) + "ms");}
}

这篇关于【面试突击】深挖网络 IO 面试实战的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/632967

相关文章

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor