字节面试:如何让单机下Netty支持百万长连接?

最近有同学在面试遇到了一道非常有深度的面试题:

如何让单机下Netty支持百万长连接?

当时在群里问小北,我发现我也没有系统化的梳理过这个问题,所以一时也没有回答的特别好。

痛定思痛的我赶紧去各种搜集资料,系统化的整理了下这道面试题该怎么回答,能让面试官眼前一亮,直呼内行。

首先我们需要先确认一点:

单机下能不能让我们的网络应用支持百万连接?

答案是:可以是可以,但是需要我们做很多工作。

插播一条:真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

一、操作系统层面的优化

首先就是要突破操作系统的限制

在Linux平台上,无论是编写客户端程序还是服务端程序,当处理高并发TCP连接时,最大的并发数量通常受到系统对单个用户进程可同时打开文件数量的限制。

这是因为系统为每个TCP连接创建一个socket句柄,而每个socket句柄也是一个文件句柄。

查看和修改文件句柄限制
你可以使用 ulimit 命令查看系统允许当前用户进程打开的文件句柄数限制:

$ ulimit -n

1024 这表示当前用户的每个进程最多允许同时打开1024个句柄。

然而,考虑到标准输入、标准输出、标准错误、服务器监听socket、进程间通信的Unix域socket等文件,实际可用于客户端socket连接的文件数大约为1014个左右。

因此,默认情况下,基于Linux的通信程序最多允许1014个TCP并发连接。

修改单个进程的最大文件数限制
要支持更多的TCP并发连接,可以修改当前用户进程可同时打开的文件数量。最简单的办法是使用 ulimit 命令:

$ ulimit -n 1000000

如果系统回显 “Operation not permitted” 之类的错误,说明上述修改失败。

这是因为指定的数值超过了Linux系统对该用户打开文件数的软限制或硬限制。

因此,需要修改系统对用户的软限制和硬限制。

修改软限制和硬限制

  1. 软限制 (soft limit):指在系统能够承受的范围内进一步限制一个进程同时打开的文件数。

  2. 硬限制 (hard limit):根据系统硬件资源(主要是内存)计算出的系统最多可同时打开的文件数量。

第一步:修改 /etc/security/limits.conf 文件

在文件中添加如下行:

* soft nofile 1000000
* hard nofile 1000000

表示修改所有用户的限制;soft 表示警告限制,hard 表示真正限制,nofile 表示最大打开文件数。软限制值应小于或等于硬限制值。保存文件。

第二步:修改 /etc/pam.d/login 文件

在文件中添加如下行:

session required /lib/security/pam_limits.so

这行配置告诉Linux在用户登录后,调用 pam_limits.so 模块来设置系统对该用户的资源数量限制(包括最大文件数限制)。

pam_limits.so 模块从 /etc/security/limits.conf 文件中读取配置来设置这些限制值。保存文件。

第三步:查看和修改系统级最大打开文件数限制

使用如下命令查看系统级的最大打开文件数限制:

$ cat /proc/sys/fs/file-max

12158 这表明该Linux系统最多允许同时打开(所有用户的文件数总和)12158个文件,这是系统级硬限制。

用户级的文件数限制不应超过这个值。

若需修改此限制,编辑 /etc/sysctl.conf 文件:

vi /etc/sysctl.conf

在末尾添加:

fs.file_max = 1000000

使其立即生效:

sysctl -p

插播一条:真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

Netty调优指南

设置合理的线程数

对于线程池的调优,主要集中在用于接收海量设备TCP连接、TLS握手的Acceptor线程池(Netty中称为boss NioEventLoopGroup)和用于处理网络数据读写、心跳发送的I/O工作线程池(Netty中称为work NioEventLoopGroup)。

服务端监听端口和线程模型优化

对于Netty服务端,通常只需启动一个监听端口用于设备接入。如果服务端实例较少,甚至是单机或双机冷备部署,当大量设备在短时间内接入时,需要对服务端的监听方式和线程模型进行优化,以满足短时间内(例如30秒)百万级的设备接入需求。

服务端可以监听多个端口,利用主从Reactor线程模型进行接入优化,前端通过SLB做4层或7层负载均衡。

主从Reactor线程模型的特点如下

1、服务端用于接收客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO线程池。

2、Acceptor接收到客户端TCP连接请求并处理后(可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(subReactor线程池)的某个I/O线程,由其负责SocketChannel的读写和编解码工作。

3、Acceptor线程池仅用于客户端的登录、握手和安全认证等。一旦链路建立成功,就将链路注册到后端subReactor线程池的I/O线程,由I/O线程负责后续的I/O操作。

对于I/O工作线程池的优化,可以先采用系统默认值(即CPU内核数×2)进行性能测试。

在性能测试过程中采集I/O线程的CPU占用情况,观察是否存在瓶颈。如果连续采集几次线程堆栈,发现线程堆栈停留在SelectorImpl.lockAndDoSelect,则说明I/O线程比较空闲,无需对工作线程数做调整。如果发现I/O线程的热点停留在读或写操作,或ChannelHandler的执行处,则可以适当增加NioEventLoop线程的数量来提升网络的读写性能。

心跳优化

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

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

当设备突然掉电、连接被防火墙挡住、长时间GC或通信线程发生非预期异常时,会导致链路不可用且不易被及时发现。

特别是在凌晨业务低谷期间,如果异常发生,当早晨业务高峰期到来时,由于链路不可用会导致瞬间大批量业务失败或超时,严重影响系统可靠性。

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

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

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

  1. 连续N次心跳检测都没有收到对方的Pong应答消息或Ping请求消息,则认为链路已经发生逻辑失效,这被称为心跳超时。
  2. 在读取和发送心跳消息时如果直接发生了I/O异常,说明链路已经失效,这被称为心跳失败。

无论发生心跳超时还是心跳失败,都需要关闭链路,由客户端发起重连操作,保证链路恢复正常。Netty提供了三种链路空闲检测机制,利用该机制可以轻松实现心跳检测:

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

对于百万级的服务器,一般不建议设置很长的心跳周期和超时时长。

接收和发送缓冲区调优

在一些场景下,端侧设备会周期性地上报数据和发送心跳,单个链路的消息收发量并不大。针对这种场景,可以通过调小TCP的接收和发送缓冲区来降低单个TCP连接的资源占用率。当然,对于不同的应用场景,收发缓冲区的最优值可能不同,需要根据实际场景结合性能测试数据进行调优。

合理使用内存池

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

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

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

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

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

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

如果服务端不做复杂的业务逻辑操作,仅是简单的内存操作和消息转发,可以通过调大NioEventLoop工作线程池的方式,直接在I/O线程中执行业务ChannelHandler,从而减少一次线程上下文切换,性能反而更高。如果有复杂的业务逻辑操作,建议I/O线程和业务线程分离。由于I/O线程之间不存在锁竞争,可以创建一个大的NioEventLoopGroup线程组,所有Channel共享同一个线程池。

对于后端的业务线程池,建议创建多个小的业务线程池,线程池可以与I/O线程绑定,这样既减少了锁竞争,又提升了后端的处理性能。

端侧并发连接数的流控

无论服务端性能优化到何种程度,都需要考虑流控功能。当资源成为瓶颈或遇到大量设备接入时,需要通过流控对系统进行保护。流控策略有很多种,针对端侧连接数的流控是其中之一。

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

插播一条:真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

JVM层面相关性能优化

当客户端的并发连接数达到数十万甚至数百万时,系统的一个较小抖动就会导致严重后果。

例如,服务端的GC(垃圾回收)可能导致应用暂停(STW),持续几秒钟,这会导致海量客户端设备掉线或消息积压。一旦系统恢复,海量设备接入或数据发送很可能瞬间将服务端冲垮。

GC参数优化

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

确定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、三选二原则

吞吐量、延迟和内存占用不能兼得,无法同时达到最优。需要根据业务场景选择优先级。对于大多数应用,吞吐量优先,其次是延迟。对于时延敏感型业务,需要调整次序。

确定服务端内存占用

在优化GC之前,需要确定应用程序的内存占用大小,以便设置合适的内存,提升GC效率。内存占用与活跃数据有关,活跃数据是指应用程序稳定运行时长时间存活的Java对象。

活跃数据的计算方式:通过GC日志采集GC数据,获取应用程序稳定时老年代占用的Java堆大小,以及永久代(元数据区)占用的Java堆大小,两者之和即为活跃数据的内存占用大小。

GC优化过程

1、GC数据的采集和研读:通过GC日志采集数据,分析GC情况。
2、设置合适的JVM堆大小:根据应用程序的内存占用,设置合适的JVM堆大小。
3、选择合适的垃圾回收器和回收策略:根据应用场景选择合适的垃圾回收器和回收策略。

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!

真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3250524.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

(三)原生js案例之滚动到底部解锁按钮状态

业务主要是注册页面&#xff0c;有很长的条款需要用户去读&#xff0c;必须确认用户是不是看到全部的条款&#xff0c;这个场景下可以使用 效果 代码实现 必要的css <style>*{padding: 0;margin: 0;}ul{list-style: none;width: 330px;height: 100%;/* height: 200px;…

kotlin compose 实现应用内多语言切换(不重新打开App)

1. 示例图 2.具体实现 如何实现上述示例,且不需要重新打开App ①自定义 MainApplication 实现 Application ,定义两个变量: class MainApplication : Application() { object GlobalDpData { var language: String = "" var defaultLanguage: Strin…

DA-SVM多变量分类预测|蜻蜓优化算法-支持向量机|Matalb

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、原理介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&a…

【LeetCode】十四、回溯法:括号生成 + 子集

文章目录 1、回溯法2、leetcode22&#xff1a;括号生成3、leetcode78&#xff1a;子集 1、回溯法 使用场景&#xff0c;如找[1&#xff0c;2&#xff0c;3]的所有子集&#xff1a; 2、leetcode22&#xff1a;括号生成 以n2为例&#xff0c;即两个左括号、两个右括号&#xff0c…

小白操作Typora快捷键操作day01

小白操作Typora快捷键操作day01 一、标题 建议先写标题内容&#xff0c;然后不需要选中直接Ctrl1~6对应所需要的标题&#xff0c;然后回车 ctrl""级别增加 ctrl1~6对应级别的标题&#xff08;ctrl0是普通文本&#xff09; 二、段落 1、换行 笑呵呵&#xff08…

科技论文在线--适合练习期刊写作和快速发表科技成果论文投稿网站

中国科技论文在线这个平台可以作为练手的一个渠道&#xff0c;至少可以锻炼一下中文写作&#xff0c;或者写一些科研方向的简单综述性文章。当然&#xff0c;如果你的老师期末要求也是交一份科技论文在线的刊载证明的话&#xff0c;这篇文章可以给你提供一些经验。 中国科技论…

政安晨【零基础玩转各类开源AI项目】基于Ubuntu系统部署Hallo :针对肖像图像动画的分层音频驱动视觉合成

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 零基础玩转各类开源AI项目 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本文目标&#xff1a;在Ubuntu系统上部署Hallo&#x…

高频面试题-CSS

BFC 介绍下BFC (块级格式化上下文) 1>什么是BFC BFC即块级格式化上下文&#xff0c;是CSS可视化渲染的一部分, 它是一块独立的渲染区域&#xff0c;只有属于同一个BFC的元素才会互相影响&#xff0c;且不会影响其它外部元素。 2>如何创建BFC 根元素&#xff0c;即HTM…

【好玩的经典游戏】Docker环境下部署赛车小游戏

【好玩的经典游戏】Docker环境下部署赛车小游戏 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 安装Docker环境3.2 检查Docker服务状态3.3 检查Docker版本3.4 检查docker compose 版本四、构建容器镜像4.1 下…

基于springboot新生宿舍管理系统

系统背景 在当今高等教育日益普及的时代背景下&#xff0c;高校作为知识传播与创新的重要基地&#xff0c;其基础设施的智能化管理显得尤为重要。新生宿舍作为大学生活的起点&#xff0c;不仅是学生日常生活与学习的重要场所&#xff0c;也是培养学生独立生活能力和团队合作精神…

IP溯源工具--IPTraceabilityTool

工具地址&#xff1a;xingyunsec/IPTraceabilityTool: 蓝队值守利器-IP溯源工具 (github.com) 工具介绍&#xff1a; 在攻防演练期间&#xff0c;对于值守人员&#xff0c;某些客户要求对攻击IP都进行分析溯源&#xff0c;发现攻击IP的时候&#xff0c;需要针对攻击IP进行分析…

【electron6】浏览器实时播放PCM数据

pcm介绍&#xff1a;PCM&#xff08;Puls Code Modulation&#xff09;全称脉码调制录音&#xff0c;PCM录音就是将声音的模拟信号表示成0,1标识的数字信号&#xff0c;未经任何编码和压缩处理&#xff0c;所以可以认为PCM是未经压缩的音频原始格式。PCM格式文件中不包含头部信…

单片机程序设计模式

RTOS:多任务拆分交叉执行 Q:状态机和多任务模式有什么区别 Q:任务创建和任务调度器是什么&#xff1f; 裸机程序的设计模式可以分为&#xff1a;轮询、前后台、定时器驱动、基于状态机。前面三种方 法都无法解决一个问题&#xff1a;假设有 A、B 两个都很耗时的函数&#xf…

基于牛顿-拉夫逊优化算法(Newton-Raphson-based optimizer, NBRO)的无人机三维路径规划

牛顿-拉夫逊优化算法(Newton-Raphson-based optimizer, NBRO)是一种新型的元启发式算法&#xff08;智能优化算法&#xff09;&#xff0c;该成果由Sowmya等人于2024年2月发表在中科院2区Top SCI期刊《Engineering Applications of Artificial Intelligence》上。 1、算法原理…

前端开发_注意事项

无论使用哪种框架开发&#xff08;vue、react、...&#xff09;&#xff0c;前端开发终究是结构&#xff08;HTML&#xff09;、样式&#xff08;CSS&#xff09;、逻辑&#xff08;用户操作数据处理对接后端API&#xff09;。那么开发过程中都需要注意哪些事项&#xff0c;本文…

VScode:前端项目中yarn包的安装和使用

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端&#xff0c;安装yarn插件 输入 npm ins…

新版本 idea 创建不了 spring boot 2 【没有jkd8选项】

创建新项目 将地址换成如下 https://start.aliyun.com/

vue this.$refs 动态拼接

业务需要&#xff0c;refs是不固定的 <vxe-grid refgridWarehouse v-bind"gridWarehouseOptions" v-if"tableHeight" :height"tableHeight":expand-config"{iconOpen: vxe-icon-square-minus, iconClose: vxe-icon-square-plus}"c…

Filebeat k8s 部署(Deployment)采集 PVC 日志发送至 Kafka——日志处理(二)

文章目录 前言Filebeat Configmap 配置Filebeat Deployment验证总结 前言 在上篇文章中总结了 Django 日志控制台输出、文件写入按天拆分文件&#xff0c;自定义 Filter 增加 trace_id 以及过滤——日志处理&#xff08;一)&#xff0c;将日志以 JSON 格式写入日志文件。我们的…

object-C 解答算法:移动零(leetCode-283)

移动零(leetCode-283) 题目如下图:(也可以到leetCode上看完整题目,题号283) 解题思路: 本质就是把非0的元素往前移动,接下来要考虑的是怎么移动,每次移动多少? 这里需要用到双指针,i 记录每次遍历的元素值, j 记录“非0元素值”需要移动到的位置; 当所有“非0元素值”都移…