涵盖常用优化与高并发环境优化方案
超时时间:
客户端加入到服务端时可以设置连接超时时间,避免过长等待
package option;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
/* 优化 - 客户端超时时间设置 */
public class TestConnectionTimeOut {
public static void main(String[] args) {
EventLoopGroup workGroup = new NioEventLoopGroup(1);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workGroup)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000) //连接服务端的超时时间,单位毫秒
.channel(NioSocketChannel.class)
.handler(new LoggingHandler());
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workGroup.shutdownGracefully();
}
}
}
长时间连接不到报错
队列:
客户端连接到服务端时需要进行三次握手,当完成三次握手后,连接信息将会从半连接队列(syns queue)转移到全连接队列(accept queue),Netty服务端进行Accept时将会从全连接队列中获取数据,通过在客户端设置 ChannelOption.SO_BACKLOG 来设置全连接队列大小
如果 accpet queue 队列满了,server 将发送一 个拒绝连接的错误信息到 client
Netty创建连接流程
Linux环境下还可以通过修改配置文件实现限制队列的大小:
●sync queue (半连接队列):大小通过/proc/sys/net/ipv4/tcp_max_syn_backlog指定,syncookies启用的情况下,逻辑上没有最大值限制,这个设置便被忽略
●accept queue (全连接队列):其大小通过/proc/sys/net/core/somaxconn指定,在使用listen函时,内核会根据传入的 backlog参数与系统参数,取二者的较小值
package option;
import http.ServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 优化 - 服务端全连接队列
*/
public class TestServerBlockLog {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.option(ChannelOption.SO_BACKLOG,1024) //限制全连接队列最多能存放1024个需要accept的连接资源
.channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer());
ChannelFuture sync = serverBootstrap.bind(6668).sync();
sync.channel().closeFuture().sync(); //对关闭通道进行监听(当有关闭通道的消息时才进行监听)
} finally {
bossGroup.shutdownGracefully(); //关闭资源
workerGroup.shutdownGracefully(); //关闭
}
}
}
关闭Nagle算法:
在拆包和粘包中提过Netty会使用Nagle算法将多次间隔较小且数据量小的数据合并成一个大的数据块进行封包发送,实际业务中该算法通常需要关闭使用
具体配置为 ChannelOption.TCP_NODELAY ,默认为 false(开启),属于SocketChannel参数
ServerBootstrap.childOption(ChannelOption.TCP_NODELAY,true) 服务端设置
Bootstrap.option(ChannelOption.TCP_NODELAY,true) 客户端设置
常用参数配置:
1、Netty 4.1 之前对象缓冲池默认没有开启池化,可以手动指定开启
2、在内存分配中分析过会创建不同级别的内存,如果手动指定可以避免进行计算(设置过大数值可能会造成资源浪费),如果无法确定可以通过自适应方式进行调整
public NettyServer(){
private EventLoopGroup bossGroup = new NioEventLoopGroup();
private EventLoopGroup workGroup = new NioEventLoopGroup();
private ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
//缓存区自适应,最好可以进行手动计算Bytebuf数据包大小来设置缓存区空间
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
//将缓冲对象池进行池化(Netty 4.1版本后默认启用池化功能)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
//开启日志记录
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
}}
);
}
优化WorkGroup执行效率:
业务耗时操作通常发生在查询数据局、IO操作、网络请求过程中,当高并发环境下需考虑使用线程组处理业务Handler执行逻辑(源码参考),在高并发环境下需要引入 Disruptor优化执行业务逻辑,添加线程组主要有两种方式:
线程组方案一:
业务 Handler 实际为 workerGroup 进行处理,由于 NioEventLoopGroup 默认创建线程数仅为CPU核数X2,高并发环境下处理能力不足,因此需要手动指定其线程数,一般为平均QPS数即可
在 workerGroup 创建时或具体业务 Handler 处指定线程数即可
线程组方案二:
在业务逻辑处理事件中使用线程池进行操作
Disruptor :
高并发环境下线程组存在瓶颈,应使用底层为无锁的 Disruptor 执行业务逻辑,参考 Disruptor实战章节
Linux环境:
实际生产服务通常部署在 Linux 环境,为实现百万并发连接处理通常会部署多个Netty服务端或者使Netty服务端绑定多个端口,但此场景下Linux的文件句柄数限制会导致客户端连接数量达不到接收能力,因此需要进行调整
单机模拟百万连接代码参考:源码
局部文件句柄数限制:
Linux系统的文件描述符打开最大数量限制,Netty的Socket和普通文件都是由文件描述符表示,当业务为高并发环境时,需要在脚本中指定该参数的值,使用 ulimit -n 可以查看一个JVM进程中最多能查看的文件数
通过在配置文件中末尾添加新参数,例如将任何用户一个进程能打开的文件数设置为100w ,从而解决单一用户的连接数限制
ulimit -n 查看当前数量限制
vim /etc/security/limits.conf 编辑配置文件
添加新参数:
* hard nofile 1000000
* soft nofile 1000000
全局文件句柄数限制:
局部文件句柄数限制针对的是单个用户,全局文件句柄数限制针对的是全部用户一共能够访问的文件数,在高并发环境下也需要对该参数进行修改,不同Linux内核下该参数值不同
主要分为临时修改与永久修改,永久修改需要刷新内核配并进行重启
cat /proc/sys/fs/file-max 查看全局文件句柄数限制
echo 1000000 > /proc/sys/fs/file-max 临时修改全局文件句柄数限制(重启后失效)
vim /etc/sysctl.conf 永久修改全局文件句柄数限制
sudo sysctl -p 刷新内核文件
修改内容:
fs:file-max=1000000