Linux下Netty实现高性能UDP服务
前言
近期笔者基于Netty接收UDP报文进行业务数据统计的功能,因为Netty默认情况下处理UDP收包只能由一个线程负责,无法像TCP协议那种基于主从reactor模型实现多线程监听端口,所以笔者查阅网上资料查看是否有什么方式可以接收UDP收包的性能瓶颈,遂以此文来记录一下笔者的解决过程。
简介Linux内核3.9的新特性对Netty的影响
常规的Netty处理UDP包我们只能用按个NIOEventLoop线程接收传输的数据包,从底层来看即只使用一个socket线程监听网络端口,通过这一个线程将数据传输到应用层上,这一切使得我们唯一能够调优的方式就是在Socket监听传输时尽可能快速将发送给应用程序,让应用程序及时处理完以便NIOEventLoop线程能够及时处理下一个UDP数据包。亦或者,我们也可以直接通过增加服务器的数量通过集群的方式提升系统整体的吞吐量。
然而事实真是如此吗?在Linux内核3.9版本新增了一个SO_REUSEPORT的特性,它使得单台Linux的端口可以被多个Socket线程监听,这一特性使得Netty在高并发场景下的UDP数据包能够及时被多个线程及时处理,尽可能的避免了丢包线程且最大化的利用了CPU核心,实现内核层面的负载均衡。
Netty实现Linux下UDP端口复用步骤
引入Netty依赖
为了使用Netty我们必须先引入对应的maven依赖,这里笔者选择了4.1.58的最终版,读者可以按需选择自己的版本。
<!--netty--><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.58.Final</version></dependency>
编写启动类和启动逻辑
然后我我们需要编写Netty的启动类,代码模板如下,因为Netty默认使用的是Java NIO,而在Linux支持epoll模型,相比与常规的Java NIO这种通过来回在用户态和内核态来回拷贝事件数组fd的方式,epoll内部自己维护了事件的数组并可以将自行去询问连接状态并将结果返回到用户态显得更加高效。
所以笔者在启动类的编写时会判断当前服务器是否支持epoll的逻辑,并通过该判断顺手解决了是否基于SO_REUSEPORT开启多线程监听的功能(注:这段代码读者必须自行查阅一下服务器内核版本是否大于等于3.9)。
/*** netty服务*/
@Component
public class NettyUdpServer {private static final Logger LOG = LoggerFactory.getLogger(NettyUdpServer.class);private EventLoopGroup bossLoopGroup;private Channel serverChannel;/*** netty初始化*/public void init(int port) {LOG.info("Epoll.isAvailable():{}", Epoll.isAvailable());//表示服务器连接监听线程组,专门接受 accept 新的客户端client 连接bossLoopGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();try {//1、创建netty bootstrap 启动类Bootstrap serverBootstrap = new Bootstrap();//2、设置boostrap 的eventLoopGroup线程组serverBootstrap.group(bossLoopGroup)//3、设置NIO UDP连接通道.channel(Epoll.isAvailable() ? EpollDatagramChannel.class : NioDatagramChannel.class)//4、设置通道参数 SO_BROADCAST广播形式.option(ChannelOption.SO_BROADCAST, true).option(ChannelOption.SO_RCVBUF, 1024 * 1024)//5、设置处理类 装配流水线.handler(new NettyUdpHandler());// linux平台下支持SO_REUSEPORT特性以提高性能if (Epoll.isAvailable()) {LOG.info("SO_REUSEPORT");serverBootstrap.option(EpollChannelOption.SO_REUSEPORT, true);}// 如果支持epoll则说明是Linux版本,则利用SO_REUSEPORT创建多个线程if (Epoll.isAvailable()) {// linux系统下使用SO_REUSEPORT特性,使得多个线程绑定同一个端口int cpuNum = Runtime.getRuntime().availableProcessors();LOG.info("using epoll reuseport and cpu:" + cpuNum);for (int i = 0; i < cpuNum; i++) {LOG.info("worker-{} bind", i);//6、绑定server,通过调用sync()方法异步阻塞,直到绑定成功ChannelFuture future = serverBootstrap.bind(port).sync();if (!future.isSuccess()) {LOG.error("bootstrap bind fail port is " + port);throw new Exception(String.format("Fail to bind on [host = %s , port = %d].", "192.168.2.128", port), future.cause());} else {LOG.info("bootstrap bind success ");}}} else {ChannelFuture future = serverBootstrap.bind(port).sync();if (!future.isSuccess()) {LOG.error("bootstrap bind fail port is " + port);throw new Exception(String.format("Fail to bind on [host = %s , port = %d].", "127.0.0.1", port), future.cause());} else {LOG.info("bootstrap bind success ");}}} catch (Exception e) {LOG.error("报错了,错误原因:{}", e.getMessage(), e);}}}
因为该代码是编写在spring boot项目中,所以我们还需要添加一下启动的逻辑。
@Component
public class InitTask implements CommandLineRunner {private static final Logger LOG = LoggerFactory.getLogger(InitTask.class);@Autowiredprivate NettyUdpServer nettyUdpServer;@Overridepublic void run(String... args) {LOG.info("netty服务器初始化成功,端口号:{}", 7000);nettyUdpServer.init(7000);}}
封装业务处理类
处理类的逻辑比较简单了,收到内容后打印后,原子类自增一下,该原子类是用于后续压测统计是否丢包用的。
/*** 报文处理器*/
@Component
@ChannelHandler.Sharable
public class NettyUdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {private static final Logger LOG = LoggerFactory.getLogger(NettyUdpHandler.class);private static AtomicInteger atomicInteger=new AtomicInteger(0);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket dp) {try {int length = dp.content().readableBytes();//分配一个新的数组来保存具有该长度的字节数据byte[] array = new byte[length];//将字节复制到该数组dp.content().getBytes(dp.content().readerIndex(), array);LOG.info("收到UDP报文,报文内容:{} 包处理个数:{}", new String(array),atomicInteger.incrementAndGet());} catch (Exception e) {LOG.error("报文处理失败,失败原因:{}", e.getMessage(), e);}}
}
基于jmeter完成压测统计丢包率
自此我们项目都编写完成了,我们不妨使用jmeter进行一次压测,可以看到笔者会一次性发送100w个数据包查看最终的收包数。
而UDP包的格式以及目的地址和内容如下
最终压测结果如下,可以看到服务器都及时的收到了数据包,并不存在丢包的现象。
为了可以看到性能的提升,笔者将代码还原回单线程监听的老代码段:
/*** netty初始化*/public void init(int port) {LOG.info("Epoll.isAvailable():{}", Epoll.isAvailable());//表示服务器连接监听线程组,专门接受 accept 新的客户端client 连接bossLoopGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();try {//1、创建netty bootstrap 启动类Bootstrap serverBootstrap = new Bootstrap();//2、设置boostrap 的eventLoopGroup线程组serverBootstrap.group(bossLoopGroup)//3、设置NIO UDP连接通道.channel(Epoll.isAvailable() ? EpollDatagramChannel.class : NioDatagramChannel.class)//4、设置通道参数 SO_BROADCAST广播形式.option(ChannelOption.SO_BROADCAST, true).option(ChannelOption.SO_RCVBUF, 1024 * 1024)//5、设置处理类 装配流水线.handler(new NettyUdpHandler());ChannelFuture future = serverBootstrap.bind(port).sync();if (!future.isSuccess()) {LOG.error("bootstrap bind fail port is " + port);throw new Exception(String.format("Fail to bind on [host = %s , port = %d].", "127.0.0.1", port), future.cause());} else {LOG.info("bootstrap bind success ");}} catch (Exception e) {LOG.error("报错了,错误原因:{}", e.getMessage(), e);}}
根据老的压测结果来看,单线程监听的情况下,确实会存在一定的丢包,所以如果在高并发场景下使用Netty接收UDP数据包的小伙伴,建立利用好Linux内核3.9的特性提升程序的吞吐量哦。
参考文献
Linux下Netty实现高性能UDP服务(SO_REUSEPORT): https://blog.csdn.net/monokai/article/details/108453746
Netty网络传输简记: https://www.sharkchili.com/pages/710071/#前言
相关文章:
Linux下Netty实现高性能UDP服务
前言 近期笔者基于Netty接收UDP报文进行业务数据统计的功能,因为Netty默认情况下处理UDP收包只能由一个线程负责,无法像TCP协议那种基于主从reactor模型实现多线程监听端口,所以笔者查阅网上资料查看是否有什么方式可以接收UDP收包的性能瓶颈…...
Ubuntu 22.04 Tesla V100s显卡驱动,CUDA,cuDNN,MiniCONDA3 环境的安装
今天来将由《蓝创精英团队》带来一个Ubuntu 显卡环境的安装,主要是想记录下来,方便以后快捷使用。 主要的基础环境 显卡驱动 (nvidia-smi)CUDA (nvidia-smi 可查看具体版本)cuDNN (cuda 深度学习加速库)Conda python环境管理(Miniconda3) Nvidia 驱动…...
FFmpeg转码流程和常见概念
视频格式:mkv,flv,mov,wmv,avi,mp4,m3u8,ts等等 FFmpeg的转码工具,它的处理流程是这样的: 从输入源获得原始的音视频数据,解封装得到压缩封装的音…...
【01】GeoScene生产海图或者电子航道图
1.1 什么是电子海图制图模块 GeoScene海事模块是一个用于管理和制作符合国际水文组织(IHO)S-100系列标准和S-57标准的海事数据的系统。提供了S-100和S-57工具,用于加载基于S-100的要素目录、创建基于S-57传输结构的数据、输入数据、符号化数…...
TWS蓝牙耳机的船运模式
TWS蓝牙耳机的船运模式 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送语音信号处理降噪算法,蓝牙耳机音频,DSP音频项目核心开发资料, TWS蓝牙耳机的船运模式是指在将耳机从一个地方运送到另一个地方时,…...
Vue系列之指令 v-html
文章の目录 1、v-html指令2、基本用法写在最后 1、v-html指令 v-html 指令类似于 v-text 指令,它与 v-text 区别在于 v-text 输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出,类似于 JavaScrip…...
Mac如何安装stable diffusion
今天跟大家一起在Mac电脑上安装下stable diffusion,在midjourney等模型收费的情况下如何用自己的电脑算力用上免费的画图大模型呢?来吧一起实操起来 一、安装homebrew 官网地址:Homebrew — The Missing Package Manager for macOS (or Lin…...
Kubernetes (k8s) 快速认知
应用部署方式 传统部署时代 早期的时候,各个组织是在物理服务器上运行应用程序。缺点 资源分配问题: 无法限制在物理服务器中运行的应用程序资源使用 维护成本问题: 部署多个物理机,维护许多物理服务器的成本很高 虚拟化部署时…...
Electron V28主进程与渲染进程互相通信总结
本文示例采用ElectronVue3TS编写,请读者理顺思路,自行带入自己的项目。 注: 读本文前请先搞懂什么是主进程,什么是渲染进程。 在Electron中有着ipcMain和ipcRenderer、contextBridge模块,以及创建窗口对象上的webCont…...
MySQL主从复制详解
目录 1. 主从复制的工作原理 1.1. 主从复制的角色 1.2. 主从复制的流程 2. 配置MySQL主从复制 2.1. 确保主服务器开启二进制日志 2.2. 设置从服务器 2.3. 连接主从服务器 2.4. 启动复制 3. 主从复制的优化与注意事项 3.1. 优化复制性能 3.2. 注意复制延迟 3.3. 处理…...
verilog基础语法-计数器
概述: 计数器是FPGA开发中最常用的电路,列如通讯中记录时钟个数,跑马灯中时间记录,存储器中地址的控制等等。本节给出向上计数器,上下计数器以及双向计数器案例。 内容 1. 向上计数器 2.向下计数器 3.向上向下计数…...
有SCL,SDA,TRIG,I2C的元器件是什么?在哪找?proteus
寻找方法:...
再谈低代码开发——值得所有程序设计和开发者重视的建议!
前几天看到关于“低代码开发”的话题,简单的谈了些自己的看法,也看了一些朋友们各抒己见的好文章,今天想结合我们实际使用的开发平台和大家再做些探讨。 在平台的简介中首先提出了这个大家一定很关心的问题: 一、“为什么使用低代…...
Docker部署MinIO对象存储服务器结合内网穿透实现远程访问
文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器,可以在各种环境中运行,例如本地、Docker容器、Kubernetes集群等。它兼…...
USB2.0 Spec
USB System Description A USB system is described by three definitional areas: • USB interconnect • USB devices • USB host USB interconnect The USB interconnect is the manner in which USB devices are connected to and communicate with the host. USB Ho…...
prbs测试
PRBS是 Pseudo Random Binary Sequence 的简称,是一种伪随机序列,用于产生随机数据。 PRBS检测主要应用在设备开局或维护期间,在没有合适误码仪的情况下,使能了PRBS检测功能的设备自行发送PRBS码流,PRBS码流通过被测试网络,经远端设备环回(远端设备需要配置环回),经过PR…...
计算机网络:数据链路层(VLAN)
今天又学到一个知识,加油! 目录 一、传统局域网的局限(促进VLAN的诞生) 二、VLAN简介 三、VLAN的实现 总结 一、传统局域网的局限(促进VLAN的诞生) 缺乏流量隔离:即使把组流量局域化道一个单一交换机中…...
C# WPF上位机开发(动态添加控件)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 写图形界面软件的时候,我们经常会遇到一种情况。那就是图形界面上面,显示的控件可能是不定的。有可能多,也有可…...
MySQL进阶|MySQL中的事务(一)
文章目录 数据库事务MySQL中的存储引擎InnoDB存储引擎架构什么是事务事务的状态总结 数据库事务 MySQL 事务主要用于处理操作量大,复杂度高的数据。比方我想要删除一个用户(销户)以及这个用户的个人信息、订单信息以及其他信息,这…...
设计模式策略模式讲解和代码示例
引言 策略是一种行为设计模式, 它将一组行为转换为对象, 并使其在原始上下文对象内部能够相互替换。 原始对象被称为上下文, 它包含指向策略对象的引用并将执行行为的任务分派给策略对象。 为了改变上下文完成其工作的方式, 其他对象可以使用另一个对象来替换当前链接的策…...
Qt容器QStackedWidget小部件堆栈
# QStackedWidget QStackedWidget是Qt框架中的一个控件,用于在同一区域显示多个子控件,只有一个子控件可见。以下是一些常用的QStackedWidget函数: addWidget(QWidget *widget):向QStackedWidget中添加一个子控件。 insertWidget(int index, QWidget *widget):在指定位置…...
设计模式 简单工厂 工厂方法模式 抽象工厂模式 Spring 工厂 BeanFactory 解析
工厂模式介绍 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。它是创建型模式。 简单工厂 简单工厂模式是指由一个工厂对象决定创建出哪一种产品类的实例, 但它不属于GOF 23种设计模式 简单工厂适用于工厂类负责创建的对象较少的场景,…...
【Hive_03】单行函数、聚合函数、窗口函数、自定义函数、炸裂函数
1、函数简介2、单行函数2.1 算术运算函数2.2 数值函数2.3 字符串函数(1)substring 截取字符串(2)replace 替换(3)regexp_replace 正则替换(4)regexp 正则匹配(5ÿ…...
RabbitMQ手动应答与持久化
1.SleepUtil线程睡眠工具类 package com.hong.utils;/*** Description: 线程睡眠工具类* Author: hong* Date: 2023-12-16 23:10* Version: 1.0**/ public class SleepUtil {public static void sleep(int second) {try {Thread.sleep(1000*second);} catch (InterruptedExcep…...
java使用枚举类型解决if-else大量堆积
调用代码 import com.example.javaone.kk.MyEnum;public class Gst {public static void main(String[] args) {MyEnum eMyEnum.getById(1);System.out.println(e.getGetSize());} }被调用代码 package com.example.javaone.kk; public enum MyEnum {ENUM1(1,2),ENUM2(2,3),E…...
【数据结构】八大排序之直接插入排序算法
🦄个人主页:修修修也 🎏所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 一.直接插入排序简介及思路 直接插入排序(Straight Insertion Sort)是一种简单直观的插入排序算法. 它的基本操作是: 将一个数据插入到已经排好的有序表中,从而得到一个新的,数…...
网络编程『socket套接字 ‖ 简易UDP网络程序』
🔭个人主页: 北 海 🛜所属专栏: Linux学习之旅、神奇的网络世界 💻操作环境: CentOS 7.6 阿里云远程服务器 文章目录 🌤️前言🌦️正文1.预备知识1.1.IP地址1.2.端口号1.3.端口号与进…...
FreeSWITCH rtp endpoint recvonly
查了下rtp.c的源码,远端端口为0就意味着recvonly,但其实不然,调用switch_rtp_new会马上返回失败 经过反复测试,增加下面几行代码之后终于变成了recvonly: tech_pvt->mode RTP_RECVONLY; rtp_flags[SWITCH_RTP_FLAG_AUTOADJ];…...
Hadoop和Spark的区别
Hadoop 表达能力有限。磁盘IO开销大,延迟度高。任务和任务之间的衔接涉及IO开销。前一个任务完成之前其他任务无法完成,难以胜任复杂、多阶段的计算任务。 Spark Spark模型是对Mapreduce模型的改进,可以说没有HDFS、Mapreduce就没有Spark。…...
英文论文降重修改技巧 papergpt
大家好,今天来聊聊英文论文降重修改技巧,希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧,可以借助此类工具: 英文论文降重修改技巧 作为网站编辑,我们经常需要处理大量…...
阿里巴巴1688怎么做网站/推广网站平台
一、JDBC 1. 概念 JDBC(Java DataBase Connectivity),Java 数据库连接, Java语言操作数据库JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据…...
html期末作业网页代码/黑帽seo技术论坛
导航控制器UINavigationController控制一系列的UIViewController,他们组成一个层次结构,每一个ViewController都在这个层次结构中上下移动,组织方式是栈形式。 每个UIViewController都有相关联的UINavigationItem,后者处于活动状…...
旅游网站首页设计模板/百度seo怎么提高排名
Tomcat内部架构学习一,Tomcat顶层架构可以看到一个Server可以有多个Service,一个Service可以有多个Connector和一个Container,这两部分是tomcat的核心。1,Connector用于处理连接相关额事情,并提供Socket与Reponse相关的…...
网站开发后端是什么/如何网络营销
大数据给各个行业发展带来了新的机遇和挑战,烟草作为对国家财政税收贡献极大的传统行业,也开始探索大数据采集、分析和应用。从烟草行业的营销、物流、生产环节入手,利用大数据分析的报表工具为烟草企业经营决策提供支撑。 在整个行业链条中…...
做网站好迷茫/长沙有实力seo优化公司
Mysql的分区分表 一、分区 好处: 1、由于将文件和索引进行了划分,所以查询的时候,速度快。 实现原理:把一个数据表的文件和索引分散到不同的物理文件中。(仍然是同一个表的操作,只不过分区了而已) mysql数…...
珊瑚绒毯移动网站建设/云南网络营销公司哪家好
已知aview 1.3 需要依赖 aalib 1.4及以上版本 其它版本兼容需要自测 一、下载 aalib 和 aview 网上教程的地址很多都是旧的 作者可能迁移过或其它原因 总之 我们要自己进去网站里面观察一下 https://sourceforge.net/projects/aa-project/files 右键复制链接就能看到下载地…...