Netty5源码分析--4.总结

JAVA NIO 复习

请先参考我之前的博文JAVA学习笔记–3.Network IO的 NIO(NonBlocking IO) SOCKET 章节。这里主要讲下JAVA NIO其中几个比较被忽略的细节,不求全,欢迎补充。

 

API

Select

当调用ServerSocketChannel.accept();时,如果该channel处于非阻塞状态而且没有等 待(pending)的连接,那么该方法会返回null;否则该方法会阻塞直到连接可用或者发生I/O错误。此时实际上Client发送了connect 请求并且服务端是处于non-blocking模式下,那么这个accept()会返回一个不为null的channel。

当调用SocketChannel.connect()时,如果该channel出于non-blocking模式,只有在本地连接下才可能立即完成连接,并返回true;在其他情况下,该方法返回false,并且必须在后面调用 SocketChannel.finishConnect方法。如果SocketChannel.finishConnect的执行结果为true,才意味着连接真正建立。

SocketChannel.write()方法内部不涉及缓冲,它直接把数据写到socket的send buffer中。

 

每次迭代selector.keys()完时,记得remove该SelectionKey,防止发生CPU100%的问题。

通常不该register OP_WRITE,一般来说socket 缓冲区总是可写的,仅在write()方法返回0时或者未完全写完数据才需要register OP_WRITE操作。当数据写完的时候,需要deregister OP_WRITE。 socket空闲时,即为可写.有数据来时,可读。对于nio的写事件,只在发送数据时,如果因为通道的阻塞,暂时不能全部发送,才注册写事件key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);。等通道可写时,再写入。同时判断是否写完,如果写完,就取消写事件即可key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
空 闲状态下,所有的通道都是可写的,如果你给每个通道注册了写事件,那么肯定是死循环了,导致发生CPU100%的问题。详见Netty的 NioSocketChannel的父类方法setOpWrite()和clearOpWrite()方法。这个在iteye里面的帖子Java nio网络编程的一点心得也有说明。

在register OP_CONNECT后,并且触发OP_CONNECT后,需要再deregister OP_CONNECT。详见Netty的NioEventLoop.processSelectedKey()方法后半部分。

另外,需要注意到Selector.wakeup()是一个昂贵的操作,一般需要减少是用。详见NioEventLoop.run()方法内的处理方式。

还有各种各样的坑,实在太多了。详见Java NIO框架的实现技巧和陷阱

 

NIO操作在Netty里面的体现

虽然笔者阅读完几个重要的交互过程,但是代码细节较多,无法体现NETTY对NIO的使用。于是画了两幅图,与读者共享。

事先说明下,下图图片上的标记2表示是一个新的EventLoop线程,非main线程。

另外,虽然Netty中大量使用了ChannelFuture异步,但是换种方式理解的话,可以理解为同步执行。

 

服务端启动

在此输入图片描述

 

客户端启动

在此输入图片描述

 

服务端处理请求

这里有个细节,就是boss线程注册了OP_ACCEPT线程,然后当接收到客户端的请求时,会使用work线程池里面的线程来处理客户端请求。代码细节在NioServerSocketChannel.doReadMessages的tag1.1.2处以及child.unsafe().register(child.newPromise());处。

 

Netty整体架构

 

模块结构

从Netty的Maven仓库可以看
到,大致分为如下模块:

  • netty-transport:框架核心类Channel,ChannelPipeline,ChannelHandler都是在这个模块里面定义的,提供了对不同协议支持框架。
  • netty-buffer:对ByteBuffer的抽象,可以理解为网络中的数据。
  • netty-codec:对不同格式的数据进行编码,解码。
  • netty-handler:对数据进行处理的handler
  • netty-common:公共处理类。

综上,可以看出,首先对框架机制和传输的数据进行了抽象,然后又对数据如何在框架中传输进行了抽象。

 

他山之石

下面的介绍的思想比较零散,不成系统,主要是阅读代码过程中产生的碰撞,供读者参考。

 

好的设计

Netty 同样也采用了多个Selector并存的结构,主要原因是单个Selector的结构在支撑大量连接事件的管理和触发已经遇到瓶颈。

Bootstrap.channel()方法通过传递class对象,然后通过反射来实例化具体的Channel实例,一定程度上避免了写死类名字符串导致未来版本变动时发生错误的可能性。

框架必备的类,大家还可以看下common工程,里面真是一个宝藏。

  • InternalLogger用来避免对第三方日志框架的依赖,如slf,log4j等等。
  • ChannelOption灵活地使用泛型机制,避免用户设置参数发生低级错误。
  • SystemPropertyUtil提供对xt属性的访问方法
  • DefaultThreadFactory提供自定义线程工程类,方便定位问题。
  • PlatformDependent如果需要支持不同平台的话,可以把平台相关的操作都放在一起进行管理。

打印日志时,可以参考这样来拼接参数,String.format("nThreads: %d (expected: > 0)", nThreads)

@Sharable表示该类是无状态的,仅仅起“文档”标记作用。

在子接口里仅仅把父接口方法返回值覆写了,然后什么都不做。这样一定程度上避免了强制转型的尴尬。

把前一个future作为下一个调用方法的参数,这样可以异步执行。然后后面的逻辑可以先判断future结果后再进行处理,从而提升性能。

 

中立

addLast0 等以0为结尾的方法表示私有的含义。

有些方法的getter、setter前缀省略,有点类似jQuery里面命名风格。不过不太建议在自己的产品中使用这种风格,尽量使用和和产品内的一样的命名风格。

 

疑惑

心中存疑,请大家不吝赐教。

在NioEventLoop.run()方法中,好像每次用完SelectionKey没有remove 掉它,可能和SelectedSelectionKeySet实现机制有关。但是没直接看出来具体之间的联系,

为什么boss也要是个线程池?目前来看,服务端2个线程保持不变,main线程出于wait状态,boss线程池其中的一个线程进行接收客户端连接请求,然后把请求转发给worker线程池。

javaChannel().register(eventLoop().selector, 0, this);,为什么ops参数默认是0?

socketChannel.register() and key.interestOps()还是有点不太明白,估计我的思路钻进死胡同了。

 

瑕疵

b.bind(port)这个里面的内容非常复杂,不仅仅是bind一个port那么简单。所以该方法命名不是很好。

父接口依赖子接口,也不是很好。

DefaultChannelPipeline.addLast 这个方法太坑了,并不是把handler加到最后一个上面,而是加到tail前一个。

无一类外的是,继承体系相对复杂。父类,子类的命名通常不能体现出谁是父类,谁是子类,除了一个Abstract能够直接看出来。

 

注意事项

客户端单实例,防止消耗过多线程。这个在http://hellojava.info/中多次提到。

 

其他

目前网上的NIO例子都基本都不太靠谱,BUG多多。建议可以参考下《JAVA 分布式JAVA应用 基础与实践》。

JAVA NIO不一定就比OIO快,重点是更加Scalable。

框架帮趟坑,不要轻易制造轮子,除非现有轮子很差劲。JAVA NIO里面的坑太多了,Netty里面的大量的issue充分说明了问题。技术某种程度上不是最重要的,如果大家努力程度差不多的话,技术上不会差到哪里去。开源产品主要是生态圈的建设。

接收对端数据时,数据通过netty从网络中读取,进行其他各种处理,然后供应用程序使用。发送本地数据时,应用程序首先完成数据处理, 然后通过netty进行各种处理,最终把数据发出。但是为什么要区分inbound,outbound,或者说提供了head->tail以及 tail->head的遍历?

当我们跳出里面的细节时,考虑一下,如果你是作者的话,会如何考虑。整体的一个算法 。不同的通讯模型,nio,sun jdk bug, option(默认和用户设置),异步future、executor,pipeline、context、handler, 设计模式 。

我想象中操作应该是这样的,handler链管理不需要走pipeline,event链也不需要走pipiline,仅仅对数据的发送,接收和操 作才走pipeline。事件机制应该起一些增强型、辅助型作用,不应该影响到核心流程的执行或者起到什么关键作用,主要是应该给第三方扩展用。而 netty中,所有的一切都要走pipelne。

太多异步,怎么测试?

招式vs心法。招式,相当于api;心法,相当于api工作原理,利与弊。还是要理解底层,否则还是可能理解不清楚。cpu,内存,io,网络,而最终浮于招式。

看完了么?知道了Netty是什么,内部大概是怎么运作的,但是有些细节还不知道为什么。真的就理解精髓了么?NIO的坑.. 底层OS的坑.. Netty的精髓是在于对各种细节的处理,坑的处理,对性能的处理,而不是仅仅一个XX模式运用。JAVA NIO细节和坑实在太多,估计再给我一周的时间,也研究不完。有点小沮丧。

 

 

源于:http://my.oschina.net/geecoodeer/blog/193941

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty5.0 架构剖析和源码解读 作者:李林锋 版权所有 email neu_lilinfeng@ © Netty5.0 架构剖析和源码解读1 1. 概述2 1.1. JAVA 的IO演进2 1.1.1. 传统BIO通信的弊端2 1.1.2. Linux 的网络IO模型简介4 1.1.3. IO复用技术介绍7 1.1.4. JAVA的异步IO8 1.1.5. 业界主流的NIO框架介绍10 2.NIO入门10 2.1. NIO服务端10 2.2. NIO客户端13 3.Netty源码分析16 3.1. 服务端创建16 3.1.1. 服务端启动辅助类ServerBootstrap16 3.1.2. NioServerSocketChannel 的注册21 3.1.3. 新的客户端接入25 3.2. 客户端创建28 3.2.1. 客户端连接辅助类Bootstrap28 3.2.2. 服务端返回ACK应答,客户端连接成功32 3.3. 读操作33 3.3.1. 异步读取消息33 3.4. 写操作39 3.4.1. 异步消息发送39 3.4.2. Flush操作42 4.Netty架构50 4.1. 逻辑架构50 5. 附录51 5.1. 作者简介51 5.2. 使用声明51 1. 概述 1.1.JAVA 的IO演进 1.1.1. 传统BIO通信的弊端 在JDK 1.4推出JAVANIO1.0之前,基于JAVA 的所有Socket通信都采用 BIO 了同步阻塞模式( ),这种一请求一应答的通信模型简化了上层的应用开发, 但是在可靠性和性能方面存在巨大的弊端。所以,在很长一段时间,大型的应 C C++ 用服务器都采用 或者 开发。当并发访问量增大、响应时间延迟变大后, 采用JAVABIO作为服务端的软件只有通过硬件不断的扩容来满足访问量的激 增,它大大增加了企业的成本,随着集群的膨胀,系统的可维护性也面临巨大 的挑战,解决这个问题已经刻不容缓。 首先,我们通过下面这幅图来看下采用BIO 的服务端通信模型:采用BIO 通信模型的 1connect NewThread1 WebBrowse 2connect 2handle(Req) WebBrowse 3connect Acceptor NewThread2 WebBrowse WebBrowse 4connect NewThread3 3sendResponsetopeer NewThread4 图1.1.1-1 BIO通信模型图 服务端,通常由一个独立的Accepto 线程负责监听客户端的连接,接收到客户 端连接之后为客户端连接创建一个新的线程处理请求消息
2023-07-14 15:19:01.215 WARN 7308 --- [sson-netty-2-15] io.netty.util.concurrent.DefaultPromise : An exception was thrown by org.redisson.misc.RedissonPromise$$Lambda$888/0x00000008008f7440.operationComplete() java.lang.NullPointerException: null 2023-07-14 15:19:01.216 ERROR 7308 --- [sson-netty-2-15] o.r.c.SentinelConnectionManager : Can't execute SENTINEL commands on /172.24.107.11:26379 org.redisson.client.RedisException: ERR No such master with that name. channel: [id: 0x2d66827d, L:/172.23.9.103:46812 - R:/172.24.107.11:26379] command: (SENTINEL SLAVES), params: [mymaster] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:365) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:196) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:134) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:104) ~[redisson-3.13.3.jar:3.13.3] at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na] 解决方法
最新发布
07-15

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值