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

前言

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.hqwc.cn/news/139197.html

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

相关文章

华为eNSP配置专题-ACL的配置

文章目录 华为eNSP配置专题-ACL的配置1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、基本终端构成和连接2.2、各终端基本配置2.2.1、PC1和PC2的配置2.2.2、模拟互联网的路由器的配置2.2.3、财务部服务器的配置2.2.4、路由器AR1的配置 2.3、让各网段能够ping通互联…

IDEA如何设置项目包名分级

按上面的勾选即可&#xff01;

win 10怎么录屏?教你轻松捕捉屏幕活动

在当今科技快速发展的时代&#xff0c;录屏已成为信息分享、教学、游戏直播等方面的重要工具。无论是为了制作教程、分享游戏过程还是保存重要信息&#xff0c;录屏功能都发挥着举足轻重的作用。可是很多人不知道win 10怎么录屏&#xff0c;本文将详细介绍win10的三种常用录屏方…

Java前后端分离项目中跨域问题 讲解

Java前后端分离项目中跨域问题 讲解 前言什么是跨域问题&#xff1f;CORS解决跨域问题使用Servlet过滤器使用Spring Framework的CrossOrigin注解 总结 我是将军我一直都在&#xff0c;。&#xff01; 前言 当在Java前后端分离项目中工作时&#xff0c;跨域问题是一个常见的挑战…

Spring中Setter注入详解

目录 一、setter注入是什么 二、setter注入详解 三、JDK内置类型注入方式 3.1 数组类型 3.2 set集合类型 3.3 list集合 3.4 map集合 3.5 properties类型 四、用户自定义类型 一、setter注入是什么 书接上回&#xff0c;我们发现在Spring配置文件中为类对象的属性赋值时&#x…

WebSocket学习笔记

一篇文章理解WebSocket原理 1.HTTP协议(半双工通信)&#xff1a; HTTP是客户端向服务器发起请求&#xff0c;服务器返回响应给客户端的一种模式。 特点&#xff1a; 1.只能是客户端向服务器发起请求&#xff0c;是单向的。 2.服务器不能主动发送数据给客户端。 半双工通信…

二叉树的最小深度(rust实现)

二叉树的最小深度 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 思路 这道题本质上是考察&#xff1a; 如何遍历二叉树理解什么是深度 对于这两点的考察&am…

欧盟反垄断法的改变:对跨境电商的冲击和机遇

2024年&#xff0c;欧盟反垄断法将经历一场革命性的改变&#xff0c;这对于跨境电商来说是一个重大的法规转折点。长达数十年的联合体集体豁免条例&#xff08;CBER&#xff09;即将失效。 这意味着货运公司将不再享受欧盟针对反竞争协议规则的特殊待遇。这一法规的变革将对跨…

探索低代码技术

低/无代码的高速发展&#xff0c;属于软件市场的选择&#xff0c;相较于传统编写代码的开发方式&#xff0c;低/无代码开发效率高、投入成本低、技术门槛也更低&#xff0c;未来更多软件应用将使用低/无代码技术完成&#xff0c;这也是趋势。 身为开发人员经常需要花大量时间在…

智慧公厕管理系统:科技赋能城市公共卫生服务的便利

在现代社会的城市化进程中&#xff0c;公共设施的管理变得越来越重要。而公厕作为城市公共设施的重要组成部分&#xff0c;也需要借助科技的力量进行管理和监控。智慧公厕管理系统应运而生&#xff0c;它为公厕管理人员提供了实时监控和数据统计分析的功能&#xff0c;大大提高…

Nginx正向代理,反向代理,负载均衡

Nginx正向代理&#xff0c;反向代理&#xff0c;负载均衡 Nginx当中有两种代理方式&#xff1a; 七层代理&#xff08;http协议&#xff09; 四层代理&#xff08;tcp/udp流量转发&#xff09; 七层代理&#xff1a;七层代理&#xff0c;代理的是http的请求和响应 客户端请求…

安防监控系统EasyCVR视频汇聚平台设备树收藏按钮的细节优化

视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#…