手写netty通信框架以及常见问题

目录

通信框架设计

实现功能点

通信模型

消息定义

可靠性设计

代码

服务端代码

常见netty问题

如何让netty支持百万长连接?

1. 操作系统层面优化

2. netty层面优化

        2.1 设置合理线程

        2.2 心跳优化

        2.3 合理使用内存池

        2.4 IO线程与业务线程剥离

3. JVM层面优化 

什么是水平触发(LT)和边缘触发(ET)?

DNS解析域名全过程?


通信框架设计

实现功能点

  1. 基于netty的NIO通信框架, 提供高性能的异步通信能力
  2. 支持消息编解码, 实现POJO的序列化和反序列化
  3. 消息安全验证, 防篡改
  4. 支持IP白名单接入
  5. 链路鉴权校验
  6. 客户端自动重连

通信模型

运行流程

  1. 客户端发送握手请求消息, 并携带节点ID和身份认证信息
  2. 服务端对握手消息请求合法性校验, 包括节点ID有效校验, 节点重复登录, IP是否合法等.校验通过后, 发送握手应答消息给客户端
  3. 链路建立成功后, 客户端发送业务消息
  4. 链路建立成功后, 客户端定时发送心跳消息
  5. 服务端发送业务消息
  6. 服务端定时发送心跳消息
  7. 服务端退出时, 服务端关闭连接, 客户端感知到服务端关闭连接后, 被动关闭客户端连接。并且开启异步线程, 客户端尝试重连

消息定义

  1. 消息头
    1. 消息id
    2. md5缺省摘要
    3. 消息类型
  2. 消息体

可靠性设计

心跳机制

重连机制

重复登录校验

md5缺省摘要校验

代码

服务端代码

1. maven依赖

        <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.42.Final</version></dependency><dependency><groupId>de.javakaffee</groupId><artifactId>kryo-serializers</artifactId><version>0.42</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.30</version></dependency><!-- logback 依赖 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.2.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency>

2. Model代码

@Data
public final class MyMessage {private MsgHeader msgHeader;private Object body;
}@Data
public final class MsgHeader {/*消息体的MD5摘要*/private String md5;/*消息的ID,因为是同步处理模式,不考虑应答消息需要填入请求消息ID*/private long msgID;/*消息类型*/private byte type;/*消息优先级*/private byte priority;private Map<String, Object> attachment = new HashMap<String, Object>();
}public enum MessageType {SERVICE_REQ((byte) 0),/*业务请求消息*/SERVICE_RESP((byte) 1), /*TWO_WAY消息,需要业务应答*/ONE_WAY((byte) 2), /*无需应答的业务请求消息*/LOGIN_REQ((byte) 3), /*登录请求消息*/LOGIN_RESP((byte) 4), /*登录响应消息*/HEARTBEAT_REQ((byte) 5), /*心跳请求消息*/HEARTBEAT_RESP((byte) 6);/*心跳应答消息*/private byte value;private MessageType(byte value) {this.value = value;}public byte value() {return this.value;}
}

3. NettyServer

@Slf4j
public class NettyServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("nt_boss"));EventLoopGroup workerGroup = new NioEventLoopGroup(0, new DefaultThreadFactory("nt_worker"));ServerBootstrap sb = new ServerBootstrap();sb.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ServerInit());sb.bind(SERVER_PORT).sync();log.info("netty server start ip = {}   port = {}", SERVER_IP, SERVER_PORT);}
}public class ServerInit extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel sc) throws Exception {// 解决粘包 半包问题sc.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));sc.pipeline().addLast(new LengthFieldPrepender(2));// 序列化成消息对象sc.pipeline().addLast(new KryoDecoder());sc.pipeline().addLast(new KryoEncoder());/*处理心跳超时*/sc.pipeline().addLast(new ReadTimeoutHandler(15));// 处理登录鉴权sc.pipeline().addLast(new LoginAuthRespHandler());// 心跳sc.pipeline().addLast(new HeartBeatRespHandler());// 处理真正业务sc.pipeline().addLast(new ServerBusiHandler());}
}

4. 业务Handler

@Slf4j
public class ServerBusiHandler extends SimpleChannelInboundHandler<MyMessage> {private static BlockingQueue<Runnable> taskQueue  = new ArrayBlockingQueue<Runnable>(3000);private static ExecutorService executorService = new ThreadPoolExecutor(NettyRuntime.availableProcessors(),NettyRuntime.availableProcessors() * 2,60, TimeUnit.SECONDS,taskQueue);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) {// 校验MD5String headMd5 = msg.getMsgHeader().getMd5();String calcMd5 = EncryptUtils.encryptObj(msg.getBody());if(!headMd5.equals(calcMd5)){log.error("报文md5检查不通过:"+headMd5+" vs "+calcMd5+",关闭连接");ctx.writeAndFlush(buildBusiResp("报文md5检查不通过,关闭连接"));ctx.close();}log.info(msg.toString());if(msg.getMsgHeader().getType() == MessageType.ONE_WAY.value()){log.debug("ONE_WAY类型消息,异步处理");executorService.execute(() -> {// 处理消息 msg});}else{log.debug("TWO_WAY类型消息,应答");ctx.writeAndFlush(buildBusiResp("OK"));}}private MyMessage buildBusiResp(String result) {MyMessage message = new MyMessage();MsgHeader msgHeader = new MsgHeader();msgHeader.setType(MessageType.SERVICE_RESP.value());message.setMsgHeader(msgHeader);message.setBody(result);return message;}
}

常见netty问题

如何让netty支持百万长连接?

1. 操作系统层面优化

        1.1 修改操作系统允许当前用户进程打开的句柄数量

# 查看允许当前用户进程打开的句柄数量
ulimit -n# 修改数量
ulimit -n 1000000

        1.2 修改软限制和硬限制

# 修改配置文件
vim /etc/security/limits.conf# 文末添加
* soft nofile 1000000
* hard nofile 1000000# 修改配置文件
vim /etc/pam.d/login# 文末添加
session required /lib/security/pam_limits.so

         1.3 修改Linux系统级能打开最大文件数限制

# 查看Linux能打开最大文件数
cat /proc/sys/fs/file-max# 修改限制
vim /etc/sysctl.conf
# 文末添加
fs.file_max = 1000000
# 配置立即生效
sysctl -p

2. netty层面优化

        2.1 设置合理线程

        对于 Nety 服务端,通常只需要启动一个监听端口用于端侧设备接入即可,但是如果服务 端集群实例比较少,甚至是单机(或者双机冷备)部署,在端侧设备在短时间内大量接入时,需要 对服务端的监听方式和线程模型做优化,以满足短时间内(例如 30s)百万级的端侧设备接入的 需要。 服务端可以监听多个端口,利用主从 Reactor 线程模型做接入优化,前端通过 SLB 做 4 层 门 7 层负载均衡。

         IO线程优化: 先使用默认构造函数的线程(CPU*2)压测, jstack监控堆栈, 如果都停留在Selectorlmpl. lockAndDoSelect, 表明IO线程比较空闲, 无须调整.如果停留在读/写操作, 可适当调大线程.

        2.2 心跳优化

心跳优化策略:

  1. 及时检测失效连接, 将其剔除, 防止句柄占用, 导致OOM等问题
  2. 设置合理心跳周期, 防止心跳定时任务积压, 造成频繁FullGC
  3. 使用netty提供的链路空闲检测机制, 不要自己创建定时任务, 增加系统负担

心跳失败判断:

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

Nety 提供了三种链路空闲检测机制:

  1. 读空闲,链路持续时间 T 没有读取到任何消息
  2. 写空闲,链路持续时间 T 没有发送任何消息
  3. 读写空闲,链路持续时间 T 没有接收或者发送任何消息
        2.3 合理使用内存池

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

        2.4 IO线程与业务线程剥离

3. JVM层面优化 

  • 调整应用内存, 最少16G以上.
  • 垃圾收集器用G1或者ZGC
  • 结合具体业务, 监控JVM, 调整堆大小

什么是水平触发(LT)和边缘触发(ET)?

  • Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写。如果这次没有把数据一次性全部读写完,那么下次调用 epoll_wait() 时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会 一直通知你。
  • Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写。如果这次没有把数据全部读写完,那么下次调用 epoll_wait()时, 它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会 通知你。这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符

select(),poll()模型都是水平触发模式,信号驱动 IO 是边缘触发模式,epoll()模型即支 持水平触发,也支持边缘触发,默认是水平触发。JDK 中的 select 实现是水平触发,而 Netty 提供的 Epoll 的实现中是边缘触发。

DNS解析域名全过程?

如将域名www.google.com解析成IP地址

  1. 用户在浏览器中输入域名www.google.com。
  2. 浏览器首先会检查本地的hosts文件和浏览器自身的DNS缓存,看是否有对应的IP地址。如果有,就直接访问这个IP地址对应的网站;如果没有,则向本地DNS服务器发起查询请求。
  3. 本地DNS服务器收到查询请求后,会查看自己的DNS缓存,看是否有对应的IP地址。如果有,就返回这个IP地址;如果没有,则向根DNS服务器发起查询请求。
  4. 根DNS服务器收到查询请求后,会将负责.com顶级域的顶级域名服务器的地址返回给本地DNS服务器。
  5. 本地DNS服务器收到顶级域名服务器的地址后,向其发起查询请求。顶级域名服务器会将负责google.com域的权威DNS服务器的地址返回给本地DNS服务器。
  6. 本地DNS服务器收到权威DNS服务器的地址后,向其发起查询请求。权威DNS服务器会将www.google.com域名对应的IP地址返回给本地DNS服务器。
  7. 本地DNS服务器收到IP地址后,将其返回给用户的浏览器。
  8. 浏览器收到IP地址后,就可以通过这个IP地址访问www.google.com网站。

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

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

相关文章

AWS EC2的SSM配置(AWS云中的跳板机)

问题 开发人员需要访问AWS云中私有子网的数据库服务等&#xff0c;都需要通过EC2进行SSH隧道代理。这里假设本地已经有一款稳定优秀的SSH客户端工具&#xff0c;并且假设已经会熟练使用SSH的隧道代理。 1.创建EC2 搜索找到EC2服务&#xff0c;如下图&#xff1a; 点击“启动…

Qt QSQlite数据库插入字符串中存在单个双引号或单个单引号解决方案

1. 前言 当进行数据库写入或更新时&#xff0c;有时会遇到存在字符串中包含单个双引号或者单引号。 2. 单引号和双引号""作用 在数据库中&#xff0c;字符串常量时需要用一对英文单引号或英文双引号""将字符串常量括起来。 比如&#xff1a; select * …

2024年 13款 Linux 最强视频播放器

Linux视频播放器选择多样&#xff0c;如榛名、MPlayer、VLC等&#xff0c;功能强大、支持多格式&#xff0c;满足各类用户需求 Linux有许多非常强大的播放器&#xff0c;与windows最强视频播放器相比&#xff0c;几乎丝毫不逊色&#xff01; 一、榛名视频播放器 榛名视频播放…

分布式事务:构建无障碍的云原生应用的完美解决方案

目录 一、前言 二、分布式事务概述 2.1 什么是分布式事务 2.2 分布式事务的挑战 2.3 分布式事务的分类 三、传统解决方案分析 3.1 两阶段提交协议&#xff08;2PC&#xff09; 3.2 三阶段提交协议&#xff08;3PC&#xff09; 3.3 补偿事务 3.4 其他传统解决方案 四…

深入分析 Spring 中 Bean 名称的加载机制

目录 前言 通过前文&#xff1a;《深入分析-Spring BeanDefinition构造元信息》一文我们可以了解到&#xff1a;Spring Framework共有三种方式可以定义Bean&#xff0c;分别为&#xff1a;XML配置文件、注解、Java配置类&#xff0c; 从Spring Framework 3.0&#xff08;2019年…

快速了解——逻辑回归及模型评估方法

一、逻辑回归 应用场景&#xff1a;解决二分类问题 1、sigmoid函数 1. 公式&#xff1a; 2. 作用&#xff1a;把 (-∞&#xff0c;∞) 映射到 (0&#xff0c; 1) 3. 数学性质&#xff1a;单调递增函数&#xff0c;拐点在x0&#xff0c;y0.5的位置 4. 导函数公式&#xff1a;f…

让java程序就像脚本一样去写工具

背景&#xff1a; 接触了各种语言之后发现&#xff0c;java还是比go&#xff0c;.netcore之类的简单&#xff0c;成熟&#xff0c;我最终选择了jenkinsshelljava去部署我们的代码&#xff0c;此时很多人可能去使用js或者python之类的去写部署逻辑&#xff0c;毕竟java每次打包…

时序预测 | Matlab实现EEMD-SSA-BiLSTM、EEMD-BiLSTM、SSA-BiLSTM、BiLSTM时序预测对比

时序预测 | Matlab实现EEMD-SSA-BiLSTM、EEMD-BiLSTM、SSA-BiLSTM、BiLSTM时间序列预测对比 目录 时序预测 | Matlab实现EEMD-SSA-BiLSTM、EEMD-BiLSTM、SSA-BiLSTM、BiLSTM时间序列预测对比预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现EEMD-SSA-BiLSTM、…

计算机缺失msvcr100.dll如何修复?分享五种实测靠谱的方法

在计算机系统的日常运行与维护过程中&#xff0c;我们可能会遇到一种特定的故障场景&#xff0c;即系统中关键性动态链接库文件msvcr100.dll的丢失。msvcr100.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序来说&#xff…

Open3D 获取点云坐标最值(17)

Open3D 获取点云坐标最值(17) 一、算法介绍二、算法实现1.代码2.结果人生天地间,忽如远行客 一、算法介绍 快速获取点云块,沿着 x y z 各方向的坐标最值,这些在点云处理中的应用范围是如此广泛,这也是点云最常被用到的关键信息,后续的很多算法都会设置到这一处理方法。…

云端绘影,让青玉案跃然眼前

编辑&#xff1a;阿冒 设计&#xff1a;沐由 “东风夜放花千树&#xff0c;更吹落&#xff0c;星如雨。宝马雕车香满路。凤箫声动&#xff0c;玉壶光转&#xff0c;一夜鱼龙舞。” 每每诵读这首《青玉案》&#xff0c;那种花灯耀眼、乐声盈耳的元夕盛况就会立刻浮现在脑海中&am…

【开源】基于JAVA+Vue+SpringBoot的校园电商物流云平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 商品数据模块2.3 快递公司模块2.4 物流订单模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 商品表3.2.2 快递公司表3.2.3 物流订单表 四、系统展示五、核心代码5.1 查询商品5.2 查询快递公司5.3 查…

java..类文件具有错误的版本 61.0, 应为 55.0 请删除该文件或确保该文件位于正确的类路径子目录中。

第一步先打开pom.xml配置文件&#xff0c;找到版本号那几行&#xff0c;这里需要更改版本号。 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/X…

一文读懂ERP、OMS、WMS、TMS(1/2)

目录 一、ERP、OMS、WMS、TMS的定义 1.1 ERP&#xff08;Enterprise Resource Planning&#xff09; 1.2 OMS&#xff08;Order Management System&#xff09; 1.3 WMS&#xff08;Warehouse Management System&#xff09; 1.4 TMS &#xff08;Transportation Manageme…

【SpringCloud】之网关应用(进阶使用)

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《SpringCloud开发之网关应用》。&#x1f3af;&a…

微机原理常考简答题(二)

一&#xff0c;简述8086CPU响应可屏蔽中断的条件及过程。 CPU响应可屏蔽中断的条件是有中断请求&#xff0c;中断标志IF1开中断&#xff0c;现行指令执行结束。 CPU响应可屏蔽中断的过程&#xff1a;CPU在INTR引脚上接到一个中断请求信号&#xff0c;如果此时IF1&#xff0c;并…

group by 查询慢的话,如何优化?

1、说明 根据一定的规则&#xff0c;进行分组。 group by可能会慢在哪里&#xff1f;因为它既用到临时表&#xff0c;又默认用到排序。有时候还可能用到磁盘临时表。 如果执行过程中&#xff0c;会发现内存临时表大小到达了上限&#xff08;控制这个上限的参数就是tmp_table…

Vue3函数式弹窗实现

要在一些敏感操作进行前要求输入账号和密码&#xff0c;然后将输入的账号和密码加到接口请求的header里面。如果每个页面都去手动导入弹窗组件&#xff0c;在点击按钮后弹出弹窗。再拿到弹窗返回的账号密码后去请求接口也太累了&#xff0c;那么有没有更简单的实现方式呢&#…

强化学习(一)简介

强化学习这一概念在历史上来源于行为心理学&#xff0c;来描述生物为了趋利避害而改变自己行为的学习过程。人类学习的过程其实就是为达到某种目的不断地与环境进行互动试错&#xff0c;比如婴儿学习走路。强化学习算法探索了一种从交互中学习的计算方法。 1、强化学习 强化学…

kylin3集群问题和思考(单机转集群)

目录 单机改集群注意事项 问题 思考 建议 单机改集群注意事项 之前是使用的单机版&#xff0c;但后面查询压力过大&#xff0c;一个方案是改成集群。 由于是同一个集群的&#xff0c;元数据没有变化&#xff0c;所以&#xff0c;直接将原本的kylin使用scp的方式发送到其他节…