實(shí)現(xiàn)單機(jī)的百萬(wàn)連接,瓶頸有以下幾點(diǎn):
1、如何模擬百萬(wàn)連接
2、突破局部文件句柄的限制
3、突破全局文件句柄的限制
在linux系統(tǒng)里面,單個(gè)進(jìn)程打開(kāi)的句柄數(shù)是非常有限的,一條TCP連接就對(duì)應(yīng)一個(gè)文件句柄,而對(duì)于我們應(yīng)用程序來(lái)說(shuō),一個(gè)服務(wù)端默認(rèn)建立的連接數(shù)是有限制的。
如下圖所示,通常一個(gè)客戶端去除一些被占用的端口之后,可用的端口大于只有6w個(gè)左右,要想模擬百萬(wàn)連接要起比較多的客戶端,而且比較麻煩,所以這種方案不合適。

在服務(wù)端啟動(dòng)800~8100,而客戶端依舊使用1025-65535范圍內(nèi)可用的端口號(hào),讓同一個(gè)端口號(hào),可以連接Server的不同端口。這樣的話,6W的端口可以連接Server的100個(gè)端口,累加起來(lái)就能實(shí)現(xiàn)近600W左右的連接,TCP是以一個(gè)四元組概念,以原IP、原端口號(hào)、目的IP、目的端口號(hào)來(lái)確定的,當(dāng)原IP 和原端口號(hào)相同,但目的端口號(hào)不同,最終系統(tǒng)會(huì)把他當(dāng)成兩條TCP 連接來(lái)處理,所以TCP連接可以如此設(shè)計(jì)。

測(cè)試環(huán)境
netty客戶端 ,和netty服務(wù)端 都是springboot項(xiàng)目。
運(yùn)行環(huán)境:linux
netty版本:4.1.6.Final
netty服務(wù)端代碼
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個(gè)端口號(hào)
*/
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 并發(fā)包中的用于計(jì)數(shù)的類
private AtomicInteger nConnection = new AtomicInteger();
public ConnectionCountHandler() {
/**
* 每?jī)擅虢y(tǒng)計(jì)一下連接數(shù)
*/
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
System.out.println("connections: " + nConnection.get());
}, 0, 2, TimeUnit.SECONDS);
}
/**
* 每次過(guò)來(lái)一個(gè)新連接就對(duì)連接數(shù)加一
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
nConnection.incrementAndGet();
}
/**
* 端口的時(shí)候減一
* @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客戶端代碼
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 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;
}
}
}
}
測(cè)試
啟動(dòng)服務(wù)端

啟動(dòng)客戶端

測(cè)試發(fā)現(xiàn)當(dāng)連接數(shù)達(dá)到13136 的時(shí)候,此時(shí)達(dá)到了最大的連接數(shù),這時(shí)候服務(wù)器將不再對(duì)新的連接進(jìn)行處理,客戶端贏長(zhǎng)時(shí)間得不到服務(wù)端的響應(yīng)而結(jié)束與服務(wù)端的連接。(不同的機(jī)器配置結(jié)果可能不同)
下面通過(guò)優(yōu)化要突破這個(gè)連接數(shù)。
八月 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.

優(yōu)化
1、局部文件句柄限制

修改65535的這個(gè)限制
vi /etc/security/limits.conf
在文件末尾添加兩行
*hard nofile 1000000
soft nofile 1000000
soft和hard為兩種限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打開(kāi)的最大文件數(shù)。整體表示任何用戶一個(gè)進(jìn)程能夠打開(kāi)1000000個(gè)文件。注意語(yǔ)句簽名有 號(hào) 表示任何用戶
image.png
shutdown -r now 重啟linux
再次查看
image.png
已經(jīng)修改生效了。
測(cè)試
最大連接數(shù)10萬(wàn)多.png
2、突破全局文件句柄的限制
cat /proc/sys/fs/file-max
file-max 表示在linux 中最終所有x線程能夠打開(kāi)的最大文件數(shù)
image.png
修改這個(gè)最大值:
sudo vi /etc/sysctl.conf
在文件的末尾添加 fs.file-max=1000000
然后讓文件生效 sudo sysctl -p
這個(gè)時(shí)候再查看一下全局最大文件句柄的數(shù)已經(jīng)變成1000000了
image.png
測(cè)試

注: 測(cè)試的服務(wù)器型號(hào)

cpu 相關(guān)配置





