【Netty】九、Netty自定义协议

news2024/11/28 7:44:49

Netty自定义协议

  • 一、Netty自定义协议
  • 二、 协议设计
  • 三、 协议实现
    • 编码:
    • 解码:
  • 时间轮算法
    • Netty中的时间轮

一、Netty自定义协议

公有协议(http、tcp)、私有协议(自己定义的,不是行业标准)
我们知道使用最为广泛的是HTTP协议,但是在一些服务交互领域,其使用则相对较少,主要原因有三方面:
1、HTTP协议会携带诸如header和cookie等信息,其本身对字节的利用率较低,这使得HTTP协议比较臃肿,在承载相同信息的情况下,HTTP协议将需要发送更多的数据包;
2、HTTP协议是基于TCP的短连接,在每次请求响应后就断开了连接,下次请求需再次建立新连接,由于服务端的交互设计一般都要求能够承载高并发的请求,因而HTTP协议性能不佳;
3、服务之间往往有一些根据其自身业务特性所独有的需求,而HTTP协议无法很好的服务于这些业务需求;
基于这些原因,一般的服务之间进行交互时都会使用自定义协议,常见的框架比如dubbo就实现了符合其自身业务需求的协议;

二、 协议设计

协议本质上是定义了一个将数据转换为字节或者将字节转换为数据的一个规范和格式,自定义协议一般包含两个部分:消息头和消息体;
消息头定义了消息的一些公有信息,比如当前服务的版本,消息的sessionId,消息的类型等等,消息头的长度一般是固定的或者说是可确定的;
消息体主要是消息所需要发送的内容,一般在消息头的最后的字节中保存当前消息的消息体的长度;
下面是设计一个自定义协议的举例:

1、魔数 magicNumber
2、主版本号 mainVersion
3、次版本号 subVersion
4、修订版本号 modifyVersion
5、会话id sessionId
6、消息类型 messageType
7、附加数据 attachments
8、消息体长度 length
9、消息体 data
对应到Java开发中,可以用一个类来承载这些信息:

public class Message {
    private int magicNumber;
    private byte mainVersion;
    private byte subVersion;
    private byte modifyVersion;
    private String sessionId;
    private MessageTypeEnum messageType;
    private Map<String, String> attachments = new HashMap<>();
    private Object data;
}

三、 协议实现

自定义协议就是根据定义的协议规范,将消息转换为相应的字节流,然后由TCP传输到目标服务器,目标服务器也根据同样的协议规范将字节流转换为相应的消息,这样就达到了相互交互通信的目的,如何基于该规范将消息转换为字节流和将字节流转换为消息?Netty提供了ByteToMessageDecoder和
MessageToByteEncoder用于进行消息和字节流的相互转换;

编码:

@Override
protected void encode(ChannelHandlerContext ctx, Message message, ByteBuf out) {
    System.out.println("报文编码前:\n" + message);
    // 判断消息类型,如果是EMPTY类型,则表示当前消息不需要写入到管道中
    if (message.getMessageType() != MessageTypeEnum.EMPTY) {
        out.writeInt(Constants.MAGIC_NUMBER);  // 写入当前的魔数
        out.writeByte(Constants.MAIN_VERSION); // 写入当前的主版本号
        out.writeByte(Constants.SUB_VERSION);  // 写入当前的次版本号
        out.writeByte(Constants.MODIFY_VERSION);   // 写入当前的修订版本号
        if (StringUtils.isNotEmpty(message.getSessionId())) {
            String sessionId = UUID.randomUUID().toString();
            message.setSessionId(sessionId);
            out.writeCharSequence(sessionId, Charset.defaultCharset());
        } else {
            //先暂时写死一个固定36位的uuid
            message.setSessionId("fdda50c1-c483-4395-8f84-6cb692b8664c");
            out.writeCharSequence("fdda50c1-c483-4395-8f84-6cb692b8664c", Charset.defaultCharset());
        }
        out.writeByte(message.getMessageType().getType()); // 写入当前消息的类型
        out.writeShort(message.getAttachments().size());   // 写入当前消息的附加参数数量
        message.getAttachments().forEach((key, value) -> {
            Charset charset = Charset.defaultCharset();
            out.writeInt(key.length());    // 写入键的长度
            out.writeCharSequence(key, charset);   // 写入键数据
            out.writeInt(value.length());  // 希尔值的长度
            out.writeCharSequence(value, charset); // 写入值数据
        });
        if (null == message.getData()) {
            out.writeInt(0);   // 如果消息体为空,则写入0,表示消息体长度为0
        } else {
            byte[] bytes = SerializeUtil.serialize(message.getData());
            out.writeInt(bytes.length);
            out.writeBytes(bytes);
        }
    }
}

解码:

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
    Message message = new Message();
    message.setMagicNumber(byteBuf.readInt());  // 读取魔数
    message.setMainVersion(byteBuf.readByte()); // 读取主版本号
    message.setSubVersion(byteBuf.readByte()); // 读取次版本号
    message.setModifyVersion(byteBuf.readByte());  // 读取修订版本号
    CharSequence sessionId = byteBuf.readCharSequence(Constants.SESSION_ID_LENGTH, Charset.defaultCharset());  // 读取sessionId
    message.setSessionId((String)sessionId);
    message.setMessageType(MessageTypeEnum.get(byteBuf.readByte()));   // 读取当前的消息类型
    short attachmentSize = byteBuf.readShort();    // 读取附件长度
    for (short i = 0; i < attachmentSize; i++) {
        int keyLength = byteBuf.readInt(); // 读取键长度和数据
        CharSequence key = byteBuf.readCharSequence(keyLength, Charset.defaultCharset());
        int valueLength = byteBuf.readInt();   // 读取值长度和数据
        CharSequence value = byteBuf.readCharSequence(valueLength, Charset.defaultCharset());
        message.addAttachment(key.toString(), value.toString());
    }
    int dataLength = byteBuf.readInt();    // 读取消息体长度和数据
    byte[] dataBytes = new byte[dataLength];
    byteBuf.readBytes(dataBytes, 0, dataLength);
    message.setData(SerializeUtil.unserialize(dataBytes));
    System.out.println("报文解码后:\n" + message);
    out.add(message);
}

时间轮算法

时间轮算法是用环形数组实现的,数组的每个元素称为槽,槽的内部用双向链表存储待执行的任务,添加和删除链表里面的任务的时间复杂度都是 O(1),槽本身也代表时间精度,比如一秒扫一个槽,那么这个时间轮的最高精度就是1秒,也就是说延迟1.5秒的任务和1.8秒的任务会被加入到同一个槽中,然后在1秒的时候遍历这个槽中的链表执行任务;
在这里插入图片描述
上图中指针指向的是第一个槽,一共有八个槽0~7,假设槽的时间单位为1秒,现在要加入一个延时5秒的任务,计算方式就是 5 % 8 + 1 = 6,即放在槽位为 6,下标为 5 的那个槽中,如果下标为5的槽已经有任务,则把任务拼到槽的双向链表的尾部;
然后每秒指针顺时针移动一格,这样就扫到了下一格,遍历这格中的双向链表执行任务,然后再循环依次继续;
如果要加入一个延迟50秒后执行的任务怎么办?
常见有两种处理方式,一种是增加轮次,50 % 8 + 1 = 3,即应该放在槽位是3,下标是2的位置,然后 (50 - 1) / 8 = 6,即轮数记为 6,也就是说当循环6轮之后扫到下标为2的这个槽位会触发该任务,Netty中的 HashedWheelTimer 使用的就是这种方式;
另外一种是增加层次,与手表相似,秒针走一圈,分针走一格,分针走一圈,时针走一格,多层次时间轮就是这样实现的,假设上图就是第一层,那么第一层走了一圈,第二层就走一格;
由此可知第二层的一格就是8秒,假设第二层也是 8 个槽,那么第二层走一圈,第三层走一格,可以得知第三层一格就是64秒,那么整个时间轮就可以处理最多延迟512秒的任务;
在这里插入图片描述
而多层次时间轮还会有降级的操作,假设一个任务延迟 500 秒执行,那么刚开始加进来肯定是放在第三层的,当时间过了 436 秒后,此时还需要 64 秒就会触发任务的执行,而此时相对而言它就是个延迟 64 秒后的任务,因此它会被降低放在第二层中,第一层还放不下它,再过个 56 秒,相对而言它就是个延迟 8 秒后执行的任务,因此它会再被降级放在第一层中等待执行,Kafka内部用的就是多层次的时间轮算法;
多层次时间轮算法相对比较复杂,Netty中没有使用这种方式;

Netty中的时间轮

在Netty中时间轮的实现类是HashedWheelTimer,代码中的wheel 就是上图的循环数组,tickDuration就是数组每一格的时间即精度,可以看到配备了一个工作线程来处理任务的执行;
通过源码分析,Netty的时间精度由 TickDuration 把控,并且工作线程除了处理执行到时的任务还做了其他操作,因此任务不一定会被精准的执行,并且任务的执行如果不是新起一个线程,或者将任务扔到线程池执行,那么耗时的任务会阻塞下个任务的执行;
另外Netty的时间轮算法会有很多无用的 tick 推进,例如 TickDuration 为1秒,此时一个延迟350秒的任务,那就是有349次无用的操作;
Netty时间轮的特点是对定时任务的执行不一定的精确的,适合于任务执行时间较短的情况,如果任务要执行的时间比较长,那么netty的时间轮执行任务会有时间上的误差,因为netty执行任务是单线程的,不是线程池,这样设计是为了大幅度减少维护定时任务的线程数;
使用Netty时间轮值需要创建和维护一个全局(一个服务)单例的HashedWheelTimer对象就可以了,不能在每个客户端连接上都创建一个;
HashedWheelTimer的底层数据结构是一个叫wheel的数组,时间轮的默认大小是512,即wheel数组默认512大小,数组的元素位置就是时间轮里所谓的槽位,可以配置槽位的时间间隔,Netty默认的配置是一个槽位代表100ms,而针对网络应用程序,大部分情况下,不需要额外定制这个时间参数,因为网络I/O的定时任务调度对时间的精确度要求没有那么高,本身网络就是不稳定的,所以这里你不需要额外配置什么,但是当定时任务是海量的时候,可以配置时间轮本身的大小,计算这个wheel大小,以减少时间轮的扫描轮数;
HashedWheelBucket 数组
HashedWheelTimeout 链表

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

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

相关文章

[Qt]QMainWindow

目录 1.基本概述 2.菜单栏 3.工具栏 4.状态栏 5.铆接部件 6.中心部件 7.资源文件 &#xff08;1&#xff09;创建菜单栏&#xff0c;及菜单项 (2)创建工具栏 (3)创建锚接部件 (4)创建中心文件 &#xff08;5&#xff09;创建状态栏 1.基本概述 QMainWindow是一个为…

腾讯网关TGW基础原理入门

本文是在组内技术分享的发言稿&#xff0c;主要介绍 TGW 基本原理和架构&#xff0c;同时为了加深理解&#xff0c;会辅助对比 TGW 与 LVS&#xff08;ipvs&#xff09;的异同。 本次分享是偏基础性的 TGW 介绍&#xff0c;不会特别深入技术细节&#xff0c;目的是帮助需要用到…

算法7:迪杰斯特拉算法

目录1. 应用场景-最短路径问题2. 迪杰斯特拉(Dijkstra)算法介绍3. 迪杰斯特拉(Dijkstra)算法过程4. 算法分析过程5. 代码实现1. 应用场景-最短路径问题 看一个应用场景和问题 胜利乡有7个村庄(A, B, C, D, E, F, G) &#xff0c;现在有六个邮差&#xff0c;从G点出发&#xff…

贪吃蛇OneDay

环境 配置git环境 安装Git Bash&#xff08;使用Mac和Linux的同学可以跳过这一步&#xff09;&#xff1a;https://gitforwindows.org/ 进入家目录生成秘钥&#xff1a;执行命令ssh-keygen 在Ac Git上注册账号&#xff0c;地址&#xff1a;https://git.acwing.com/ 将id_rsa.p…

Unity中的AssetBundle

AssetBundle的概念 AssetBundle又称AB包&#xff0c;是Unity提供的一种用于存储资源的资源压缩包&#xff0c;是对Unity 初始Resources的一种扩展&#xff1b;一般使用的策略是把必须的资源和不需要更新的资源放在Resources文件夹下&#xff0c;其他的资源放在AssetBundle下面…

【微信小程序】flex布局

&#x1f3c6;今日学习目标&#xff1a;flex布局 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;20分钟 &#x1f389;专栏系列&#xff1a;微信小程序开发 文章目录前言Flex布局什么是flex&#xff1f;flex-direc…

Hive中内部表、外部表、分区表、分桶表之间的关系

文章目录Hive中内部表、外部表、分区表、分桶表之间的关系1.0&#x1f680;内部表2.0&#x1f440;外部表3.0&#x1fae5;内部表和外部表的差异3.0&#x1f418;分区表4.0&#x1f603;分桶表Hive中内部表、外部表、分区表、分桶表之间的关系 1.0&#x1f680;内部表 内部表&…

HEAD: HEtero-Assists Distillation for Heterogeneous Object Detectors

ECCV 2022 异质辅助蒸馏 Abstract Conventional knowledge distillation (KD) methods for object detection mainly concentrate on homogeneous teacher-student detectors. However, the design of a lightweight detector for deployment is often significantly differ…

计算机毕业设计(附源码)python智能仓储进出货管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

web前端期末大作业:基于HTML+CSS+JavaScript制作我的音乐网站(带设计报告)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【python】都2022年不会还有人不会在电脑桌面上养宠物吧~

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ! 上班枯燥&#xff0c;对着冷冰冰的电脑&#xff0c;相信很多小伙伴即使摸鱼&#xff0c;心情也不愉快。 这时如果有个萌宠能大家进行实时互动&#xff0c;这该有多好呀。再无聊的工作也能增添那么一丝趣味。 今天博主就来给大…

2、Ubuntu下安装Vivado下的下载器驱动 Digilent 版本

简介 在Ubuntu下安装Vivado时&#xff0c;安装工具会提醒你&#xff0c;digilent驱动无法自动安装&#xff0c;需要手动安装&#xff0c;并且让用户参考UG973手册安装。 由于安装驱动很简单&#xff0c;不用麻烦大家去找手册了&#xff0c;这里直接给出安装方法 安装方法 …

【Pytorch Lighting】第 6 章:深度生成模型

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

FPGA HLS 卷积单元 数据类型hls优化约束设置

数据类型 自定义精度整形&#xff1a; ap_int<4> in1, in2; ap_int<8> concat; concat (in1, in2); // in1和in2拼起来&#xff08;按照补码拼起来&#xff09; /* 例子&#xff1a; in1 1, in2 -1 补码&#xff1a; in1 0001 in2 1001 > 11101 > 1…

Spring mvc处理异常

文章目录一、Handler ExceptionResolver处理异常二、ExceptionHandler注解三、重点&#xff1a;添加ExceptionHandler注解方法的形参只能是异常类型四、重点2&#xff1a;捕获所有方法的异常—ControllerAdvice注解五、总结六、ResponseStatusExceptionResolve自定义异常显示页…

[go学习笔记.第十一章.项目案例] 1.家庭收支记账软件项目

一.基本介绍 1.项目开发流程说明 2.项目需求说明 目标: 模拟实现一个基于文本界面的<<家庭记账软件>> 掌握初步的编程技巧和调试技巧 主要涉及以下知识点 : (1).局部变量和基本数据类型 (2).循环语句 (3).分支语句 (4).简单的屏幕输出格式控制 (5).进阶&#xff1…

刷题日记【第九篇】-笔试必刷题【杨辉三角的变形+计算某字符出现的次数+字符串通配符+统计每个月兔子的总数】

下列sql语句中哪条语句可为用户zhangsan分配数据库userdb表userinfo的查询和插入数据权限&#xff08;A&#xff09;。 常用的管理权限的命令为&#xff1a; grant select/insert/update/delete on 数据库名.表名 to 用户名‘该用户允许访问的ip’ 在oracle中&#xff0c;下面哪…

世界上只有一种共识算法,那就是Paxos

分布式系统原理系列目录 分布式系统的麻烦副本与一致性为什么需要一个分布式共识算法世界上只有一种共识算法&#xff0c;那就是PaxosCAP定理&#xff0c;说起来一句话&#xff0c;实际坑不少BASE&#xff0c;可用性高于强一致性分布式事务方案那么多&#xff0c;到底该选哪一…

计算机毕业设计(附源码)python智慧灭火器管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

大数据学习3.1 Hadoop环境准备

Hadoop环境Hadoop集群拓扑1、集群拓扑2、角色分配一、虚拟机安装二、虚拟机克隆1、克隆类型&#xff08;1&#xff09;完整克隆&#xff08;2&#xff09;链接克隆2、克隆步骤&#xff08;1&#xff09;克隆出master虚拟机&#xff08;2&#xff09;克隆出slave1虚拟机&#xf…