【Netty 从成神到升仙系列 大结局】全网一图流死磕解析 Netty 源码

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【Netty 从成神到升仙系列 大结局】全网一图流死磕解析 Netty 源码

全网一图流死磕解析 Netty 源码

通过之前介绍的几篇关于 Netty 的文章,相信大家多少对 Netty 有了一点了解,本篇文章主要从整个 Netty 的调用流程图来做一个汇总

一、Netty 服务端的启动

Netty 服务端的整个启动流程,我做成了一个流程图:

image.png高清的可在公众号 爱敲代码的小黄 回复:Netty,即可获取

这个我们之前讲过,在 【Netty 从成神到升仙系列 一】Netty 服务端的启动源码剖析(一),当时通过源码的角度剖析了下 Netty 的启动流程。

由于当时对于 Netty 整体的把控不太好,有一些细节性的东西选择性的忽略,导致最终整体的连贯性差强人意

后来,学完 Netty 之后,又对服务端的启动做了一些细致化的分析

1. Java NIO 的启动

我们学过 Netty 的都知道,Netty 无非在 Java NIO 的基础上做了一定的优化,使得 I/O 网络的效率大大提高

但从整体来说,Netty 服务端的启动和 Java NIO 的启动基本一模一样,主要集中在以下几方面:

  • 创建一个 selector 对象
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open(); // 创建FD-1
ssc.configureBlocking(false); // 非阻塞模式

建立 selector 与 channel 的联系(注册)

SelectionKey sscKey = ssc.register(selector, SelectionKey.OP_ACCEPT, null);

注册端口号

ssc.bind(new InetSocketAddress(8080));

而我们 Netty 用了好多好多的代码去描述了上述几行代码。

当我看完第一遍 Netty 服务端启动流程,感觉这样写的好处在哪呢,完全没有 GET 到,当我第二遍带着疑惑去看并画清流程图之后,发现 Netty 的源码确实令人耳目一新

具体表现在哪些方面呢,我们可以继续往下看

2. Netty 服务端的启动

我们看到,最核心的当属 Pipline,这里我们也不多介绍了,之前也都讲过。

从整体来看,其实和我们写的业务代码也差不多,但博主感觉比较厉害的一点就是:对于一些我们不需要立即获取结果的处理,全部封装成 Task 交给线程处理,这样会让我们的系统线程的使用率提高并且性能提升。

在轮询处理时,,也会使用 ioRatio 控制 I/O 的比例,同时依靠计数器解决了 Epoll 空轮询的BUG。

我们初始化时,分别有 BOSSWORK 两个线程组,我们的 BOSS 线程组主要用来接受客户端的连接事件,而 WORK 线程组用来处理客户端的读事件。

二、Netty 服务端的读写

通过上述 Netty 服务端的启动,我们向我们的 Selector 上注册了 OP_ACCEPT 事件,当有客户端连接到服务端时,就会触发该事件。

1.注册读事件

通过上述的流程图我们可以看到,通过 Channel 的不同来实现不同的 unsafe.read() 的实现

这里 channel 不同主要指的是:NioServerSocketChannel NioSocketChannel 两种

对于 NioServerSocketChannel 来说,负责接受当前客户端的 连接请求,生成 NioSocketChannel 将当前的连接注册到 Work 的线程组上

childGroup.register(child))并向 Selector 上注册 读事件(接受客户端)

2.读数据

而对于 NioSocketChannel,主要负责处理客户端的 读请求

对于读数据来说,主要通过 allocHandle.lastBytesRead(doReadBytes(byteBuf)) 进行读取

这里说下读取及扩容的逻辑:

  • 服务端默认接受客户端的 byteBuf 是 1024
  • NettybyteBuf 可以根据接受数据的大小进行动态的扩缩容(SIZE_TABLE),规律如下:
  • 扩容:当小于 512 时,一次性增加 64,当大于 512 时,一次性增加 16倍
  • 缩容:当小于 512 时,一次性减少 16,当大于 512 时,一次性减少 一倍

  • 当然,源码中扩缩容的前提不同:缩容需要连续两次都小于,而扩容只需要大于一次就可以
  • byteBuf 扩容也是有范围限制的:默认在64和65536之间

什么时候停止数据的读取,主要由 allocHandle.continueReading() 控制

  • 当前的数据已被读取完毕
  • 当前的数据被读取16次,需要结束
  • 这里规定读取次数的原因:控制次数,防止无限次循环,浪费线程。

3.写数据

上面在读取数据时,会调用 pipeline.fireChannelRead(byteBuf)

这里会执行 Pipline 上的各个 HandlerchannelRead方法,这里我们一般使用 writeAndFlushwrite,我们分开来讲。

首先,对于 write 的方法,主要由 incrementPendingOutboundBytes(long size, boolean invokeLater) 执行,这里会向 ChannelOutboundBuffer

写入信息并判断当前写入的数据是否超越水位线(默认 64 * 1024)。

如果超越了我们的水位线,我们会给与 ChannelOutboundBuffer

一个标记,将 unwritable = 0(false) 修改为 unwritable = 1(true),这样在我们后续发送的过程中,可以根据此标记让应用自己选择是否发送。

这里的 ChannelOutboundBuffer 主要相当于一个容器,write 向里面写,Flush 往内核发。

4.刷数据

这里的刷数据,指的是用户空间的 Buffer 向内核空间的 Scoket 刷数据,类似:


我们的数据会发送到 Socket 缓冲区,由网卡发出。

我们来讲一讲 NettyFlush 流程:

  • 判断当前通道是否关闭(防止当前的客户端已经断开连接)
  • 刷数据
  • case 0:文件数据,通过零拷贝刷,这里之前讲过,参考:【Netty 从成神到升仙系列 四】让我们一起探索 Netty 中的零拷贝
  • case 1:单个数据的写入
  • default:批量数据的写入:由于批量数据写入,必然存在缓冲区满的问题,当缓冲区满的时候,Netty 会注册一个 写事件,当缓冲区有空闲时,触发写事件,交由其他线程处理。
  • 如果写了16次数据还没有写完的话,会新起一个 flush 任务,让其余的 Work 线程执行该任务。这里不需要写事件的原因:当前的缓存区是空闲的,如果注册了写事件,会造成不断的有写事件触发。
  • 这里可能有小伙伴们疑惑,为什么一个写事件就能避免批量数据写入的问题,缓冲区满与空闲代表什么?

当我们的服务端收到客户端的请求时,我们会 writeflush,我们从整个流程看下:

对于服务端来说,我们的 flush 操作会将处理用户态的 buffer 中的数据刷到内核态的 Scoket缓冲区

对于客户端来说,会通过网卡接受服务端的数据并刷新到 Scoket缓冲区 中,通过用户态的 buffer 读取

如果我们当前的服务端发送的数据过大,客户端接受的数据过少,就会导致服务端这边的 Scoket缓冲区 阻塞

  • 87380 :tcp 接收缓冲区的默认值
  • 16384 : tcp 发送缓冲区的默认值
  • 这个时候,我们只能等待 Scoket缓冲区 有空闲时,继续向里面 flush,但这种无疑会把当前的线程阻塞住,违背 NIO 的架构初衷

所以,当批量的数据一次性写入不成功时,,我们会向 Selector 注册一个写事件,当 Scoket缓冲区 有空闲时,会触发该事件,并交由其他的 work 线程去处理,这样极大的发挥了 Netty 高效的网络通讯框架的作用。

三、总结

自此,Netty 的章节基本就结束了~

不得不说,学习 Netty 的过程还是挺痛苦的,有一些网络层面的东西,之前都没有接触过,只能慢慢的去补

还有一些学习中的疑惑,也需要大量的时间去消化,比如:

  • Netty 为什么能成为最高效的网络 I/O 框架?
  • 当你刚学习时,你可能会听说 NIO 使 Netty 变的有效起来
  • NIO 是什么?为什么会出现 NIO?
  • 这个时候你会了解到,主要由于 C10K 的经典问题出现,导致了 NIO 的出现
  • NIO 实际上是 Linux 下 IO多路复用的机制,通过 select、poll、epoll三种方式来实现
  • select、poll、epoll 是什么?
  • 了解到每个实现方式的不同以及慢慢的优化,最终采用的 epoll 作为当前 NIO 的实现
  • 网络是怎么发送的?Socket 的本质又是什么?
  • Linux 网络编程又是如何实现的?

其实,慢慢的思索,好像学习一个知识,会带来一系列的蝴蝶效应,尤其是对下层知识的缺乏,导致自己上层知识的不稳定。

就像我一开始其实是分析的 kafka 的源码,到了通信那一节,看不懂了

于是去看了看 IO,继而又去看 操作系统计算机网络

不过用了这么多时间,还算有成效,至少 Netty 可以懂了一点了

本期的内容就到这里,后续的话,可能会出一篇 select、poll、epoll 的文章,其次,后面应该会重新回到 kafka 的源码解析来




相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
102 2
|
19天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
19天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
19天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
60 12
|
1月前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
19天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
2月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
2月前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
67 3
|
3月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
70 5

推荐镜像

更多