0
点赞
收藏
分享

微信扫一扫

价值百万的netty源码笔记,赶紧收藏

janedaring 2022-04-22 阅读 94

Netty是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端,有了Netty,你可以实现自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等,如果你想知道Nginx是怎么写出来的,如果你想知道Tomcat和Jetty是如何实现的,如果你也想实现一个简单的Redis服务器,那都应该好好理解一下Netty,它们高性能的原理都是类似的。

首先我们要知道自己为什么要看源码?

1、提升技术功底:

学习源码里的优秀设计思想,比如一些疑难问题的解决思路,还有一些优秀的设计模式,整体提升自己的技术功底

2、深度掌握技术框架:

源码看多了,对于一个新技术或框架的掌握速度会有大幅提升,看下框架demo大致就能知道底层的实现,技术框架更新再快也不怕

3、快速定位线上问题:

遇到线上问题,特别是框架源码里的问题(比如bug),能够快速定位,这就是相比其他没看过源码的人的优势

4、对面试大有裨益:

面试一线互联网公司对于框架技术一般都会问到源码级别的实现

5、知其然知其所以然:

对技术有追求的人必做之事,使用了一个好的框架,很想知道底层是如何实现的

6、拥抱开源社区:

参与到开源项目的研发,结识更多大牛,积累更多优质人脉

看源码方法

1、先使用:

先看官方文档快速掌握框架的基本使用。

2、抓主线:

找一个demo入手,顺藤摸瓜快速静态看一遍框架的主线源码(抓大放小),画出源码主流程图,切勿一开始就陷入源码的细枝末节,否则会把自己绕晕。

3、画图做笔记:

总结框架的一些核心功能点,从这些功能点入手深入到源码的细节,边看源码边画源码走向图,并对关键源码的理解做笔记,把源码里的闪光点都记录下来,后续借鉴到工作项目中,理解能力强的可以直接看静态源码,也可以边看源码边debug源码执行过程,观察一些关键变量的值。

4、整合总结:

所有功能点的源码都分析完后,回到主流程图再梳理一遍,争取把自己画的所有图都在脑袋里做一个整合。

Netty线程模型图

Netty高并发高性能架构设计精髓

无锁串行化设计思想

在大多数场景下,并行多线程处理可以提升系统的并发性能。但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最 终会导致性能的下降。为了尽可能的避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间 不进行线程切换,这样就避免了多线程竞争和同步锁。

NIO的多路复用就是一种无锁串行化的设计思想(理解下Redis和Netty的线程模型) 为了尽可能提升性能,Netty采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降。表面上看,串行化 设计似乎CPU利用率不高,并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。 

Netty的NioEventLoop读取到消息之后,直接调用ChannelPipeline的fireChannelRead(Object msg),只要用户不主动切换线程,一直 会由NioEventLoop调用到用户的Handler,期间不进行线程切换,这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。

直接内存 

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也 会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间 对应的内存也叫作直接内存,它们对应的都是机器的物理内存。

从程序运行结果看出直接内存申请较慢,但访问效率高。在java虚拟机实现上,本地IO一般会直接操作直接内存(直接内存=>系统调用 =>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)。

直接内存分配源码分析

使用直接内存的优缺点:

优点:不占用堆内存空间,减少了发生GC的可能 java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内 存=>直接内存=>系统调用=>硬盘/网卡)

缺点:初始分配较慢 没有JVM直接帮助管理内存,容易发生内存溢出。为了避免一直没有FULL GC,最终导致直接内存把物理内存耗完。我们可以 指定直接内存的最大值,通过-XX:MaxDirectMemorySize来指定,当达到阈值的时候,调用system.gc来进行一次FULL GC,间 接把那些没有被使用的直接内存回收掉。

Netty的接收和发送ByteBuf采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的JVM堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才能写入Socket 中。JVM堆内存的数据是不能直接写入Socket中的。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。可以看下netty的读写源码,比如read源码NioByteUnsafe.read()

ByteBuf内存池设计

随着JVM虚拟机和JIT即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区Buffer(相当于一个内存块),情况 却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty提供了基于ByteBuf内存池的缓冲 区重用机制。需要的时候直接从池子里获取ByteBuf使用即可,使用完毕之后就重新放回到池子里去。下面我们一起看下Netty ByteBuf的 实现:

可以看下netty的读写源码里面用到的ByteBuf内存池,比如read源码NioByteUnsafe.read()

继续看newDirectBuffer方法,我们发现它是一个抽象方法,由AbstractByteBufAllocator的子类负责具体实现,代码如下:

代码跳转到PooledByteBufAllocator的newDirectBuffer方法,从Cache中获取内存区域PoolArena,调用它的allocate方法进行内存分 配:

PoolArena的allocate方法如下:

我们重点分析newByteBuf的实现,它同样是个抽象方法,由子类DirectArena和HeapArena来实现不同类型的缓冲区分配

我们这里使用的是直接内存,因此重点分析DirectArena的实现

最终执行了PooledUnsafeDirectByteBuf的newInstance方法,代码如下:

通过RECYCLER的get方法循环使用ByteBuf对象,如果是非内存池实现,则直接创建一个新的ByteBuf对象。

灵活的TCP参数配置能力

合理设置TCP参数在某些场景下对于性能的提升可以起到显著的效果,例如接收缓冲区SO_RCVBUF和发送缓冲区SO_SNDBUF。如果设置 不当,对性能的影响是非常大的。通常建议值为128K或者256K。 

Netty在启动辅助类ChannelOption中可以灵活的配置TCP参数,满足不同的用户场景。

ByteBuf扩容机制 

如果我们需要了解ByteBuf的扩容,我们需要先了解ByteBuf中定义的几个成员变量,再从源码的角度来分析扩容。

2. 采用步进4MB的方式完成扩容

3. 采用64为基数,做倍增的方式完成扩容

总结:

Netty的ByteBuf需要动态扩容来满足需要,扩容过程:默认门限阈值为4MB(这个阈值是一个经验值,不同场景,可能取 值不同),当需要的容量等于门限阈值,使用阈值作为新的缓存区容量 目标容量,如果大于阈值,采用每次步进4MB的方式进行 内存扩张((需要扩容值/4MB)*4MB),扩张后需要和最大内存(maxCapacity)进行比较,大于maxCapacity的话就用 maxCapacity,否则使用扩容值 目标容量,如果小于阈值,采用倍增的方式,以64(字节)作为基本数值,每次翻倍增长64 -- >128 --> 256,直到倍增后的结果大于或等于需要的容量值。

福利:

今天的内容还是挺干的,在此感谢每一个有耐心看到这里的~同时,前面说的为每位粉丝准备了一点见面礼,如果你是最近打算跳槽换工作或者正在准备学习更高阶技术点的,你应该能用得上:

  • Java架构师学习路线图(对标阿里P7级别,更高阶的大佬小弟就不在这献丑了)
  • 模块化学习资源(Java并发编程、分布式缓存的原理及应用、ZooKeeper原理及应用、Netty网络编程原理及应用、Kafka原理及应用、常见的23种经典设计模式、Spring原理及应用、数据结构与算法……)
  • 2022年大厂面试高频知识点整理

资料持续更新中,目前全部都是免费送给大家,如果有需要,尽管拿走,添加我助手领取

举报

相关推荐

0 条评论