一、系统架构设计
1、操作系统文件描述符限制
1、操作系统file-max修改:理论上系统内存有多少就可以打开多少的文件描述符,但是在实际中内核是会做相应的处理,一般最大打开文件数会是系统内存的10%(以KB来计算),称之为系统级限制。这个数字可以通过 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max 命令查看。
2、用户文件描述符file-nr修改:用户打开文件描述符默认是1024,文件描述符范围1-65535,这个数字可以通过 ulimit -n查看,可以在65535范围内调整。
3、单进程文件描述符nr_open修改:单进程文件描述符必须小于等于file-max,对于netty单进程要支持100万长连接,需要将这个值设置为100万,可以通过cat /proc/sys/fs/nr_open查看。
1、操作系统最大句柄数查询(机器内存越大,默认值越大)
$ cat /proc/sys/fs/file-max2、查询当前打开文件句柄数
$ cat /proc/sys/fs/file-nr
704(已经打开的句柄数) 0 100000(file-max数)3、手工修改file-max
$ vi /etc/sysctl.conf
fs.file-max = 1100000 #环境变量值修改大点
sudo sysctl -p #使环境变量生效
2、用户文件描述符限制
1、用户打开句柄限制(最大值65535)
$ ulimit -n
10242、用户句柄修改(user用户句柄软限制修改未30000,硬限制修改为65535,系统可以提前告警)
$ sudo vi /etc/security/limits.conf
user soft nofile 30000
user hard nofile 65535
3、单进程文件描述符限制
1、查看单进程打开文件句柄数
$ cat /proc/sys/fs/nr_open2、修改单进程打开文件句柄数
$ vim /etc/sysctl.conf
fs.nr_open = 1000000
4、端口监听范围
单个IP+端口最多只能打开65535个文件描述符,要实现100万长连接必须要监听多个端口。具体设置要求:file-max数量>=nr_open数量>=ulimit -n(65535)*端口数量。按照单用户打开文件描述符为5万算,100万连接需要开20个端口。
二、方案设计
1、netty百万链接核心代码
netty代码比较简单,核心就是开多端口逻辑。确立端口段前要检查这个端口是否有其他应用占用。具体程序可以设置为随机端口这样做更安全,也可以使用固定端口,好处是更明确。
try {//netty入口引导类ServerBootstrap b = new ServerBootstrap();//设置核心线程组和业务工作线程组b.group(bossGroup, workGroup);//快速复用端口,避免端口冲突,原理tcp连接需要2ML时间单位回收,这个配置加快进度b.option(ChannelOption.SO_REUSEADDR, true);//设置nio类型的channelb.channel(NioServerSocketChannel.class);//流量整形 读写最大流量100M/sGlobalTrafficShapingHandler globalTrafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroupForTrafficShaping, gatewayConfig.getNettyConfig().getTrafficShapingWriteLimit(), gatewayConfig.getNettyConfig().getTrafficShapingReadLimit());//netty日志级别,用于排查问题使用LoggingHandler debugLogHandler = new LoggingHandler(LogLevel.DEBUG);LoggingHandler infoLogHandler = new LoggingHandler(LogLevel.INFO);b.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//最大字符支持及分隔符pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(gatewayConfig.getNettyConfig().getMaxFrameLength(), Delimiters.lineDelimiter()));//编码类型pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));//心跳超时检测 及超时清除通道pipeline.addLast("heartBeatTimeOutCheckHandler", new HeartBeatTimeOutCheckHandler(gatewayConfig.getNettyConfig().getIdleTimeOut()));//心跳、读、写超时拦截pipeline.addLast("timeServerHandler", new TimeServerHandler());//在线设备统计pipeline.addLast("metricHandler", metricsHandler);//日志级别pipeline.addLast("debegLog", debugLogHandler);pipeline.addLast("infoLog", infoLogHandler);//流量整形pipeline.addLast("tsHandler", globalTrafficShapingHandler);//通道合法性校验pipeline.addLast("sessionAuthHandler",sessionAuthHandler); //心跳消息pipeline.addLast("heartBeatRespHandler",heartBeatRespHandler); //业务接收消息pipeline.addLast("businessHandler",businessServerHandler);pipeline.addLast(businessGroup, new OrderServerProcessHandler());}});/*** 监听端口范围* */for(int port = 4000;port<=4030;port++) {int portTemp = port;b.bind(port).sync().addListener(future -> {if (future.isSuccess()) {log.info("netty监听端口成功:{}", portTemp);} else {log.info("netty监听端口失败:{}", portTemp);}});}} catch (Exception e) {log.error("netty异常:{}",e.getMessage()); stopNetty();}
2、方案测试
百万长连接最难的是测试环节。通常压测使用jmeter,一般一台服务器也就1、5万线程,远远低于100万的要求。
1、Jmeter集群方案
应为我没有这样的集群,只能按照一台的情况推断,单台jmeter能支持5w长连接。预计百万链接需要20台Jmeter。
2、其他方案
那个大佬有巧妙的、资源低消耗的方案,麻烦留言介绍下。
三、操作系统参数优化
net.ipv4.tcp_max_tw_buckets = 10000
#表示系统同时保持TIME_WAIT套接字的最大数量net.ipv4.tcp_timestamps = 0
#关闭TCP时间戳
#以一种比重发超时更精确的方法(请参阅 RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项net.ipv4.tcp_tw_recycle = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。net.ipv4.tcp_tw_reuse = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;net.ipv4.tcp_retries2 = 1
#活动TCP连接重传次数,超过次数视为掉线,放弃连接。缺省值:15,建议设为 2或者3.net.ipv4.tcp_fin_timeout = 1
#FIN_WAIT状态的TCP连接的超时时间