侧边栏壁纸
博主头像
soulballad博主等级

技术文章记录及总结

  • 累计撰写 169 篇文章
  • 累计创建 26 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Netty性能调优

soulballad
2022-03-04 / 0 评论 / 0 点赞 / 132 阅读 / 10,902 字
温馨提示:
本文最后更新于 2022-03-09,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 操作系统参数调优

要实现百万级的长连接接入,首先需要对服务端的操作系统参数进行性能调优,如果保持出厂的默认配置,性能是无法满足业务需求的。

1.1 文件描述符

首先查看系统最大文件句柄数,执行命令 # cat /proc/sys/fs/file-max,查看最大句柄数是否满足需要,如果不满足,通过 # vim /ete/sysctl.conf 命令插入如下配置:

fs.file-max=1000000

配置完成后,执行 #sysctl -p 命令,让配置修改立即生效。
设置完系统最大文件句柄数,对单进程打开的最大句柄数进行设置。通过 ulimit -a 命令查看当前设置的值是否满足要求:

[root@mail ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15071
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 100001
// 后续省略

当并发接入的TCP连接数超过上限时,就会提示 “too many open files",所有新的客户端接入将失败。通过 # vim /etc/security/limits.conf 命令添加如下配置参数:

* soft nofile 1000000
* hard nofile 1000000

修改之后保存,注销当前用户,重新登录,通过 ulimit -a 命令查看修改是否生效。

1.2 TCP/IP相关参数

需要重点调优的TCP/IP参数如下:

  1. net.ipv4.tcp_rmem: 为每个TCP连接分配的读缓冲区内存大小。第一个值是socket接收缓冲区分配的最小字节数。第二个值是默认值,缓冲区在系统负载不高的情况下可以增长到该值。第三个值是接收缓冲区分配的最大字节数。
  2. net.ipv4.tcp_wmem:为每个TCP连接分配的写缓冲区内存大小。第一个值是socket发送缓冲区分配的最小字节数。第二个值是默认值, 缓冲区在系统负载不高的情况下可以增长到该值。第三个值是发送缓冲区分配的最大字节数。
  3. net.ipv4.tcp_mem: 内核分配给TCP连接的内存, 单位是page (1 个page通常为4096字节, 可以通过#getconf PAGESIZE命令查看), 包括最小、默认和最大三个配置项。
  4. net.ipv4.tcp_keepalive_time:最近-次 数据包发送与第一次 keep alive 探测消息发送的时间间隔, 用于确认TCP连接是否有效。
  5. tcp_keepalive_intvl:在未获得探测消息响应时, 发送探测消息的时间间隔。
  6. tcp_keepalive_probes: 判断TCP连接失效连续发送的探测消息个数, 达到之后判定连接失效。
  7. net.ipv4.tcp_tw_reuse: 是否允许将TIME_ WAIT Socket重新用于新的TCP连接, 默认为0, 表示关闭。
  8. net.ipv4.tcp_tw_recycle: 是否开启TCP连接中TIME_ _WAIT Socket的快速回收功能, 默认为0, 表示关闭。.
  9. net.ipv4.tcp_fin_timeout: 套接字自身关闭时保持在FIN_ WAIT_ 2状态的时间, 默认为60。

通过 #vi /et/sysctl.conf 命令对上述网络参数进行优化, 具体修改如下(大约可以接入50万个连接, 可以根据业务需要调整参数):

net.ipv4.tcp_mem = 64608 1048576 2097152
net.ipv4.tcp_wmem = 4096 87380 4194304
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.top_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_tw_reuse = 1
net.ipv4.top_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

修改完成后,通过执行 # sysctl-p 命令使配置立即生效。

1.3 多网卡队列和软中断

随着网络带宽的不断提升,单核CPU不能完全满足网卡的需求,通过多队列网卡驱动的支持,将各个队列通过中断绑定到不同的CPU内核,以满足对网络吞吐量要求比较高的业务场景的需要。

多队列网卡需要网卡硬件支持,首先判断当前系统是否支持多队列网卡,通过命令“Ispci -vvv”或者“ethtool-| 网卡interface 名”查看网卡驱动型号,根据网卡驱动官方说明确认当前系统是否支持多队列网卡(是否支持多队列网卡与网卡硬件、操作系统版本等有关)。有些网卡驱动默认开启了多队列网卡,有些则没有,由于不同的网卡驱动、云服务商提供的开启命令不同,因此需要根据实际情况处理,此处不再详细列举开启方式。

对于不支持多队列网卡的系统,如果内核版本支持RPS (kernel 2.6.35 及以上版本),开启RPS后可以实现软中断,提升网络的吞吐量。RPS根据数据包的源地址、目的地址及目的和源端口,算出一个hash值,然后根据这个hash值选择软中断运行的CPU,从上层来看,也就是说将每个连接和CPU绑定,并通过这个hash值,在多个CPU上均衡软中断,提升网络并行处理性能,它实际提供了一种通过软件模拟多队列网卡的功能。

2. Netty性能调优

2.1 设置合理的线程数

对于线程池的调优,主要集中在用于接收海量设备TCP连接、TLS握手的Acceptor线程池(Netty 通常叫boss NioEventLoopGroup) 上,以及用于处理网络数据读写、心跳发送的I/0工作线程池( Netty通常叫work NioEventL oopGroup)上。

Netty进阶之路:跟着案例学Netty对于Netty 服务端,通常只需要启动一个监听端口用于端侧设备接入即可,但是如果服务端集群实例比较少,甚至是单机(或者双机冷备)部署,在端侧设备在短时间内大量接入时,需要对服务端的监听方式和线程模型做优化,以满足短时间内(例如30s)百万级的端侧设备接入的需要。

对于I/O工作线程池的优化,可以先采用系统默认值(即CPU内核数X2)进行性能测试,在性能测试过程中采集I0线程的CPU占用大小,看是否存在瓶颈,具体策略如下。

  1. 通过执行 ps-ef|grep java 找到服务端进程pid
  2. 执行 top -Hp pid 查询该进程下所有线程的运行情况,通过 “shif + p” 对CPU占用大小做排序,获取线程的pid及对应的CPU占用大小。
  3. 使用 printf '%x\n' pid 将pid转换成16进制格式。
  4. 通过 jstack -f pid 命令获取线程堆栈,或者通过 jvisualvm 工具打印线程堆栈,找到 I/O work工作线程,查看它们的CPU占用大小及线程堆栈。

如果连续采集几次进行对比,发现线程堆栈都停留在Selectorlmpl.lockAndDoSelect处,则说明I/0线程比较空闲,无须对工作线程数做调整。

如果发现 I/O线程的热点停留在读或者写操作,或者停留在ChannelHandler的执行处,则可以通过适当调大NioEventLoop线程的个数来提升网络的读写性能。调整方式有两种。

  1. 接口API指定:在创建 NioEventLoopGroup 实例时指定线程数。
  2. 系统参数指定:通过 -Dio.netty.eventLoopThreads 来指定 NioEventLoopGroup 线程

2.2 心跳优化

针对海量设备接入的loT服务端,心跳优化策略如下。

  1. 要能够及时检测失效的连接,并将其剔除,防止无效的连接句柄积压,导致OOM等问题。
  2. 设置合理的心跳周期,防止心跳定时任务积压,造成频繁的老年代GC (新生代和老年代都有导致STW的GC,不过耗时差异较大),导致应用暂停。
  3. 使用Netty提供的链路空闲检测机制,不要自已创建定时任务线程池,加重系统的负担,以及增加潜在的并发安全问题。

当设备突然掉电、连接被防火墙挡住、长时间GC或者通信线程发生非预期异常时,会导致链路不可用且不易被及时发现。特别是如果异常发生在凌晨业务低谷期间,当早晨业务高峰期到来时,由于链路不可用会导致瞬间大批量业务失败或者超时,这将对系统的可靠性产生重大的威胁。

从技术层面看,要解决链路的可靠性问题,必须周期性地对链路进行有效性检测。目前最流行和通用的做法就是心跳检测。心跳检测机制分为三个层面。

  1. TCP层的心跳检测,即TCP的Keep-Alive机制,它的作用域是整个TCP协议栈。
  2. 协议层的心跳检测,主要存在于长连接协议中,例如MQTT。
  3. 应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。

心跳检测的目的就是确认当前链路是否可用,对方是否活着并且能够正常接收和发送消息。作为高可靠的NIO框架,Netty也提供了心跳检测机制。

不同协议的心跳检测机制存在差异,归纳起来主要分为两类:

  1. Ping-Pong型心跳:由通信一方定时发送Ping消息,对方接到Ping消息立即返回Pong应答消息给对方,属于“请求-响应型”心跳。
  2. Ping-Ping型心跳:不区分心跳请求和应答,由通信双方按照约定定时向对方发送心跳Ping消息,它属于双向心跳。

心跳检测策略如下:

  1. 连续N次心跳检测都没有收到对方的Pong应答消息或者Ping请求消息,则认为链路已经发生逻辑失效,这被称为心跳超时。
  2. 在读取和发送心跳消息的时候如果直接发生了I/O 异常,说明链路已经失效,这被称为心跳失败。无论发生心跳超时还是心跳失败,都需要关闭链路,由客户端发起重连操作,保证链路能够恢复正常。

Netty提供了三种链路空闲检测机制,利用该机制可以轻松地实现心跳检测。

  1. 读空闲,链路持续时间T没有读取到任何消息。
  2. 写空闲,链路持续时间T没有发送任何消息。
  3. 读写空闲,链路持续时间T没有接收或者发送任何消息。

链路空闲事件被触发后并没有关闭链路,而是触发IdleStateEvent 事件,用户订阅IdleStateEvent事件,用于自定义逻辑处理,例如关闭链路、客户端发起重新连接、告警和打印日志等。

利用Netty提供的链路空闲检测机制,可以非常灵活地实现协议层的心跳检测。如果选择双向心跳,在初始化Channel时将Netty的IdleStateHandler实例添加到ChannelPipeline中,然后监听READER_IDLE 事件,一旦READER_IDLE 事件发生,说明周期T内没有读取到设备的消息,触发服务端主动发送心跳,检测链路是否存活,如果发生I/O异常说明链路已经失效,则主动关闭链路:如果发送成功,则等待最终的心跳超时,即在连续N个周期T内都没有接收到端侧设备发送的业务数据或者心跳消息,则说明端侧设备已经发生故障,服务端主动关闭连接,释放资源。

采用双向心跳检测的主要优点有两个:

  1. 可以及时识别网络单通、对方突然掉电等特殊异常。
  2. 可以识别对方是否能够正常工作,而不仅是网络层面的互通性检测。
    除了IdleStateHandler,也可以根据实际需要选择ReadTimeoutHandler 或者 WriteTimeoutHandler等。

对于IOT场景,不建议过长的心跳检测周期和超时机制。主要有如下几点考虑:

  1. 百万级的长连接就有百万级的定时器,这么庞大的定时器会占用大量内存,如果长时间存活,会被“晋升”到老年代,加重CMS等老年代垃圾收集器的负担,容易导致STW问题。
  2. 过长的心跳检测超时不能及时发现掉线的设备(例如突然掉电),导致大量无效的TCP连接在内存中积压,同时占用操作系统句柄,影响性能,也容易导致OOM异常。

在创建IdleStateHlandler实例时,可以指定空闲检测时间

2.3 接收和发送缓冲区调优

在一些场景下,端侧设备会周期性地上报数据和发送心跳,单个链路的消息收发量并不大,针对此类场景,可以通过调小TCP的接收和发送缓冲区来降低单个TCP连接的资源占用率,例如将收发缓冲区设置为8KB,相关代码如下:

.childHandler(new ChannelInitializer<NioServerSocketChannel>() {
    @Override
    protected void initChannel(NioServerSocketChannel ch) throws Exception {

    }
})
    .childOption(ChannelOption.SO_RCVBUF, 8 * 1024)
    .childOption(ChannelOption.SO_SNDBUF, 8 * 1024);

需要指出的是,对于不同的应用场景,收发缓冲区的最优值可能不同,用户需要根据实际场景,结合性能测试数据进行针对性的调优。

2.4 合理使用内存池

随着JVM虚拟机和JIT即时编译技术的发展,对象的分配和回收是一个非常轻量级的工作。但是对于缓冲区Buffer, 情况却稍有不同,特别是堆外直接内存的分配和回收,是一个耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。

在物联网场景下,需要为每个接入的端侧设备至少分配一个接收和发送缓冲区对象,采用传统的非池模式,每次消息读写都需要创建和释放ByteBuf对象,如果有100万个连接,每秒上报一次数据或者心跳,就会有100万次/秒的ByteBuf对象申请和释放,即便
务端的内存可以满足要求,GC的压力也会非常大。

以上问题最有效的解决方法就是使用内存池,每个NioEventLoop线程处理N个链路,在线程内部,链路的处理是串行的。假如A链路首先被处理,它会创建接收缓冲区等对象,待解码完成,构造的POJO对象被封装成任务后投递到后台的线程池中执行,然后接收缓冲区会被释放,每条消息的接收和处理都会重复接收缓冲区的创建和释放。如果使用内存池,则当A链路接收到新的数据报时,从NioEventLoop的内存池中申请空闲的ByteBuf,解码后调用release将ByteBuf释放到内存池中,供后续的B链路使用。

Netty内存池从实现上可以分为两类:堆外直接内存和堆内存。由于ByteBuf 主要用于网络I0读写,因此采用堆外直接内存会减少一次从用户堆内存到内核态的字节数组拷贝,所以性能更高。由于DirectByteBuf的创建成本比较高,因此如果使用DirectByteBuf,则需要配合内存池使用,否则性价比可能还不如HeapByteBuf。

Netty默认的I/O 读写操作采用的都是内存池的堆外直接内存模式,如果用户需要额外使用ByteBuf,建议也采用内存池方式:如果不涉及网络I/O操作( 只是纯粹的内存操作),可以使用堆内存池,这样内存的创建效率会更高一些。

尽量使用内存池,涉及网络IO使用堆外直接内存池,不涉及使用堆内存池

由于堆外直接内存定位内存泄漏等问题不太方便,有时候需要在测试环境将内存分配策略调整为堆内存模式,待问题解决后再切换到堆外直接内存。假如将消息读取ByteBuf设置为非内存池、堆内存模式,代码示例如下:

//代码省略
.childHandler(new ChannelInitializer<SocketChanne1>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.config().setAllocator(UnpooledByteBufAllocator.DEFAULT) ;
        ChannelPipeline p = ch.pipeline();
        //后续代码省略
    }
}

设置 -Dio.netty.noUnsafe=true,使用Heap堆内存方式创建 ByteBuf

2.5 防止I/O线程被意外阻塞

通常情况下,大家都知道不能在Netty的I/O线程上做执行时间不可控的操作,例如访
问数据库、调用第三方服务等。但是有些隐形的阻塞操作却容易被忽略,例如打印日。

在生产环境中,通常需要实时打印接口日志,其他日志处于ERROR级别,当服务发生I/O 异常时,会记录异常日志。如果当前磁盘的WIO比较高,写日志文件操作可能会被同步阻塞(阻塞时间无法预测)。这就会导致Netty的NioEventLoop线程被阻塞,Socket链路无法被及时关闭,其他的链路也无法进行读写操作。

以最常用的log4j (1.2.X版本)为例,尽管它支持异步写日志(AsyncAppender),但是当日志队列满时,它会同步阻塞业务线程(采用等待非丢弃方式时),直到日志队列有空闲位置可用,相关代码如下:

synchronized (buffer) {
    while (true) {
        int previousSize = buffer.size();

        if (previousSize < bufferSize) {
            buffer.add(event);
            if (previousSize == 0) {
                buffer.notifyAll();
            }

            break;
        }

        boolean discard = true;
        if (blocking && !Thread.interrupted() 
            && Thread.currentThread() != dispatcher) { // 判断是不是业务线程
            try {
                buffer.wait(); // 阻塞业务线程
                discard = false;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        // 后续省略...
    }
}

类似问题具有极强的隐蔽性,往往WIO高的时间持续非常短,或者是偶现的,在测试环境中很难模拟此类故障,问题定位难度非常大。一旦在生产环境中出现问题,在测试环境中又无法重现,就会比较被动。

2.6 I/O线程和业务线程分离

如果服务端不做复杂的业务逻辑操作,仅是简单的内存操作和消息转发,则可以通过调大NioEventLoop工作线程池的方式,直接在I/O线程中执行业务ChannelHandler,这样便减少了一次线程上下文切换,性能反而更高。

如果有复杂的业务逻辑操作,则建议I/O 线程和业务线程分离,对于I/O线程,由于互相之间不存在锁竞争,可以创建一个大的NioEventLoopGroup线程组,所有Channel都共享同一个线程池。对于后端的业务线程池,则建议创建多个小的业务线程池,线程池可以与 I/O 线程绑定,这样既减少了锁竞争,又提升了后端的处理性能。

2.7 针对端侧并发连接数的流控

无论服务端的性能优化到多少,都需要考虑流控功能。当资源成为瓶颈,或者遇到端侧设备的大量接入,需要通过流控对系统做保护。流控的策略有很多种,loT 场景最重要的就是针对端侧连接数的流控。

在Netty中,可以非常方便地实现流控功能:新增一个FlowControlChannelHandler,添加到ChannelPipeline靠前的位置,继承channelActive() 方法,创建TCP链路后,执行流控逻辑,如果达到流控阈值,则拒绝该连接,调用 ChannelHandlerContext 的 close() 方法关闭连接。

TLS/SSL的连接数的流控相对复杂一 些,可以在TLS/SSL握手成功后,监听握手成功的事件,执行流控逻辑。握手成功后发送 SslHandshakeCompletionEvent 事件,代码示例如下(SslHandler 类):

private void setHandshakeSuccess() {
    handshakePromise.trySuccess(ctx.channel());

    if (logger.isDebugEnabled()) {
        SSLSession session = engine.getSession();
        logger.debug(
            "{} HANDSHAKEN: protocol:{} cipher suite:{}",
            ctx.channel(),
            session.getProtocol(),
            session.getCipherSuite());
    }
    ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS);

    if (readDuringHandshake && !ctx.channel().config().isAutoRead()) {
        readDuringHandshake = false;
        ctx.read();
    }
}

FlowControlChannelHandler继承userEventTriggered0方法,拦截TLS/SSL握手成功事件,执行流控逻辑,示例代码如下:

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if(evt == SslHandshakeCompletionEvent.SUCCESS){
        // 执行流控逻辑
    }
}

3. JVM相关性能优化

JVM层面的调优主要涉及GC参数优化,GC参数设置不当会导致频繁GC,甚至OOM异常,对服务端的稳定运行产生重大影响。

3.1 GC调优

3.1.1 确定GC优化目标

GC (垃圾收集)有三个主要指标:

  1. 吞吐量:是评价GC能力的重要指标,在不考虑GC引起的停顿时间或内存消耗时,吞吐量是GC能支撑应用程序达到的最高性能指标。
  2. 延迟: GC能力的最重要指标之一,是由于GC引起的停顿时间,优化目标是缩短延迟时间或完全消除停顿(STW),避免应用程序在运行过程中发生抖动。
  3. 内存占用: GC正常时占用的内存量。

JVM GC调优的三个基本原则如下。

  1. Minor GC回收原则:每次新生代GC回收尽可能多的内存,减少应用程序发生Full GC的频率。
  2. GC内存最大化原则:垃圾收集器能够使用的内存越大,垃圾收集效率越高,应用程序运行也越流畅。但是过大的内存一次Full GC耗时可能较长,如果能够有效避免Full GC,就需要做精细化调优。
  3. 3选2原则:吞吐量、延迟和内存占用不能兼得,无法同时做到吞吐量和暂停时间都最优,需要根据业务场景做选择。对于大多数loT应用,吞吐量优先,其次是延迟。
    当然对于时延敏感型的业务,需要调整次序。

3.1.2 确定服务端内存占用

在优化GC之前,需要确定应用程序的内存占用大小,以便为应用程序设置合适的内存,提升GC效率。内存占用与活跃数据有关,活跃数据指的是应用程序稳定运行时长时间存活的Java对象。活跃数据的计算方式:通过GC日志采集GC数据,获取应用程序稳定时老年代占用的Java堆大小,以及永久代(元数据区)占用的Java堆大小,两者之和就是活跃数据的内存占用大小。

3.1.3 GC数据的采集

GC数据的采集有两种方式:

  1. 通过-XX:+PrintGC、-XX:+PrintGCDetails 和-XX:+PrintGCDateStamps等参数打印GC日志,通过GC日志采集数据
  2. 通过Visual GC工具监控GC数据
    如果应用程序没有或者很少发生Full GC,则可以通过jvisualvm等工具人工触发Full GC

3.1.4 Java堆大小设置原则

空间VM命令行参数占用比例
Java堆-Xms|-Xmx3~4倍Full GC后的老年代空间占用
新生代-Xmn1~1.5倍Full GC后的老年代空间占用
老年代堆大小新生代大小2~3倍Full GC后的老年代空间占用
永久代-XX:PermSiz/-XX: MaxPermSize12~15倍Full GC后的水久代空间占用

上述经验数据已经可以满足大多数应用场景的需要,如果业务场景比较特殊,可以根据GC统计数据和应用性能测试结果进行相应的优化。

3.1.5 垃圾收集器的选择

如果是JDK8及以上版本,建议选择G1收集器,如果是较低版本的JDK,或者业务已经积累了一些优化的GC参数,则可以继续使用“ParNew收集器+CMS收集器”组合,根据测试情况和对应垃圾收集器的特点做相应的调优。

CMS吞吐量调优的主要策略如下:

  1. 增加新生代空间,降低新生代GC频率,减少固定时间内新生代GC的次数。
  2. 增加老年代空间,降低CMS的频率并减少内存碎片,最终减小并发模式失效引起FullGC发生的概率。
  3. 调整新生代Eden和Survivor空间的大小比例,减少由新生代晋升到老年代的对象数目,降低CMS GC频率。

G1调优的策略如下:

  1. 不要使用-Xmn选型或者-XX:NewRatio等其他相关选型显式设置年轻代的大小,这样会覆盖暂停时间指标。
  2. 暂停时间不要设置得太小,否则为了达到暂停时间目标会增加垃圾回收的开销,影响吞吐量指标。
  3. 防止触发Full GC: 在某些情况下,例如并发模式失败,G1会触发Full GC,这时G1会退化使用Serial 收集器来完成垃圾清理工作,它仅使用单线程来完成GC,GC暂停时间可能会达到秒级。

3.1.6 一些GC调优误区

在实际项目中,针对GC相关的一些认知或者做法存在误区,总结如下:

  1. 在生产环境中不配置GC日志打印参数,担心影响业务性能。
  2. GC日志格式选择-XX:+PrintGCTimeStamps,导致GC日志很难跟其他业务日志对应起来。
  3. GC日志文件路径设置为静态路径,例如gc.log,没有配置绕接、切换策略,导致重启之后日志被覆盖。
  4. GC日志文件没有配置单个文件大小、绕接和备份机制,导致单个GC文件过大。
  5. 只有FullGC才会导致应用暂停,分析STW问题时直接使用Full GC关键字搜索,其他的不看。
  6. 给出最优的GC参数或者明确GC优化方向之后,通过一次调整就能解决问题。
  7. 业务内存使用不当导致的性能问题,希望通过GC参数优化解决问题,业务不用改代码。
  8. 只有GC才会导致应用暂停。

3.2 其他优化手段

在实际测试时,往往需要先在测试环境模拟海量端侧设备的接入,为了方便测试,节约机器资源,需要在客户端配置虛拟IP:

# ifconfig eth0:1 XX.XX.XX.XX netmask 255.255.255.0

配置完成后通过 ifconfig eth0:1 查看IP与子网掩码是否正确,如果没有问题,通过 ifconfg eth0:1 up启动新增的虚拟IP进行测试。
操作系统对单个IP是有连接数限制的,每个IP对应的端口范围为0~65535,其中0~1023被系统占用,所以连接能够分配的端口从1024开始,考虑到其他进程的端口占用,单个IP能够接入的连接数约为6万个。如果系统支持多网卡,则可以采用多网卡、多IP的方式解决,否则需要使用虚拟IP的方式解决连接数限制问题。

0

评论区