单机百万连接调优和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要求在本地测试中要模拟出百万用户同时在线的需求.虽然该产品最后不一定有这么 ...
随机推荐
- session.flush与transaction.commit
以session的save方法为例来看一个简单.完整的事务流程,如下是代码片段: ---------------------------- Session session = sessionFacto ...
- [题解] SPOJ GSS1 - Can you answer these queries I
[题解] SPOJ GSS1 - Can you answer these queries I · 题目大意 要求维护一段长度为 \(n\) 的静态序列的区间最大子段和. 有 \(m\) 次询问,每次 ...
- Promise.race()
Promise.race([ ])---race竞赛,只要有一个决议了,就返回一个promise实例(对应resolve()或reject( )中参数值: 1.与Promise.all()对应的,还有 ...
- 那些优秀的python代码
时间:2019-04-18 收藏:PangYuaner 标题:Python如何生成树形图案 地址:https://www.jb51.net/article/132049.htm 标题:用python- ...
- springboot系列总结(二)---springboot的常用注解
上一篇文章我们简单讲了一下@SpringBootApplication这个注解,申明让spring boot自动给程序进行必要的配置,他是一个组合注解,包含了@ComponentScan.@Confi ...
- D3之svg transform 与 css3 transform 区别与联系
D3就不用多介绍了,在数据可视化界属于大佬级别的js库.在这里主要想记录一下在写程序期间遇到的一个问题. 如下图所示,想完成主视图在小地图上的映射,小地图的白色矩形框用来代表当前主视图可见区域,主视图 ...
- C++模板简介
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数.返回值取得任意类型. 模板是一种对类型进行参数化的工具: 通常有两种形式:函 ...
- java交互Scanner类
用next方法接收 import java.util.Scanner; public class Demo01 { public static void main(String[] args) { / ...
- 羽夏笔记——PE结构(不包含.Net)
写在前面 本笔记是由本人独自整理出来的,图片来源于网络.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你 ...
- Java HashMap工作原理:不仅仅是HashMap
前言: 几乎所有java程序员都用过hashMap,但会用不一定会说. 近年来hashMap是非常常见的面试题,如何为自己的回答加分?需要从理解开始. "你用过hashMap吗?" ...