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);



    @Autowired
    private NettyUdpServer nettyUdpServer;






    @Override
    public 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);

    @Override
    protected 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/#前言

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins配置代理节点时遇到的坑和解决办法

需求&#xff1a;服务器太满了&#xff0c;需要找个比较空闲的机器分担一下&#xff0c;看上了同网络的某开会用的笔记本&#xff0c;把这个本本利用起来能跑一个算一个。 但配置起来并不容易&#xff0c;遇到的问题有些网上也几乎找不到答案。这里记录一下能救一个是一个&…

STP原理与配置

为了提高网络可靠性&#xff0c;交换网络中通常会使用冗余链路。然而&#xff0c;冗余链路会给交换网络带来环路风险&#xff0c;并导致广播风暴以及MAC地址表不稳定等问题&#xff0c;进而会影响到用户的通信质量。生成树协议STP&#xff08;Spanning Tree Protocol&#xff0…

【深度学习目标检测】九、基于yolov5的路标识别(python,目标检测)

YOLOv5是目标检测领域一种非常优秀的模型&#xff0c;其具有以下几个优势&#xff1a; 1. 高精度&#xff1a;YOLOv5相比于其前身YOLOv4&#xff0c;在目标检测精度上有了显著的提升。YOLOv5使用了一系列的改进&#xff0c;如更深的网络结构、更多的特征层和更高分辨率的输入图…

VSCode 配置自动生成头文件

相关文章 VSCode 开发C/C实用插件分享——codegeex VSCode 开发C/C实用插件分享——koroFileHeader VSCode 配置自动生成头文件 一、snippets二、配置步骤三、效果展示 一、snippets 相信大家对C、C都头文件都不陌生&#xff0c;都会发现每个头文件都会包括下面的这些格式&…

【PostgreSQL】从零开始:(三)PgAdmin4下载与安装

【PostgreSQL】从零开始:&#xff08;三&#xff09;PgAdmin4下载与安装 pgAdmin简介liunx下部署通过yum部署pgAdmin4&#xff08;6.21&#xff09;1.安装依赖包2.永久停止防火墙3.配置pgadmin4项目源4.下载并安装pgAdmin45.执行初始化命令6.访问我们的网站 liunx下通过python方…

思码逸企业版 4.0 特性之三:研发效能数据的智能化分析与解读

建立研发效能体系&#xff0c;数据的收集与清理并建立指标体系只是第一步&#xff0c;如果不针对这些指标采集到的数据进行分析&#xff0c;那就无法做到研发效能度量闭环&#xff0c;那么指标体系也就毫无意义。所以研发效能分析在整个研发效能改进闭环中占据非常重要的一环。…

前端开发中的webpack打包工具

前端技术发展迅猛&#xff0c;各种可以提高开发效率的新思想和框架层出不穷&#xff0c;但是它们都有一个共同点&#xff0c;即源代码无法直接运行&#xff0c;必须通过转换后才可以正常运行。webpack是目前主流的打包模块化JavaScript的工具之一。 本章主要涉及的知识点有&am…

算法-动态规划

动态规划算法 应用场景-背包问题 介绍 动态规划(Dynamic Programming)算法的核心思想是&#xff1a;将大问题划分为小问题进行解决&#xff0c;从而一步步获取最优解的处理算法动态规划算法与分治算法类似&#xff0c;其基本思想也是将待求解问题分解成若干个子问题&#xff0…

【运维笔记】mvware centos挂载共享文件夹

安装mvware-tools 这里用的centos安装 yum install open-vm-tools 设置共享文件夹 依次点击&#xff1a;选项-共享文件夹-总是启用-添加&#xff0c;安装添加向导操作添加自己想共享的文件夹后。成功后即可在文件夹栏看到自己共享的文件夹 挂载文件夹 临时挂载 启动虚拟机&…

视频推拉流EasyDSS互联网直播/点播平台构建户外无人机航拍直播解决方案

一、背景分析 近几年&#xff0c;国内无人机市场随着航拍等业务走进大众&#xff0c;出现爆发式增长。无人机除了在民用方面的应用越来越多&#xff0c;在其他领域也已经开始广泛应用&#xff0c;比如公共安全、应急搜救、农林、环保、交通 、通信、气象、影视航拍等。无人机使…

什么是工业互联网平台?

1.什么是工业互联网平台&#xff1f; 1.1 工业互联网平台的定义 工业互联网平台是一个连接设备与服务、数据与人的跨行业、跨领域的全新工业平台。工业互联网平台利用了互联网、物联网、大数据、AI等技术&#xff0c;集成各类工业设备&#xff0c;不断采集和分析数据&#xff…

数据库动态视图和存储过程报表数据管理功能设计

需求&#xff1a;需要将ERP的报表数据挪到OA中&#xff0c;但是OA表单设计不支持存储过程动态传参&#xff0c;所以需要设计一个系统&#xff0c;可以手动配置&#xff0c;动态显示原本ERP的报表数据&#xff0c;ERP报表是存在数据库的视图和存储过程中 思路&#xff1a;因为E…

Which local search operator best 4 SPVRPTW:or and 2-opt*

这篇文献的研究背景是对车辆路径问题&#xff08;VRP&#xff09;的局部搜索移动算子进行测试&#xff0c;其中包括分割配送和时间窗口的车辆路径问题。 VRP涉及根据各种约束条件为客户提供货物的最优路线。当配送的时间窗口和分割配送选项被引入时&#xff0c;问题变得更加复…

Kotlin 笔记 -- Kotlin 语言特性的理解(一)

函数引用、匿名函数、lambda表达式、inline函数的理解 双冒号对函数进行引用的本质是生成一个函数对象只有函数对象才拥有invoke()方法&#xff0c;而函数是没有这个方法的kotlin中函数有自己的类型&#xff0c;但是函数本身不是对象&#xff0c;因此要引用函数类型就必须通过双…

arcgis javascript api4.x加载天地图cgs2000坐标系

需求&#xff1a;arcgis javascript api4.x加载天地图cgs2000坐标系 效果&#xff1a; 示例代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"wid…

vscode使用remote ssh到server上 - Node进程吃满CPU

起因&#xff1a;Node进程吃满CPU 分析 我发现每次使用vscode的remote插件登陆到server后&#xff0c;就会出现node进程&#xff0c;不太清楚干什么用的&#xff0c;但是绝对和它有关。 查找原因 首先找到了这篇文章&#xff0c;解决了rg进程的问题&#xff1a; https://blo…

克服端口顺序影响,使用PCAN实现固定设备ID/通道分配

来源&#xff1a;虹科智能互联 虹科干货 | 克服端口顺序影响&#xff0c;使用PCAN实现固定设备ID/通道分配 原文链接&#xff1a;https://mp.weixin.qq.com/s/Ik2fp9sWyI9MiQOOHO1dCA 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 导读 多设备协同工作是常见的需求…

Mac managing Multiple Python Versions With pyenv 【 mac pyenv 管理多个python 版本 】

文章目录 1. 简介2. 安装2.1 brew 安装 pyenv2.2 脚本安装 3. pyenv 安装 Python4. 卸载 python5. 管理 python 1. 简介 Pyenv 是一个用于管理和切换多个 Python 版本的工具。它允许开发人员在同一台计算机上同时安装和使用多个不同的 Python 版本&#xff0c;而无需对系统进行…

Apache Seatunnel本地源码构建编译运行调试

Apache Seatunnel本地源码构建编译运行调试 文章目录 1. 环境准备1.1 Java环境1.2 Maven1.3 IDEA1.4 Docker环境1.5 Mysql8.0.281.6 其它环境准备 2. 源码包下载3. idea项目配置3.1 项目导入3.2 maven配置3.3 项目JDK配置3.4 项目启动参数配置3.4.1 seatunnel项目启动参数配置3…

SpringBoot+WebSocket

SpringBootWebSocket 1.导入依赖&#xff1a; -- Spring Boot 2.x 使用 javax.websocket-- Spring Boot 3.x 使用 jakarta.websocket<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId&g…