单机百万连接调优和Netty应用级别调优
作者:Grey
原文地址:单机百万连接调优和Netty应用级别调优
说明
本文为深度解析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 files
at 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>() {
@Override
protected 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();
@Override
protected 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;
}
@Override
public 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>() {
@Override
protected 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);
@Override
protected 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>() {
@Override
protected 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
.....
更多优化建议
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应用级别调优的更多相关文章
- 高性能C++网络库libtnet实践:comet单机百万连接挂载测试
最近在用go语言做一个挂载大量长连接的推送服务器,虽然已经完成,但是内存占用情况让我不怎么满意,于是考虑使用libtnet来重新实现一个.后续我会使用comet来表明推送服务器. 对于comet来说, ...
- 单机c1000k连接
单机c1000k连接即单机实现百万连接,首先要注意的是连接是虚拟的逻辑的,连接最终落于 网卡,清晰概念才能更深入更清晰的想出问题的解决办法. 参考 http://www.ideawu.net/blog ...
- [Spark性能调优] 第一章:性能调优的本质、Spark资源使用原理和调优要点分析
本課主題 大数据性能调优的本质 Spark 性能调优要点分析 Spark 资源使用原理流程 Spark 资源调优最佳实战 Spark 更高性能的算子 引言 我们谈大数据性能调优,到底在谈什么,它的本质 ...
- spark 性能调优(一) 性能调优的本质、spark资源使用原理、调优要点分析
转载:http://www.cnblogs.com/jcchoiling/p/6440709.html 一.大数据性能调优的本质 编程的时候发现一个惊人的规律,软件是不存在的!所有编程高手级别的人无论 ...
- 架构师养成记--22.客户端与服务器端保持连接的解决方案,netty的ReadTimeoutHandler
概述 保持客户端与服务器端连接的方案常用的有3种 1.长连接,也就是客户端与服务器端一直保持连接,适用于客户端比较少的情况. 2.定时段连接,比如在某一天的凌晨建立连接,适用于对实时性要求不高的情况. ...
- 开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石
微信于2013年开源的ibco库,是微信后台大规模使用的c/c++协程库,2013年至今稳定运行在微信后台的数万台机器上.libco在2013年的时候作为腾讯六大开源项目首次开源,ibco支持后台敏捷 ...
- netty百万连接跟踪记录
0. 启动客户端和服务端 # 测试环境: centos7 jdk8 2核16G# 服务端启动nohup java -Xmx8192m -Xms4096m -XX:+UseG1GC -XX:Parall ...
- Netty SSL性能调优
TLS算法组合 在TLS中,5类算法组合在一起,称为一个CipherSuite: 认证算法 加密算法 消息认证码算法 简称MAC 密钥交换算法 密钥衍生算法 比较常见的算法组合是 TLS_ECDHE_ ...
- Nginx 单机百万QPS环境搭建
一.背景 最近公司在做一些物联网产品,物物通信用的是MQTT协议,内部权限与内部关系等业务逻辑准备用HTTP实现.leader要求在本地测试中要模拟出百万用户同时在线的需求.虽然该产品最后不一定有这么 ...
随机推荐
- HttpClient4.3教程 第三章 Http状态管理
最初,Http被设计成一个无状态的,面向请求/响应的协议,所以它不能在逻辑相关的http请求/响应中保持状态会话.由于越来越多的系统使用http协议,其中包括http从来没有想支持的系统,比如电子商务 ...
- 阿里云(CentOS)搭建MediaWiki
搭建环境 系统:CentOS 7.3 PHP:5.4.16 Mysql:maria MediaWiki:1.26.4(更高版本不再支持PHP5.4) 下面我们开始安装LAMP环境: 1.安装Apach ...
- asp语言中if判断语句的求助
If a < 5 Then Response.Redirect("1.asp")ElseIf a > 5 And a < 8 Then Response. ...
- mybatis第一个程序随笔
今天继续学习了解如何写一个mybatis程序 创建了Dao层 1.1 创建一个UserDao接口 1.2 创建UserMapper.xml文件 在mybaits中文手册查找配置信息 <?xml ...
- Springboot 日志、配置文件、接口数据如何脱敏?老鸟们都是这样玩的!
一.前言 核心隐私数据无论对于企业还是用户来说尤其重要,因此要想办法杜绝各种隐私数据的泄漏.下面陈某带大家从以下三个方面讲解一下隐私数据如何脱敏,也是日常开发中需要注意的: 配置文件数据脱敏 接口返回 ...
- Mysql force index和ignore index 使用实例
前几天统计一个sql,是一个人提交了多少工单,顺便做了相关sql优化.数据大概2000多w. select CustName,count(1) c from WorkOrder where Creat ...
- Windows系统定时备份MySQL数据库
当一个网站投入使用时,定期备份数据库是必要的事.那么,在Windows系统上,我们该如何做呢? 如下语句可以实现备份及还原MySQL数据库: 备份MySQL数据库 mysqldump -uroot - ...
- Django——cookie保持登录
普通登录时,无法保持登录的状态,每一次请求时都需要重新登录. 而在登录时,生成cookie并保存在浏览器中,这样每次登录就会携带登录信息,就可以保持登录状态了. 操作cookie语法: # (1) 设 ...
- 物理机burp抓虚拟机包
先设置网络连接为NAT模式. 这就相当于主机虚拟出一个网卡,虚拟机单独成为一个网段(相当于虚拟机为单独一台主机,物理机作为路由器网关使用),我将会在物理机,也就是这个"路由器"上设 ...
- UVA 506 System Dependencies(模拟 烂题)
https://vjudge.net/problem/UVA-506 题目是给出了五种指令,DEPEND.INSTALL.REMOVE.LIST.END,操作的格式及功能如下: DEPEND item ...