单机百万连接调优和Netty应用级别调优
单机百万连接调优
准备两台Linux服务器,一个充当服务端,一个充当客户端。
服务端
- 操作系统:CentOS 7
- 配置:4核8G
- IP:192.168.118.138
客户端
- 操作系统:CentOS 7
- 配置:4核8G
- IP:192.168.118.139
服务端和客户端均要配置java环境,基于jdk1.8。
如何模拟百万连接#
如果服务端只开一个端口,客户端连接的时候,端口号是有数量限制的(非root用户,从1024到65535,大约6w),所以服务端开启一个端口,客户端和服务端的连接最多6w个左右。
为了模拟单机百万连接,我们在服务端开启多个端口,例如8000~8100,一共100个端口,客户端还是6w的连接,但是可以连接服务端的不同端口,所以就可以模拟服务端百万连接的情况。
准备服务端程序#
服务端程序的主要逻辑是:
绑定8000端口一直到8099端口,一共100个端口,每2s钟统计一下连接数。
channelActive触发的时候,连接+1, channelInactive触发的时候,连接-1。
代码见:Server.java
准备客户端程序#
客户端程序的主要逻辑是:
循环连接服务端的端口(从8000一直到8099)。
代码见:Client.java
准备好客户端和服务端的代码后,打包成Client.jar和Server.jar并上传到客户端和服务端的/data/app目录下。打包配置参考pom.xml
服务端和客户端在/data/app下分别准备两个启动脚本,其中服务端准备的脚本为startServer.sh, 客户端准备的脚本为startClient.sh,内容如下:
startServer.sh
java -jar server.jar -Xms6.5g -Xmx6.5g -XX:NewSize=5.5g -XX:MaxNewSize=5.5g -XX:MaxDirectMemorySize=1g
startClient.sh
java -jar client.jar -Xms6.5g -Xmx6.5g -XX:NewSize=5.5g -XX:MaxNewSize=5.5g -XX:MaxDirectMemorySize=1g
脚本文件见:startServer.sh 和 startClient.sh
先启动服务端
cd /data/app/ ./startServer.sh
查看日志,待服务端把100个端口都绑定好以后。
在启动客户端
cd /data/app/./startClient.sh
然后查看服务端日志,服务端在支撑了3942个端口号以后,报了如下错误:
Caused by: java.io.IOException: Too many open filesat sun.nio.ch.FileDispatcherImpl.init(Native Method)at sun.nio.ch.FileDispatcherImpl.<clinit>(FileDispatcherImpl.java:35)
突破局部文件句柄限制#
使用ulimit -n命令可以查看一个jvm进程最多可以打开的文件个数,这个是局部文件句柄限制,默认是1024,我们可以修改这个值
vi /etc/security/limits.conf
增加如下两行
* hard nofile 1000000
* soft nofile 1000000
以上配置表示每个进程可以打开的最大文件数是一百万。
突破全局文件句柄限制#
除了突破局部文件句柄数限制,还需要突破全局文件句柄数限制,修改如下配置文件
vi /proc/sys/fs/file-max
将这个数量修改为一百万
echo 1000000 > /proc/sys/fs/file-max
通过这种方式修改的配置在重启后失效,如果要使重启也生效,需要修改如下配置
vi /etc/sysctl.conf
在文件末尾加上
fs.file-max=1000000
服务端和客户端在调整完局部文件句柄限制和全局文件句柄限制后,再次启动服务端,待端口绑定完毕后,启动客户端。
查看服务端日志,可以看到,服务端单机连接数已经达到百万级别。
.....
connections: 434703
connections: 438238
connections: 441195
connections: 444082
connections: 447596
.....
connections: 920435
connections: 920437
connections: 920439
connections: 920442
connections: 920443
connections: 920445
.....
Netty应用级别调优#
场景#
服务端接受到客户端的数据,进行一些相对耗时的操作(比如数据库查询,数据处理),然后把结果返回给客户端。
模拟耗时操作#
在服务端,模拟通过sleep方法来模拟耗时操作,规则如下:
- 在90.0%情况下,处理时间为1ms
- 在95.0%情况下,处理时间为10ms
- 在99.0%情况下,处理时间为100ms
- 在99.9%情况下,处理时间为1000ms
代码如下
protected Object getResult(ByteBuf data) {int level = ThreadLocalRandom.current().nextInt(1, 1000);int time;if (level <= 900) {time = 1;} else if (level <= 950) {time = 10;} else if (level <= 990) {time = 100;} else {time = 1000;}try {Thread.sleep(time);} catch (InterruptedException e) {}return data;
}
客户端统计QPS和AVG逻辑#
获取当前时间戳,客户端在和服务端建立连接后,会每隔1s给服务端发送数据,发送的数据就是当前的时间戳,服务端获取到这个时间戳以后,会把这个时间戳再次返回给客户端,所以客户端会拿到发送时候的时间戳,然后客户端用当前时间减去收到的时间戳,就是这个数据包的处理时间,记录下这个时间,然后统计数据包发送的次数,根据这两个变量,可以求出QPS和AVG,其中:
QPS 等于 总的请求量 除以 持续到当前的时间
AVG 等于 总的响应时间除以请求总数
客户端源码参考:Client.java
服务端源码参考:Server.java
服务端在不做任何优化的情况下,关键代码如下
...
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));ch.pipeline().addLast(/*businessGroup,*/ ServerBusinessHandler.INSTANCE);
// ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);}});
...
@ChannelHandler.Sharable
public class ServerBusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {public static final ChannelHandler INSTANCE = new ServerBusinessHandler();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {ByteBuf data = Unpooled.directBuffer();data.writeBytes(msg);Object result = getResult(data);ctx.channel().writeAndFlush(result);}protected Object getResult(ByteBuf data) {int level = ThreadLocalRandom.current().nextInt(1, 1000);int time;if (level <= 900) {time = 1;} else if (level <= 950) {time = 10;} else if (level <= 990) {time = 100;} else {time = 1000;}try {Thread.sleep(time);} catch (InterruptedException e) {}return data;}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// ignore}
}
运行服务端和客户端,查看客户端日志
.....
qps: 1466, avg response time: 35.68182
qps: 832, avg response time: 214.28384
qps: 932, avg response time: 352.59363
qps: 965, avg response time: 384.59448
qps: 957, avg response time: 403.33804
qps: 958, avg response time: 424.5246
qps: 966, avg response time: 433.35272
qps: 980, avg response time: 484.2116
qps: 986, avg response time: 478.5395
.....
优化方案一:使用自定义线程池处理耗时逻辑#
将服务端代码做如下调整
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));//ch.pipeline().addLast(/*businessGroup,*/ ServerBusinessHandler.INSTANCE);ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);}});
其中
ServerBusinessThreadPoolHandler中,使用了自定义的线程池来处理耗时的getResult方法。关键代码如下:
private static ExecutorService threadPool = Executors.newFixedThreadPool(1000);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {ByteBuf data = Unpooled.directBuffer();data.writeBytes(msg);threadPool.submit(() -> {Object result = getResult(data);ctx.channel().writeAndFlush(result);});}
再次运行服务端和客户端,可以查看客户端日志,QPS和AVG指标都有明显的改善
....
qps: 1033, avg response time: 17.690498
qps: 1018, avg response time: 17.133448
qps: 1013, avg response time: 15.563113
qps: 1010, avg response time: 15.415672
qps: 1009, avg response time: 16.049961
qps: 1008, avg response time: 16.179882
qps: 1007, avg response time: 16.120466
qps: 1006, avg response time: 15.822202
qps: 1006, avg response time: 15.987518
....
实际生产过程中,
Executors.newFixedThreadPool(1000);中配置的数量需要通过压测来验证。
优化方案二:使用Netty原生的线程池优化#
我们可以通过Netty提供的线程池来处理耗时的Handler,这样的话,无需调整Handler的逻辑(对原有Handler无代码侵入),关键代码:
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));// ch.pipeline().addLast(ServerBusinessHandler.INSTANCE);// 使用业务线程池方式// ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);// 使用Netty自带线程池方式ch.pipeline().addLast(businessGroup,ServerBusinessHandler.INSTANCE);}});
其中businessGroup是Netty自带的线程池
EventLoopGroup businessGroup = new NioEventLoopGroup(1000);
ServerBusinessHandler中的所有方法,都会在businessGroup中执行。
再次启动服务端和客户端,查看客户端日志
.....
qps: 1027, avg response time: 23.833092
qps: 1017, avg response time: 20.98855
qps: 1014, avg response time: 18.220013
qps: 1012, avg response time: 17.447332
qps: 1010, avg response time: 16.502508
qps: 1010, avg response time: 15.692251
qps: 1009, avg response time: 15.968423
qps: 1008, avg response time: 15.888149
.....
更多优化建议#
参考Netty性能调优奇技淫巧还有其他的吗?
1.如果QPS过高,数据传输过快的情况下,调用writeAndFlush可以考虑拆分成多次write,然后单次flush,也就是批量flush操作
2.分配和释放内存尽量在reactor线程内部做,这样内存就都可以在reactor线程内部管理
3.尽量使用堆外内存,尽量减少内存的copy操作,使用CompositeByteBuf可以将多个ByteBuf组合到一起读写
4.外部线程连续调用eventLoop的异步调用方法的时候,可以考虑把这些操作封装成一个task,提交到eventLoop,这样就不用多次跨线程
5.尽量调用
ChannelHandlerContext.writeXXX()方法而不是channel.writeXXX()方法,前者可以减少pipeline的遍历
6.如果一个ChannelHandler无数据共享,那么可以搞成单例模式,标注@Shareable,节省对象开销对象
7.如果要做网络代理类似的功能,尽量复用eventLoop,可以避免跨reactor线程
单机百万连接调优和Netty应用级别调优相关推荐
- 单机百万连接调优和 Netty 应用级别调优
单机百万连接调优和 Netty 应用级别调优 作者:Grey 原文地址: 博客园:单机百万连接调优和 Netty 应用级别调优 CSDN:单机百万连接调优和 Netty 应用级别调优 说明 本文为深度 ...
- 一文搞定Netty,打造单机百万连接测试!
文章目录 1.Netty框架简介 1.1.Netty简介 1.2.Netty主要特性 1.3.Netty和Tomcat的区别 1.4.BIO编写Client-Server通信 2.常见的网络IO模型 ...
- Netty 单机百万连接测试
1.Netty框架简介 1.1.Netty简介 netty是jboss提供的一个java开源框架,netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可用性的网络服务器和客户 ...
- 用Netty实现单机百万TCP长连接
一 服务实现模型 单机百万连接有多种方式, 这里采用一个netty server 占用8888 端口,用客户端机器模拟百万客户端连接 模拟实现的方式 以下是示意图 如果一台客户端模拟3万个连接,那么 ...
- Netty 实现百万连接的难点和优化点
推送服务 还记得一年半前,做的一个项目需要用到 Android 推送服务.和 iOS 不同,Android 生态中没有统一的推送服务.Google 虽然有 Google Cloud Messaging ...
- Netty 模拟百万连接
我们知道单机的端口最多65536,除去系统使用的端口, 留给程序使用的也就6万个端口, 在需要对单机做长连接压力测试的时候,如果要测60W的长连接并发,就得找10台机器,而一般情况下我们并没有这么 ...
- bootstrap外不引用连接_网络编程Netty IoT百万长连接优化,万字长文精讲
IoT是什么 The Internet of things的简称IoT,即是物联网的意思 IoT推送系统的设计 比如说,像一些智能设备,需要通过APP或者微信中的小程序等,给设备发送一条指令,让这个设 ...
- Hive 调优集锦,让 Hive 调优想法不再碎片化
一.前言 1.1 概念 Hive 依赖于 HDFS 存储数据,Hive 将 HQL 转换成 MapReduce 执行,所以说 Hive 是基于Hadoop 的一个数据仓库工具,实质就是一款基于 HDF ...
- JVM调优参数大全及G1GC调优
一.JVM常见参数 1.标准参数 -verbose:class 打印每个class信息 -verbose:gc 打印每次gc信息 2.非标参数 -X -Xlog:gc:filename 设置GC lo ...
最新文章
- 编写可调模板并使用自动调谐器
- centos8开启网络
- linux网络管理证书,计算机网络管理工程师技术水平证书有什么用
- 常用开发技巧系列(三)
- oracle 自动补全函数,Oracle自我补充之trunc()函数的使用方法
- [云炬创业基础笔记]第七章创业资源测试5
- [MATLAB调试笔记]phase space plot
- 给选择模型的子模型添加COL
- iOS数据库编程(Andy)
- CDA数据分析师携手万宝盛华开启人才培训新篇章
- c语言实现定积分运算
- 钉钉关联微信公众号刷步数思路
- 2022年Unity客户端面试题总结
- linux下查看磁盘空间
- 服务容错 - Hystrix
- vm虚拟机网络标志_虚拟机安装win7系统后网络图标黄色标志不能上网如何解决
- JAVA获取tomcat信息
- java中format
- ubuntu20.04系统安装及配置
- 关于CWL来创作和分析的优势
热门文章
- YII Framework学习教程-YII的日志
- FTP操作命令(windows系统)
- docker-comose搭建sonarqube 及 maven项目使用
- java堆按照最小排序_java.util.TaskQueue的最小堆排序算法的应用
- 我期待一个由UBTC改写加密货币历史的全新时代
- 云服务器ECS,你真的懂吗?
- python自动化办公教程书籍_盘点使用Python进行自动化办公所需要的知识点
- 联想Y7000在配置ubuntu16.04过程中所遇到的一些问题.例如WIFI禁用,外接屏显示错误,NVIDIA驱动安装等
- 天正索引号是什么lisp_天正电气CAD教程之符号篇(内附往期秘籍)
- 电子信息工程专业打工人的蓝桥杯单片机竞赛时记