字节面试:如何让单机下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://www.rhkb.cn/news/379779.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unity渲染人物模型透明度问题

问题1:有独立的手和衣服的模型,但最终只渲染出来半透明衣服 问题2:透明度贴图是正确的但显示却不正确 这上面两个模型的问题都是因为人物模型是一个完整的,为啥有些地方可以正常显示,有些地方透明度却有问题。 其中…

数据结构小测试:排序算法

目录 1、请简述数据结构八大排序算法的思路。 2、常用排序算法手写 冒泡排序: 选择排序: 快速排序: 归并排序: 堆排序: 3、额外再加一个二分查找吧 1、请简述数据结构八大排序算法的思路。 冒泡排序&#xff…

亚信安全发布2024年第24期《勒索家族和勒索事件监控报告》

本周态势快速感知 本周,勒索软件LockBit涉嫌对美国一家生产乙烯基产品的公司(Homeland Vinyl)进行攻击。LockBit声称他们已窃取了销售、库存、财务交易数据及其他公司记录,并声明将于2024年7月19日公开这些被盗信息。本周全球共监…

【iOS】OC类与对象的本质分析

目录 前言clang常用命令对象本质探索属性的本质对象的内存大小isa 指针探究 前言 OC 代码的底层实现都是 C/C代码,OC 的对象都是基于 C/C 的数据结构实现的,实际 OC 对象的本质就是结构体,那到底是一个怎样的结构体呢? clang常用…

AI算法17-贝叶斯岭回归算法Bayesian Ridge Regression | BRR

贝叶斯岭回归算法简介 贝叶斯岭回归(Bayesian Ridge Regression)是一种回归分析方法,它结合了岭回归(Ridge Regression)的正则化特性和贝叶斯统计的推断能力。这种方法在处理具有大量特征的数据集时特别有用&#xff…

安全入门day01

一、常用名词 1、前后端 (1)前端 前端主要负责用户界面的展示和交互。它通常包括HTML、CSS和JavaScript等技术的使用,也可能使用各种前端框架和库,如React、Vue.js、Angular等,来构建更加复杂和动态的用户界面。前端…

校验el-table中表单项

需求: 表格中每一行都有几个必填项,如用户提交时有未填的选项,将该选项标红且给出提示,类似el-form 的那种校验 el-table本身并没有校验的方法,而且每一行的输入框也是通过插槽来实现的,因此我们要自己跟…

log4js node日志插件

最近不是特别忙在用express搭建后台项目,在开发过程中遇到了需要输入日志的问 本来想直接用node自带的console来实现,后来发现console输出的日志达不到自己希望的 日志格式,后来各种百度发现了log4js插件,本文来记录log4js插件使用…

一文-深入了解Ansible常见模块、安装和部署

1 Ansible 介绍 Ansible是一个配置管理系统configuration management system, python 语言是运维人员必须会的语言, ansible 是一个基于python 开发的(集合了众多运维工具 puppet、cfengine、chef、func、fabric的优点)自动化运维工具, 其功能实现基于ss…

django实现用户的注册、登录、注销功能

创建django项目的步骤:Django项目的创建步骤-CSDN博客 一、前置工作 配置数据库,设置数据库引擎为mysql 1、在settings文件中找到DATABASES, 配置以下内容 DATABASES {"default": {ENGINE: django.db.backends.mysql, # 数据库引擎NAME: dja…

基于springboot和mybatis的RealWorld后端项目实战二之实现tag接口

修改pom.xml 新增tag数据表 SET FOREIGN_KEY_CHECKS0;-- ---------------------------- -- Table structure for tags -- ---------------------------- DROP TABLE IF EXISTS tags; CREATE TABLE tags (id bigint(20) NOT NULL AUTO_INCREMENT,name varchar(255) NOT NULL,PR…

【hadoop大数据集群 2】

【hadoop大数据集群 2】 文章目录 【hadoop大数据集群 2】1. 虚拟机克隆2. 时间同步3. 环境变量配置、启动集群、关闭集群 1. 虚拟机克隆 克隆之后一定要重新生成新虚拟机唯一的MAC地址和UUID等,确保新虚拟机与源虚拟机在网络拓扑中不发生冲突。 注意1.生成新的MA…

IDEA关联数据库

《IDEA破解、配置、使用技巧与实战教程》系列文章目录 第一章 IDEA破解与HelloWorld的实战编写 第二章 IDEA的详细设置 第三章 IDEA的工程与模块管理 第四章 IDEA的常见代码模板的使用 第五章 IDEA中常用的快捷键 第六章 IDEA的断点调试(Debug) 第七章 …

Spring Boot2(Spring Boot 的Web开发 springMVC 请求处理 参数绑定 常用注解 数据传递)

目录 一、Spring Boot 的Web开发 1. 静态资源映射规则 2. enjoy模板引擎 二、springMVC 1. springMVC-请求处理 测试: 以post方式请求 限制请求携带的参数 GetMapping 查询 PostMapping 新增 DeleteMapping删除 PutMapping 修改 2. springMVC-参…

电子画册制作攻略:如何让你的作品吸引眼球

随着数字化时代的到来,电子画册作为一种新兴的传播媒介,已经越来越受到人们的青睐。它不仅能够以生动的形式展现内容,还可以轻松地实现互动和分享。然而,如何让你的电子画册作品在众多竞争中脱颖而出,吸引更多眼球呢&a…

景联文科技构建高质量心理学系知识图谱,助力大模型成为心理学科专家

心理大模型正处于快速发展阶段,在临床应用、教育、研究等多个领域展现出巨大潜力。 心理学系知识图谱能够丰富心理大模型的认知能力,使其在处理心理学相关问题时更加精确、可靠和有洞察力。这对于提高心理健康服务的质量和效率、促进科学研究以及优化教育…

【MySQL进阶篇】SQL优化

1、插入数据 insert优化 批量插入: insert into tb_user values(1,tom),(2,cat),(3,jerry); 如果插入数据过大,可以将业务分割为多条insert语句进行插入。 手动提交事务: start transaction; insert into tb_user values(1,tom),(2,cat),(3…

Linux——多路复用之select

目录 前言 一、select的认识 二、select的接口 三、select的使用 四、select的优缺点 前言 在前面,我们学习了五种IO模型,对IO有了基本的认识,知道了select效率很高,可以等待多个文件描述符,那他是如何等待的呢&a…

视频活码如何在线制作?分享快速制作二维码的方法

视频想要快速的分享现在有很多的人会选择二维码的方式,将视频转换成二维码通过手机扫码就能够快速在线查看视频内容,这样可以不占用扫码者自身的内存,随时扫码从云端调取内容查看,更加的方便快捷便于内容的分享。那么具体该如何实…

搭建个人智能家居 7 - 空气颗粒物检测

搭建个人智能家居 7 - 空气颗粒物检测 前言说明PMS5003ESPHomeHomeAssistant结束 前言 到目前为止,我们这个智能家居系统添加了4个外设,分别是:LED灯、RGB灯、DHT11温度传感器和SGP30。今天继续添加环境测量类传感器“PMS5003空气颗粒物检测…