Netty 5 迁移指南
为了允许 netty 5 和 netty 4 在同一个类路径中同时存在,我们将 netty 5 类的包名更改为 io.netty5.*
。
Netty 5 引入了新的缓冲区 API,比 ByteBuf
更简单、更安全。
Netty 4.1 ByteBuf
在写入时会根据需要自动扩展容量,直到达到某个最大容量。
新的 Buffer
API 不再执行此操作,也不再有容量和最大容量之分。相反,代码应该分配大小合适的缓冲区(大小参数现在是必需的),和/或根据需要调用 ensureWritable()
。ensureWritable()
方法现在还可以接受参数,允许它同时进行压缩(与旧的 discardReadBytes()
相同)和扩展,只需一次内存复制。
包含一组适配器,可以在两个 API 之间进行转换,并允许它们共存,直到所有句柄和相关代码都已迁移。
ByteBufBuffer.wrap()
方法采用一个 ByteBuf
实例并返回一个 Buffer
- 新 API 的缓冲区实例。相反,ByteBufAdaptor.intoByteBuf
方法采用一个 Buffer
并返回一个 ByteBuf
。这两种方法都将以尽可能最高效的方式进行转换,并且多次转换将相互撤销,以避免嵌套适配器。
还包括一个 BufferConversionHandler
,它可以插入到依赖于不同 API 的句柄之间的管道中。请注意,BufferConversionHandler
无法转换任何 ByteBufHolder
或 BufferHolder
对象。包含 Buffer
或 ByteBuf
实例的对象需要使用自定义 MessageToMessageCodec
等进行转换。
新 Buffer
API 中的关键区别在于不再允许别名。别名是指两个或多个缓冲区对象引用相同的底层内存。这意味着 slice()
和 duplicate()
等方法不再可用。另一方面,生命周期处理得到了简化,并且可以从 API 中完全删除引用计数。
在您使用 slice()
、duplicate()
及其 retain*
变体以及 retain()
方法系列的地方,您现在将改为使用 split()
、readSplit()
和 copy()
。
split
方法系列类似于 slice()
,但它们只能在缓冲区的末尾进行切片,并且返回的缓冲区切片从原始缓冲区中移除,从而防止别名。
retain()
和 release()
方法已经消失。相反,缓冲区有一个 close()
方法,它将在缓冲区的生命周期结束时调用。缓冲区实现 AutoCloseable
作为一种便利,当缓冲区的范围和生命周期完全是本地的。
在大多数使用 retain()
的地方,实际上是为了取消超类或子类中无条件 release()
的效果。在这些情况下,可以使用 split()
,因为这些情况通常涉及传递缓冲区的可读部分,而 split()
会生成两个缓冲区,每个缓冲区都必须关闭。
有一个新的 send()
方法,可用于在类型系统中对缓冲区的“所有权”从一个地方移动到另一个地方进行编码。例如,CompositeBuffer
工厂方法使用此方法来确保复合缓冲区获得对组件缓冲区的独占访问权限。这防止了通过缓冲区组合进行别名引用。
缓冲区现在始终是大端序的,*LE
访问器方法已消失。要执行小端序读取或写入,请将 Integer
或 Long
中的 reverseBytes
方法与大端序读取或写入结合使用。
为了简化 API 和类型层次结构,我们决定完全删除 ChannelFuture
/ ChannelPromise
(以及所有子类型/实现)。作为替换,直接使用 Future<Void>
和 Promise<Void>
。
在 netty 5 中完全删除了对 ProgressiveFuture / ProgressivePromise 的支持。原因是,虽然它有时可能有用,但它还要求管道中的所有处理程序在连接承诺时采取特殊操作。实际上并非如此,而且在现实中这样做非常繁琐。
由于上述问题,我们决定最好完全删除此功能,因为此功能的使用量本来就不多。最好根本不支持某些东西,而不是让某些东西只能“有时”起作用。这也意味着要维护的代码更少。
在 netty 4.1.x 中,可以使用 voidPromise()
方法获取一个特殊的 ChannelPromise
实现,可用于各种 IO 操作(如 write
)以减少创建的对象数量。虽然此功能的动机很好,但事实证明,此 ChannelPromise
特例实际上带来了很多问题
- 已删除
Future.addListeners()
、Future.removeListeners()
和Future.removeListener()
。我们删除了删除先前添加的侦听器功能。此功能并未真正使用,因此它允许我们删除一些复杂性并删除一些 API 表面。 - 已添加
Future.isFailed()
方法,该方法检查 future 是否已完成且失败。这类似于现有的Future.isSuccess()
,它检查 future 是否已完成且成功。 - 添加了新的
Future.map()
和Future.flatMap()
方法,这使得基于现有 future 轻松组合和创建新的 future。这些方法通过传播正确处理故障和取消。 - 添加了新的方法以转换为
CompletionStage
,从而更容易与其他 API 进行交互。 - 从
Future
接口中删除了所有阻塞方法,因为人们很容易误用这些方法并阻塞EventLoop
。如果您仍然需要从EventLoop
外部进行阻塞,则需要通过Future.asStage()
转换Future
。返回的FutureCompletionStage
提供阻塞方法。
在 netty 5.x 中,我们向 ChannelOutboundInvoker
添加了 executor()
方法,由于此方法返回 EventExecutor
,因此我们决定从 Channel
中删除 eventLoop()
方法,并仅覆盖 executor()
以返回 EventLoop
以用于 Channel
。
由于我们删除了 ChannelFuture
/ ChannelPromise
,因此我们还将方法的返回类型更改为 Future<Void>
Netty5 现已在其核心支持半关闭构建。为此,引入了 ChannelHandler.shutdown
和 ChannelHandler.channelShutdown
。除此之外,还添加了 Channel.isShutdown(...)
和 ChannelOutboundInvoker.shutdown(...)
。这取代了旧的 DuplexChannel
抽象,该抽象已完全删除。有关更多详细信息,请参阅 请求拉取 #12468。
我们更改了 ChannelHandlerContext,使其不再扩展 AttributeMap。如果您使用属性,则应直接使用仍扩展 AttributeMap
的 Channel
。
Channel.Unsafe 接口已完全删除,因此最终用户无法弄乱内部。
ChannelOutboundBuffer
是我们 AbstractChannel
实现的实现细节,因此已完全从 Channel
本身中删除。
我们删除了 Channel.beforeBeforeWritable()
方法,因为它根本没有使用,并将 Channel.bytesBeforeUnwritable()
重命名为 Channel.writableBytes
。
在 netty 4.x 中,我们添加了使用显式 EventExecutorGroup
向 ChannelPipeline
添加 ChannelHandler
的功能。虽然这看起来像一个好主意,但事实证明,在生命周期方面存在一些问题
-
handlerRemoved(...)
、handlerAdded(...)
可能在“错误的时间”被调用。这会导致很多问题。最糟糕的情况是,handlerRemoved(...)
被调用,而处理程序释放了一些本机内存,因为它期望该处理程序永远不会再被使用了。可能发生的情况是,在它被调用后,channelRead(...)
可能被调用,然后尝试访问先前释放的内存,从而导致 JVM 崩溃。 - 以并发访问/修改管道的方式正确实现“可见性”也相当有难度。
考虑到这一点,我们意识到用户最想要的是让传入的消息由另一个线程处理业务逻辑。最好由用户在自定义实现中完成,因为用户可以更好地控制何时可以销毁事物。
由于我们删除了 ChannelFuture
/ ChannelPromise
,因此我们还将方法的返回类型更改为 Future<Void>
Netty 5 极大地简化了 ChannelHandler
的类型层次结构。
ChannelInboundHandler
和 ChannelOutboundHandler
已合并到 [ChannelHandler
] 中。[ChannelHandler
] 现在同时具有入站和出站处理程序方法。所有采用 ChannelPromise
的出站方法都已更改为返回 Future<Void>
。此更改使使用更不容易出错,并简化了 API。
ChannelInboundHandlerAdapter
、ChannelOutboundHandlerAdapter
和 ChannelDuplexHandlerAdapter
已被删除,并替换为 [ChannelHandlerAdapter
]。
由于现在无法判断处理程序是入站处理程序还是出站处理程序,因此 CombinedChannelDuplexHandler
已被 [ChannelHandlerAppender
] 替换。
有关此更改的更多信息,请参阅 请求提取 #1999。
如果你正在使用 [SimpleChannelInboundHandler
],则必须将 channelRead0()
重命名为 messageReceived()
。
现在可以通过管道双向触发用户/自定义事件。对于入站事件,你可以使用 fireChannelInboundEvent(...)
(替换 fireUserEventTriggered(...)
),对于出站事件,可以使用 sendOutboundEvent(...)
。这两者都可以像往常一样被 ChannelHandler
中定义的方法拦截。
现在可以轻松地通过 ChannelPipeline
中包含的 ChannelHandler
影响 Channel
的可写性。当 ChannelHandler
本身缓冲出站数据时,这使得可以影响反压。
在 netty 4.x 中,我们针对各种不同的传输(即 NioEventLoopGroup
、EpollEventLoopGroup
等)有不同的 EventLoopGroup
/ EventLoop
实现,而在 netty 5 中,我们已将其更改为仅有一个 EventLoopGroup
实现,称为 MultiThreadEventLoopGroup
。此 MultiThreadEventLoopGroup
采用特定于传输本身的 IoHandlerFactory
(即 NioHandler.newFactory()
、EpollHandler.newFactory()
等)。此更改带来了许多优势。例如,可以轻松扩展 MultiThreadEventLoopGroup
,从而装饰事物或添加自定义指标等。然后,此实现可以在不同的传输中重复使用。在自定义的可能性方面,这与 JDK 中提供的 ThreadPoolExecutor
非常相似。
现在,可以在尝试使用 Channel
子类型之前检查它是否与 EventLoopGroup
/ EventLoop
兼容。这有助于选择正确的 Channel
子类型。
已向 EventLoop
接口添加了新方法,以允许注册和取消注册 Channel
。这些方法不应由用户直接使用,而应由 Channel
实现本身使用。
为了精简代码库并减轻维护负担,以下编解码器和处理程序已移至 Netty Contrib
- netty-codec-xml
- netty-codec-redis
- netty-codec-memcache
- netty-codec-stomp
- netty-codec-haproxy
- netty-codec-mqtt
- netty-codec-socks
- netty-handler-proxy
io.netty.handler.codec.json
io.netty.handler.codec.marshalling
io.netty.handler.codec.protobuf
io.netty.handler.codec.serialization
io.netty.handler.codec.xml
io.netty.handler.pcap