Triple协议的隐式参数传递过程

news2025/3/13 18:23:48

前言

Dubbo 框架的 RPC 调用除了可以传递正常的接口参数外,还支持隐式参数传递。
隐式参数的传递依赖 RpcContext 对象,它持有一个 Map 对象,消费者往 Map 里写入数据,客户端在发起 RPC 调用前会构建 RpcInvocation,然后把 RpcContext 里的 Map 数据拷贝到 RpcInvocation 的 attachments 属性,最后客户端把 RpcInvocation 序列化后再传输给服务端。

隐式参数传递的一个典型的应用场景:分布式链路追踪。给调用链生成一个 TraceID,由于 TraceID 与业务无关,放在请求参数里显然不合适,我们可以通过 attachments 进行传递。同时为了不侵入业务,我们可以实现一个 Filter 来统一处理。

在 Dubbo 3 中,RpcContext 被拆分为四大模块(ServerContext、ClientAttachment、ServerAttachment 和 ServiceContext)。
它们分别承担了不同的职责:

  • ServiceContext:在 Dubbo 内部使用,用于传递调用链路上的参数信息,如 invoker 对象等
  • ClientAttachment:在 Client 端使用,往 ClientAttachment 中写入的参数将被传递到 Server 端
  • ServerAttachment:在 Server 端使用,从 ServerAttachment 中读取的参数是从 Client 中传递过来的
  • ServerContext:在 Client 端和 Server 端使用,用于从 Server 端回传 Client 端使用,Server 端写入到 ServerContext 的参数在调用结束后可以在 Client 端的 ServerContext 获取到

image.png

Dubbo3 的 Triple 协议针对 attachments 的传输有改动。
dubbo 协议的处理方式是:Dubbo 会把 RpcInvocation 按照格式序列化,其中就包含 attachments,服务端反序列化后就能拿到 attachments。
Triple 协议的处理方式是:DATA Frame 只包含序列化后的请求参数,attachments 是不包含在内的。Triple 把 attachments 放到哪里去了呢???没错,在 Headers 里面。

源码分析

Triple 协议对应的客户端是 TripleInvoker,客户端在发起 RPC 调用前会先创建请求元数据对象 RequestMetadata,它除了没有实际的请求参数外,该有的都有了:

public class RequestMetadata {
    public AsciiString scheme;
    public String application;
    public String service;
    public String version;
    public String group;
    public String address;
    public String acceptEncoding;
    public String timeout;
    public Compressor compressor;
    public CancellationContext cancellationContext;
    public MethodDescriptor method;
    public PackableMethod packableMethod;
    public Map<String, Object> attachments;
    public boolean convertNoLowerHeader;
}

RequestMetadata 的构建依赖 RpcInvocation,很多数据都是从 RpcInvocation 拷贝过来的,attachments 就是。
RPC 调用就是客户端给服务端发送一段请求数据,Dubbo 会调用TripleClientCall#sendMessage()发送请求数据:

@Override
public void sendMessage(Object message) {
    if (canceled) {
        throw new IllegalStateException("Call already canceled");
    }
    // 先发送Headers帧,再发送Data帧
    if (!headerSent) {
        headerSent = true;
        stream.sendHeader(requestMetadata.toHeaders());
    }
    final byte[] data;
    try {
        data = requestMetadata.packableMethod.packRequest(message);
        stream.sendMessage(compress, compressed, false)
    }
}

实际的请求参数会放在 DATA Frame 里,在发送 DATA Frame 前必须先发送 HEADERS Frame。
RequestMetadata#toHeaders()会生成 DefaultHttp2Headers 对象,它是 Netty 对 HEADERS Frame 的封装。

public DefaultHttp2Headers toHeaders() {
    DefaultHttp2Headers header = new DefaultHttp2Headers(false);
    // 设置HTTP2 伪首部 & triple内置首部
    header.scheme(scheme)
        .authority(address)
        .method(HttpMethod.POST.asciiName())
        .path("/" + service + "/" + method.getMethodName())
        .set(TripleHeaderEnum.CONTENT_TYPE_KEY.getHeader(), TripleConstant.CONTENT_PROTO)
        .set(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
    setIfNotNull(header, TripleHeaderEnum.TIMEOUT.getHeader(), timeout);
    if (!"1.0.0".equals(version)) {
        setIfNotNull(header, TripleHeaderEnum.SERVICE_VERSION.getHeader(), version);
    }
    setIfNotNull(header, TripleHeaderEnum.SERVICE_GROUP.getHeader(), group);
    setIfNotNull(header, TripleHeaderEnum.CONSUMER_APP_NAME_KEY.getHeader(),
        application);
    setIfNotNull(header, TripleHeaderEnum.GRPC_ACCEPT_ENCODING.getHeader(),
        acceptEncoding);
    if (!Identity.MESSAGE_ENCODING.equals(compressor.getMessageEncoding())) {
        setIfNotNull(header, TripleHeaderEnum.GRPC_ENCODING.getHeader(),
            compressor.getMessageEncoding());
    }
    // 转换attachments,解决key大小写问题
    StreamUtils.convertAttachment(header, attachments, convertNoLowerHeader);
    return header;
}

StreamUtils#convertAttachment()会先转换 attachments,再写入 Headers。
为什么还要转换呢???

因为 HTTP2 规范里 Headers key 是不区分大小写的,但是 attachments key 是区分大小写的,如果不做处理,就乱套了。

看下 Dubbo 是怎么转换的:

  • 遍历 attachments,把 key 转换成小写
  • 判断 key 是否与 HTTP2 伪首部、Triple 内置的 key 冲突,冲突则忽略不传输
  • 校验 value 类型,只能传输 String、Number、Boolean、byte[],其中字节数组会被 Base64 编码
  • 把 key value 写入 Headers
  • 把转换后的 key 和转换前的 key 构建一个 JSON 串,写入 Headers,key = tri-header-convert,远端接收到以后,再把 key 转换回去即可
public static void convertAttachment(DefaultHttp2Headers headers,
                                     Map<String, Object> attachments,
                                     boolean needConvertHeaderKey) {
    if (attachments == null) {
        return;
    }
    Map<String, String> needConvertKey = new HashMap<>();
    for (Map.Entry<String, Object> entry : attachments.entrySet()) {
        String key = lruHeaderMap.get(entry.getKey());
        if (key == null) {
            final String lowerCaseKey = entry.getKey().toLowerCase(Locale.ROOT);
            lruHeaderMap.put(entry.getKey(), lowerCaseKey);
            key = lowerCaseKey;
        }
        // key的命名与 HTTP2伪首部、内部key 冲突则不会传输
        if (TripleHeaderEnum.containsExcludeAttachments(key)) {
            continue;
        }
        if (needConvertHeaderKey && !key.equals(entry.getKey())) {
            needConvertKey.put(key, entry.getKey());
        }
        // 转换写入Headers 只能传 String、Number、Boolean、byte[](Base64编码)
        final Object v = entry.getValue();
        convertSingleAttachment(headers, key, v);
    }
    /**
     * 因为http头部key是忽略大小写的 统一转小写发送
     * 但是attachments key是区分大小写的
     * 这里会映射转换前后的key,远端接收到再转换一下
     */
    if (!needConvertKey.isEmpty()) {
        String needConvertJson = JsonUtils.getJson().toJson(needConvertKey);
        headers.add(TripleHeaderEnum.TRI_HEADER_CONVERT.getHeader(), TriRpcStatus.encodeMessage(needConvertJson));
    }
}

尾巴

Dubbo3 的 Triple 协议会把隐式参数 attachments 通过 HTTP2 头部传输,受限于 HTTP2 协议本身,所以 attachments 只能传输 String、Number、Boolean 和 byte[],其中 byte[] 会先经过 Base64 编码再传输。
又因为 HTTP2 Headers key 是不区分大小写的,但 attachments key 是区分大小写的,所以 Dubbo 还要先对 attachments 做转换处理,先统一把 key 转换成小写,再写入一个转换前后 key 的映射关系,对方拿到以后再转换回来就好了。

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

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

相关文章

linux中如何配置静态ip模式?

对于Linux系统来说&#xff0c;静态IP模式是一种常见的网络配置方式。相比动态IP模式&#xff0c;静态IP模式可以提供更加稳定的网络连接和更好的网络性能。本文将介绍在Linux中如何配置静态IP模式。 一、准备工作 在开始配置之前&#xff0c;需要先做好以下准备工作&#xff1…

Python爬虫基础之Selenium详解

目录 1. Selenium简介2. 为什么使用Selenium&#xff1f;3. Selenium的安装4. Selenium的使用5. Selenium的元素定位6. Selenium的交互7. Chrome handless参考文献 原文地址&#xff1a;https://program-park.top/2023/10/16/reptile_3/ 本文章中所有内容仅供学习交流使用&…

k8s集群授权prometheus(集群外部署)

一、前言 在集群外部prometheus想要调用k8s集群的apiserver获取监控数据需要通过token和ca验证&#xff0c;在集群内部部署的prometheus就不会有这个情况&#xff0c;因为集群内部部署prometheus pod的时候就已经注入了访问集群的token和ca文件&#xff0c;所以以下就针对k8s集…

吴恩达深度学习笔记

B站看的视频&#xff0c;课太长了&#xff0c;180多节&#xff0c;但搬运的没有作业练习&#xff0c;最好找个能练习的 1&#xff0c;假设模型时&#xff0c;以前(2011版机器学习)用西塔代表参数组成的向量&#xff0c;现在用w代表参数组成的向量&#xff0c;b代表西塔0&#x…

Django REST Framework完整教程-RESTful规范-序列化和反序列数据-数据视图

文章目录 1.简介及安装2.案例模型2.1.创建模型2.2.安装mysql必要组件2.3.管理后台转中文2.4.启动后台 3.数据序列化4.RESTful规范4.1.协议、域名和版本4.2.uri(统一资源标识符)4.3.查增删改4.4.过滤信息&#xff08;Filtering&#xff09;4.5.状态码&#xff08;Status Codes&a…

Prometheus-Grafana

Grafana可对Prometheus实现可视化操作。prometheus-grafana提供了一个可运行的环境用于对测试网络进行实时监控。prometheus-grafana下有一个docker-compose.yaml文件用于控制prometheus和grafana的启动&#xff0c;和监控网络的指标。 配置需求&#xff1a; 推荐使用linux系统…

外汇天眼;VT Markets 赞助玛莎拉蒂MSG Racing电动方程式世界锦标赛

随着国际汽联电动方程式世界锦标赛第十赛季的到来&#xff0c;外汇经纪商 VT Markets 和玛莎拉蒂 MSG Racing 宣布了一项为期多年的全球合作。 外汇天眼温馨提醒&#xff1a;在做外汇交易之前&#xff0c;一定要审核清楚外汇平台的资质以及官网信息&#xff0c;以防上当受骗&am…

大模型Agent最新论文及源码合集,覆盖构建、应用、评估

人们对于通用人工智能&#xff08;AGI&#xff09;的追求可以追溯到1950 年代中期&#xff0c;当时的AI研究者对机器拥有人类思维能力抱有很高的期望&#xff0c;但是随着研究的深入&#xff0c;他们发现想实现这个目标比最初设想的困难许多。到如今&#xff0c;AGI仍然有很长的…

【计算机网络】网络原理

目录 1.网络的发展 2.协议 3.OSI七层网络模型 4.TCP/IP五层网络模型及作用 5.经典面试题 6.封装和分用 发送方(封装) 接收方(分用) 1.网络的发展 路由器&#xff1a;路由指的是最佳路径的选择。一般家用的是5个网口&#xff0c;1个WAN口4个LAN口(口&#xff1a;端口)。可…

JDK 21的新特性总结和分析

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

[正式学习java②]——数组的基本使用,java内存图与内存分配

一、数组的两种初始化方式 1.完整格式(静态初始化) 数据类型[] 数组名 new 数据类型[]{元素1,元素2…}; //范例 int[] arr new int[]{1,2,3,4}; 简化书写 一般我们会省略后面的 new 数据类型[] int[] arr {1,2,3,4}; 2.动态初始化 当不知道数组里面的初始值的时候&#xf…

AI绘画使用Stable Diffusion(SDXL)绘制玉雕风格的龙

一、引言 灵感来源于在逛 LibLib 时&#xff0c;看到的 Lib 原创者「熊叁gaikan」发布的「翠玉白菜 sdxl&#xff5c;玉雕风格」 的 Lora 模型。简直太好看了&#xff0c;一下子就被吸引了&#xff01; 科普下「翠玉白菜」&#xff1a; 翠玉白菜是由翠玉所琢碾出白菜形状的清…

四川天蝶电子商务有限公司抖音电商服务引领行业标杆

随着电子商务的飞速发展&#xff0c;四川天蝶电子商务有限公司作为一家领先的抖音电商服务提供商&#xff0c;已经脱颖而出。本文将详细解析四川天蝶电子商务有限公司的抖音电商服务&#xff0c;让您一探究竟。 一、卓越的服务理念 四川天蝶电子商务有限公司始终坚持以客户为中…

微前端三:qiankun 协作开发和上线部署

我们先看qiankun怎么上线部署&#xff1a; 我这边用的是yaml 文件在 rancher上部署的&#xff1a; base是基座&#xff0c;这里每个应用都是一个服务&#xff0c;这个还是跟之前一样并没有区别&#xff0c;那如何在一个域名上挂载多个服务呢&#xff1f; 最开始我们主要是在in…

【调度算法】NSGA II

简介 NSGA-II&#xff08;Nondominated Sorting Genetic Algorithm II&#xff09;是一种经典的多目标优化算法&#xff0c;由Srinivas和Deb于2000年在NSGA的基础上提出&#xff0c;用于解决多目标优化问题。相较于NSGA&#xff0c;NSGA-II在运行速度和解集的收敛性上表现更好…

nginx的优先级和匹配方式

Nginx的location的优先级和匹配方式&#xff1a; 在http模块当中有server&#xff0c;在server模块才有location&#xff0c;location匹配的是uri /test /image 在一个server当中有多个location&#xff0c;如何来确定匹配那个location Nginx支持正则表达式&#xff1a; ^…

PLC 学习day01 了解PLC 的组成和知识。

1.资料来源 链接&#xff1a;3.三菱PLC编程视频关于PLC工作原理的介绍_哔哩哔哩_bilibili 2. PLC 的知识 2.1 PLC 的概述及特点功能 PLC是可编程逻辑控制器&#xff08;Programmable Logic Controller&#xff09;的英文缩写&#xff0c;是融合了继电器控制功能和计算机运算功…

补体C3/C4(C3/C4)介绍

补体是一种血清蛋白质&#xff0c;存在于人和脊椎动物血清及组织液中&#xff0c;不耐热&#xff0c;活化后具有酶活性、可介导免疫应答和炎症反应。可被抗原-抗体复合物或微生物所激活&#xff0c;导致病原微生物裂解或被吞噬。可通过三条既独立又交叉的途径被激活&#xff0c…

DNS(二)

实现 Internet DNS 架构 架构图 实验环境 关闭SELinux、Firewalld。时间保持一致 主机名IP角色client192.168.28.146DNS客户端&#xff0c;DNS地址为192.168.28.145localdns192.168.28.145本地DNS服务器&#xff08;只缓存&#xff09;forward192.168.28.144转发目标DNS服务…

TARJAN复习 求强连通分量、割点、桥

TARJAN复习 求强连通分量、割点、桥 文章目录 TARJAN复习 求强连通分量、割点、桥强连通分量缩点桥割点 感觉之前写的不好&#xff0c; 再水一篇博客 强连通分量 “有向图强连通分量&#xff1a;在有向图G中&#xff0c;如果两个顶点vi,vj间&#xff08;vi>vj&#xff09;有…