一个稳定高效的netty服务端开发.
我以通俗易懂的语言
记录了,优雅退出,流量控制,流量整形,内存池,读写队列积压,内存泄漏 等重要点
如果你看到了这文章,对你有一点帮助,可以点个赞
前言:
假设客户端每秒给服务器发送1G的数据
而服务器每秒一定要读取1G吗?服务端会在高峰时期导致读队列积压然后宕机吗
假设服务端每秒给客户端发送1G的数据
而客户端接收的过来吗? 高峰期服务端会造成发送队列积压导致雪崩吗
netty的bytebuf中的堆外内存,堆内存,池化和未池化,如何正确使用?
netty的work线程组如何保证高效执行.
netty的优雅退出之类的.
很多这样问题都很有意思.这是入坑netty最常见的疑问.
0.同步思维
需要先记得比较重要的一件事.
当我们调用了channel的write或writeandflush方法,并不代表数据真的发送过去了
这个时候对发送字节做统计是不准确的,netty的网络io操作都是异步的,并不是同步执行的.
异步和同步的思维是不同的,不是说调用了这个方法后就立刻执行.
如果想监听是否发送的成功,应该在write后返回的channelfuture对象中进行判断.
当write发送成功后,write就会回调channelfuturelisten的operationcomplete方法.
但是异步的回调方法的执行是由Work的NioEventloop线程调用的,
也要保证该线程不能阻塞,或者在回调内开启一个业务线程执行.并且也要考虑多线程的并发安全问题.
ByteBuf bt1 = Unpooled.buffer();
bt1.writeBytes("我爱netty".getBytes("UTF-8"));
ChannelFuture cf = ctx.writeAndFlush(bt1);
cf.addListener((f)->
{
System.out.println("数据发送成功的回调");
});
1.Netty的优雅关闭 同步/异步思维
Main运行后,JVM管理的多个线程中(main主线程,GC守护线程,部分子线程)
只有非守护线程都运行完毕后,Jvm就会自动推出并且关闭守护线程,
守护线程常见的有GC垃圾回收,也可以自己设定线程为守护线程
Thread.setDaemon(true);可以用来进行一些无关紧要的日志输出为守护线程.
Netty中通过boostrap.bind(port)绑定端口这个方法,
并非在调用方线程执行,通俗的说,虽然是main线程调用的它,但它会用自己的线程执行这个操作
(也就是底层通过NioEventLoop线程执行)(调用方main,执行方nioEventLoop)
这个时候异步就产生了,main线程进行接下来的代码执行,NioEventLoop线程进行端口绑定.
而我们的多数工作都需要等端口绑定完毕后执行,所以需要在.bind(port)后面添加.sync()方法
它能保证让我们的main线程,在这个异步方法执行完毕后,再往后执行,会让main阻塞下来等待.
1.1 Netty的优雅关闭 意外情况
根据上面的特性来说
绑定完毕线程后,如果设置端口的时候,没设置sync(),
这个时候main线程没有被阻塞住,且main后面没有任何代码需要执行的时候
那么当NioEventLoop执行完毕绑定端口后,main线程代码也早已执行完毕,已经不存在了.
如果main中写了finally,就代表运行即退出
(一个线程绑定端口,一个线程运行完毕执行了finally,异步就混乱了)这是非常需要注意的.
注意的就是NioEventLoop是非守护线程,运行之后,不会主动退出,只有调用shutdown才会退出.
如果你的netty服务端意外退出了,那么很大概率是被调用了shutdown.
1.2 Netty的优雅关闭 优雅的执行过程
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.group(boosGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//代码省略
}
});
ChannelFuture cf = sb.bind(9999).sync();
cf.channel().closeFuture().sync();//A标记
} finally {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
一般shutdownGracefully会写在finally中比如上面的操作:
这个代码的运行也是没有任何问题的.但是它不优雅.
这样会有一个main线程在A标记处无限的阻塞
此时main线程最终作用,就是等待A标记处的sync的返回,这样也会浪费一个main线程…
它有点违背了netty的异步操作的概念了.
可以把shutdownGracefully函数写在cf.channel().closeFuture().addListener中,
这样main线程会主动释放无需在阻塞等待了.并且管道关闭的时候,会自动的进行优雅关闭.
优雅式:初始化netty服务端,绑定监听端口,向colosefuture注册监