# Netty-study **Repository Path**: debugzt/netty-study ## Basic Information - **Project Name**: Netty-study - **Description**: 记录学习netty过程中的代码实现(包括传统bio、nio以及netty) - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-09-09 - **Last Updated**: 2022-01-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: 学习 ## README # 为什么学习netty Netty是一个高性能、异步事件驱动的NIO框架,基于JAVA NIO提供的API实现。Netty提供异步的、[事件驱动](https://baike.baidu.com/item/事件驱动/9597519)的网络应用程序框架和工具,**用以快速开发高性能、高可靠性的[网络服务器](https://baike.baidu.com/item/网络服务器/99096)和客户端程序。** 参考:https://blog.csdn.net/bjweimengshu/article/details/78786315 # **netty高性能的原因** ## 基于主从Reactors多线程模型 reactor线程模型有三种: ### **Reactor单线程模型** 只有一个线程处理所有的【连接请求】、【接收消息】、【发送消息】等IO操作(缺点明显) img ### **Reactor多线程模型** 与单线程模型不同点在于:使用线程池来处理【连接请求】、【接收消息】、【发送消息】等IO操作,提高并发能力。但是,在一些特殊的应用场景下,单独的一个Acceptor线程可能会存在性能不足的问题。 img ### **主从Reactor多线程模型** 与第二种多线程模型相比,将Reactor分为两部分: 1.mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor; 2.subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。 img ## 同步非阻塞通信 **Netty 的IO 线程NioEventLoop 聚合了多路复用器Selector**,可以同时并发处理成百上千个客户端Channel,由于读写操作都是非阻塞的,这就可以充分提升IO 线程的运行效率,避免由于频繁IO 阻塞导致的线程挂起,(Netty对应操作系统层面的IO模型应是IO多路复用的epoll)。 服务端通信序列图: ![输入图片说明](https://images.gitee.com/uploads/images/2021/1027/145721_4c817952_8718564.png "屏幕截图.png") 客户端通信序列图: ![输入图片说明](https://images.gitee.com/uploads/images/2021/1027/150216_a5aad2f0_8718564.png "屏幕截图.png") 另外,由于**Netty采用了异步通信模式**(事件驱动),一个IO 线程可以并发处理N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 ## 零拷贝(Zero copy) Netty 的“零拷贝”主要体现在如下三个方面: - Netty 的接收和发送ByteBuffer 采用DIRECT BUFFERS,使用堆外直接内存进行Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket 读写,JVM 会将堆内存Buffer 拷贝一份到直接内存中,然后才写入Socket 中。**相比于堆外直接内存,多了将数据从操作系统内核缓冲区拷贝到用户缓冲区这个过程**。 - Netty 提供了组合Buffer 对象,可以聚合多个ByteBuffer 对象,用户可以像操作一个Buffer 那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer 合并成一个大的Buffer。 - Netty 的文件传输采用了**transferTo()**方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write()方式导致的内存拷贝问题。对于很多操作系统它直接将文件缓冲区的内容发送到目标Channel 中,而不需要通过拷贝的方式,这是一种更加高效的传输方式,它实现了文件传输的“零拷贝” 零拷贝原理参考:https://zhuanlan.zhihu.com/p/258513662 ## 内存池 ## 无锁化的串行设计理念 参考:https://www.cnblogs.com/wuzhenzhao/p/11202952.html ​ https://blog.csdn.net/mazhongjia/article/details/108417761 # 对netty组件的一些理解 netty的核心组件包括bootstrap、channel、eventLoopGroup、channelHandler、channelPipeline ## (1)bootstrap 分为服务器端的ServerBootstrap和客户端的bootstrap,是整个程序的启动引导类,用于配置整个netty程序,串联各个组件 ## (2)channel 可理解为通道,即服务器端在和客户端连接之后会生成channel,分为服务器端的NioServerSocketChannel和客户端的SocketChannel(可理解为 传统BIO当中的ServerSocket和socket)。用于提供网络IO操作 ## (3)eventLoopGroup & eventLoop eventLoop:每个eventLoopGroup当中有若干个eventLoop同时工作,每个eventLoop维护着一个selector选择器\ eventLoopGroup:可理解为线程组,一般服务器端会有两个group:bossGroup和workerGroup,连接过程如下: ![输入图片说明](https://images.gitee.com/uploads/images/2021/1012/153126_491bd061_8718564.png "屏幕截图.png") **1.bossGroup当中的eventLoop不断轮询selector 用来查看是否有新连接产生;--> 2.一旦有新连接产生那么会为其生成一个SocketChannel,并注册到workerGroup当中的eventLoop中的selector上;--> 3.每一个生成的channel都会关联上一个pipeline,用于处理channel的入站和出站操作;--> 4.workerGroup当中的eventLoop轮询selector查询这些注册的channel是否有已就绪的IO事件发生,若有则调用pipeline当中的handler进行操作** ## (4)channelHandler & channelPipeline channelHandler:handler类 用来处理IO事件或是拦截IO操作,(比如在有数据可读时、数据读取完毕时)执行相应的操作 channelPipeline:一个channel有且仅有一个channelPipeline与之对应,channelPipeline当中维护着一个由channelHandlerContext组成的双向链表, 每个channelHandlerContext中又关联着一个channelHandler 输入图片说明 **!!channelHandler实际上就是一个事件回调函数,程序员并不会主动的去调用这些方法,而是当某些事件发生时自动触发这些方法; !!因为一个channelPipeline当中是一个有很多个channelHandler的双向链表,所以当事件发生时,会按链表的顺序执行到这些handler当中的方法 (一个handler执行完,会将其转发到pipeline中的下一个handler)** # 基于netty+zookeeper实现rpc框架 ## 1.关键技术 - 服务发布与订阅:服务端使用Zookeeper注册服务地址,客户端从Zookeeper获取可用的服务地址。 - 通信:使用Netty作为通信框架 - Spring:使用Spring配置服务,加载Bean,扫描注解。 - 动态代理:客户端使用代理模式透明化服务调用。 - 消息编解码(序列化):使用Protostuff序列化和反序列化消息 ## 2.核心流程 1. 生产者server:加载服务接口,并缓存;服务注册,将服务接口以及服务主机信息写入注册中心(本例使用的是 zookeeper),启动网络服务器并监听。消费方(client)调用以本地调用方式调用服务; 2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;代理服务接口生成代理对象,服务发现(连接 zookeeper,拿到服务地址列表,通过客户端负载策略获取合适的服务地址)。 3. client stub找到服务地址,并将消息发送到服务端; 4. server stub收到消息后进行解码; 5. server stub根据解码结果调用本地的服务; 6. 本地服务执行并将结果返回给server stub; 7. server stub将返回结果打包成消息并发送至消费方; 8. client stub接收到消息,并进行解码; 9. 服务消费方得到最终结果。 ## 3.学习路线 详情见另一个仓库: 一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架(fork from [SnailClimb](https://gitee.com/SnailClimb) / [guide-rpc-framework](https://gitee.com/SnailClimb/guide-rpc-framework)) guide-rpc-framework:https://gitee.com/debugzt/guide-rpc-framework