實現單機的百萬鏈接,瓶頸有如下幾點:
一、如何模擬百萬鏈接
二、突破局部文件句柄的限制
三、突破全局文件句柄的限制
在linux系統裏面,單個進程打開的句柄數是很是有限的,一條TCP鏈接就對應一個文件句柄,而對於咱們應用程序來講,一個服務端默認創建的鏈接數是有限制的。java
以下圖所示,一般一個客戶端去除一些被佔用的端口以後,可用的端口大於只有6w個左右,要想模擬百萬鏈接要起比較多的客戶端,並且比較麻煩,因此這種方案不合適。linux
image.pngspring
在服務端啓動800~8100,而客戶端依舊使用1025-65535範圍內可用的端口號,讓同一個端口號,能夠鏈接Server的不一樣端口。這樣的話,6W的端口能夠鏈接Server的100個端口,累加起來就能實現近600W左右的鏈接,TCP是以一個四元組概念,以原IP、原端口號、目的IP、目的端口號來肯定的,當原IP 和原端口號相同,但目的端口號不一樣,最終系統會把他當成兩條TCP 鏈接來處理,因此TCP鏈接能夠如此設計。bootstrap
image.pngspringboot
netty客戶端 ,和netty服務端 都是springboot項目。
運行環境:linux
netty版本:4.1.6.Final服務器
netty maven
<properties>
<netty-all.version>4.1.6.Final</netty-all.version>
</properties>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>併發
@SpringBootApplication public class NettyserverApplication { private static final int BEGIN_PORT = 8000; private static final int N_PORT = 100; public static void main(String[] args) { SpringApplication.run(NettyserverApplication.class, args); new Server().start(BEGIN_PORT, N_PORT); } } /---------------------------------------------------------------------------------- import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public final class Server { public void start(int beginPort, int nPort) { System.out.println("server starting...."); EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup); bootstrap.channel(NioServerSocketChannel.class); bootstrap.childOption(ChannelOption.SO_REUSEADDR, true); bootstrap.childHandler(new ConnectionCountHandler()); /** * 綁定100個端口號 */ for (int i = 0; i < nPort; i++) { int port = beginPort + i; bootstrap.bind(port).addListener((ChannelFutureListener) future -> { System.out.println("bind success in port: " + port); }); } System.out.println("server started!"); } } /------------------------------------------------------------------------------------------------- import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @Sharable public class ConnectionCountHandler extends ChannelInboundHandlerAdapter { //jdk1.5 併發包中的用於計數的類 private AtomicInteger nConnection = new AtomicInteger(); public ConnectionCountHandler() { /** * 每兩秒統計一下鏈接數 */ Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { System.out.println("connections: " + nConnection.get()); }, 0, 2, TimeUnit.SECONDS); } /** * 每次過來一個新鏈接就對鏈接數加一 * @param ctx */ @Override public void channelActive(ChannelHandlerContext ctx) { nConnection.incrementAndGet(); } /** * 端口的時候減一 * @param ctx */ @Override public void channelInactive(ChannelHandlerContext ctx) { nConnection.decrementAndGet(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); Channel channel = ctx.channel(); if(channel.isActive()){ ctx.close(); } //…… } }
netty maven
<properties>
<netty-all.version>4.1.6.Final</netty-all.version>
</properties>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>jvm
@SpringBootApplication public class NettyclientApplication { private static final int BEGIN_PORT = 8000; private static final int N_PORT = 100; public static void main(String[] args) { SpringApplication.run(NettyclientApplication.class, args); new Client().start(BEGIN_PORT, N_PORT); } } //---------------------------------------------------------------------------------------- package com.nettyclient.test; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class Client { private static final String SERVER_HOST = "127.0.0.1"; public void start(final int beginPort, int nPort) { System.out.println("client starting...."); EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup); bootstrap.channel(NioSocketChannel.class); bootstrap.option(ChannelOption.SO_REUSEADDR, true); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { } }); int index = 0; int port; while (!Thread.interrupted()) { port = beginPort + index; try { ChannelFuture channelFuture = bootstrap.connect(SERVER_HOST, port); channelFuture.addListener((ChannelFutureListener) future -> { if (!future.isSuccess()) { System.out.println("鏈接失敗, 退出!"); System.exit(0); } }); channelFuture.get(); } catch (Exception e) { } if (++index == nPort) { index = 0; } } } }
啓動服務端socket
image.pngmaven
啓動客戶端
最大鏈接數一萬多.png
測試發現當鏈接數達到13136 的時候,此時達到了最大的鏈接數,這時候服務器將再也不對新的鏈接進行處理,客戶端贏長時間得不到服務端的響應而結束與服務端的鏈接。(不一樣的機器配置結果可能不一樣)
下面經過優化要突破這個鏈接數。
八月 25, 2018 9:29:41 上午 io.netty.channel.DefaultChannelPipeline onUnhandledInboundException
警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
image.png
一個jvm進程最大可以打開的文件數.png
修改65535的這個限制
vi /etc/security/limits.conf
在文件末尾添加兩行
*hard nofile 1000000
soft nofile 1000000
soft和hard爲兩種限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打開的最大文件數。總體表示任何用戶一個進程可以打開1000000個文件。注意語句簽名有 號 表示任何用戶image.png
shutdown -r now 重啓linux
再次查看image.png
已經修改生效了。
測試最大鏈接數10萬多.png
cat /proc/sys/fs/file-max
file-max 表示在linux 中最終全部x線程可以打開的最大文件數image.png
修改這個最大值:
sudo vi /etc/sysctl.conf
在文件的末尾添加 fs.file-max=1000000
而後讓文件生效 sudo sysctl -p
這個時候再查看一下全局最大文件句柄的數已經變成1000000了image.png
測試
最大鏈接數36萬多.png
注: 測試的服務器型號
image.png
cpu 相關配置
image.png
做者:s_j_x 連接:https://www.jianshu.com/p/490e2981545c 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。