突破netty單機(jī)最大連接數(shù)

實(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)連接要起比較多的客戶端,而且比較麻煩,所以這種方案不合適。


image.png

在服務(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ì)。


image.png

測(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ù)端


image.png

啟動(dòng)客戶端


最大連接數(shù)一萬(wàn)多.png

測(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.


image.png

優(yōu)化

1、局部文件句柄限制

一個(gè)jvm進(jìn)程最大能夠打開(kāi)的文件數(shù).png

修改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è)試

最大連接數(shù)36萬(wàn)多.png

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


image.png

cpu 相關(guān)配置


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評(píng)論 6 342
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風(fēng)情閱讀 1,861評(píng)論 0 3
  • 創(chuàng)建Maven Web項(xiàng)目 在Spring Tool Suite中創(chuàng)建Maven項(xiàng)目,生成web.xml文件 we...
    在努力中閱讀 664評(píng)論 0 0
  • 今天由于項(xiàng)目需要,簡(jiǎn)單的配置了一下ssm框架,maven配置所需包,可直接粘貼復(fù)制 maven依賴 <depend...
    胡GaQue閱讀 806評(píng)論 0 4

友情鏈接更多精彩內(nèi)容