一个 Dubbo Triple 协议的 "Bug"

开发 前端
在使用 Dubbo-go 客户端调用 Dubbo-java 服务时,发现通过 context 设置的 attachment 中,"remote.application" 键值对在服务端无法获取,而 "remote.application1" 和 "remote.application2" 可以正常获取。

问题描述

近期涂鸦智能的筒子反馈:dubbogo 客户端使用 Triple 协议发往 dubbo (Java) 服务端的某些 attachment 会发送丢失现象,并报了 issue:

https://github.com/apache/dubbo-go/issues/2752

问题简述:服务端无法从附件中获取键名为 remote.application 的值。

具体来说,在使用 Dubbo-go 客户端调用 Dubbo-java 服务时,发现通过 context 设置的 attachment 中,"remote.application" 键值对在服务端无法获取,而 "remote.application1" 和 "remote.application2" 可以正常获取。

这里先给出结论:这不是一个 bug,之所以 attachment 中 key 为 "remote.application" 的 item 在 dubbo server 端被过滤掉,是因为该 key 是 dubbo(Java) attachment 中的保留字段,不允许用户使用。

下面给出问题复现,以及问题分析过程。

环境准备

  • 服务端:Dubbo-Java v3.3.0
  • 客户端:Dubbo-go v3.2.0-rc2
  • 协议:triple
  • 注册中心:Zookeeper

参考:https://github.com/apache/dubbo-go-samples

  • 代码:Dubbo java and go interoperability, protobuf and triple protocol
  • 文档:基于 protobuf 实现 triple 协议互通(适用于两边都用 protobuf 开发的场景)

Client 端

先下载 dubbo-go-samples 工程,在工程根目录下执行如下命令,以更新 dubbo-go-sample 依赖的 dubbogo 版本。

$ go get dubbo.apache.org/dubbo-go/v3@v3.2.0-rc2

java_interop/protobuf-triple/go/go-client/cmd/client.go:

package main


import (
	"context"
	"dubbo.apache.org/dubbo-go/v3/client"
	"dubbo.apache.org/dubbo-go/v3/common/constant"
	_ "dubbo.apache.org/dubbo-go/v3/imports"
	greet "github.com/apache/dubbo-go-samples/java_interop/protobuf-triple/go/proto"
	"github.com/dubbogo/gost/log/logger"
)
func main() {
	cli, err := client.NewClient(
		client.WithClientURL("127.0.0.1:50052"),
	)
	if err != nil {
		panic(err) // If there's an error, it's handled immediately by panicking.
	}
	svc, err := greet.NewGreeter(cli)
	if err != nil {
		panic(err) // Same here, handle the error immediately.
	}
	ctx := context.Background()
	ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{
		"remote.application":  "appname",
		"remote.application1": "appname",
		"remote.application2": "appname",
	})
	resp, err := svc.SayHello(ctx, &greet.HelloRequest{Name: "hello world"})
	if err != nil {
		logger.Error(err)
		return // Now, we explicitly handle the error by logging it and then returning from the function.
	}
	logger.Infof("Greet response: %s", resp.Message)
}

Server 端

java_interop/protobuf-triple/java/java-server/pom.xml:

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <dubbo.version>3.3.0</dubbo.version>
</properties>

java_interop/protobuf-triple/java/java-server/src/main/java/org/apache/dubbo/sample/GreeterImpl.java:

package org.apache.dubbo.sample;
import com.alibaba.fastjson2.JSON;
import org.apache.dubbo.rpc.RpcContext;

import java.util.Map;

public class GreeterImpl extends DubboGreeterTriple.GreeterImplBase {
    @Override
    public HelloReply sayHello(HelloRequest request) {
        Map<String, Object> serverAttachments = RpcContext.getServerAttachment().getObjectAttachments();
        System.out.println("ContextService serverAttachments:" + JSON.toJSONString(serverAttachments));
        return HelloReply.newBuilder()
                .setMessage(request.getName())
                .build();
    }
}

运行

Go client 调用 java server

  • java-server 目录中:
$ ./run.sh
...
Dubbo triple java server started
  • go-client 目录中:
$ go run cmd/client.go
  • 结果输出

服务端:

2月 12, 2025 6:26:30 下午 org.apache.dubbo.common.logger.jdk.JdkLogger info
信息:  [DUBBO] The connection [id: 0x156ed855, L:/127.0.0.1:50052 - R:/127.0.0.1:56961] of 127.0.0.1:56961 -> 127.0.0.1:50052 is established., dubbo version: 3.3.0, current host: 10.60.200.103
ContextService serverAttachments:{"retries":"","remote.application1":"appname","remote.application2":"appname"}
2月 12, 2025 6:26:30 下午 org.apache.dubbo.common.logger.jdk.JdkLogger info
信息:  [DUBBO] The connection [id: 0x156ed855, L:/127.0.0.1:50052 ! R:/127.0.0.1:56961] of 127.0.0.1:56961 -> 127.0.0.1:50052 is disconnected., dubbo version: 3.3.0, current host: 10.60.200.103
2月 12, 2025 6:26:30 下午 org.apache.dubbo.common.logger.jdk.JdkLogger warn
警告:  [DUBBO] All clients has disconnected from /127.0.0.1:50052. You can graceful shutdown now., dubbo version: 3.3.0, current host: 10.60.200.103, error code: 99-0. This may be caused by unknown error in remoting module, go to https://dubbo.apache.org/faq/99/0 to find instructions. 
2月 12, 2025 6:26:30 下午 org.apache.dubbo.common.logger.jdk.JdkLogger info
信息:  [DUBBO] The connection [id: 0x156ed855, L:/127.0.0.1:50052 ! R:/127.0.0.1:56961] of 127.0.0.1:56961 -> 127.0.0.1:50052 is disconnected., dubbo version: 3.3.0, current host: 10.60.200.103

客户端:

2025-02-12 18:26:29	INFO	logger/logging.go:42	URL specified explicitly 127.0.0.1:50052
2025-02-12 18:26:30	INFO	logger/logging.go:42	[TRIPLE Protocol] Refer service: tri://127.0.0.1:50052/org.apache.dubbo.sample.Greeter?app.version=&application=dubbo.io&async=false&bean.name=org.apache.dubbo.sample.Greeter&cluster=failover&config.tracing=&environment=&generic=&group=&interface=org.apache.dubbo.sample.Greeter&loadbalance=&metadata-type=local&module=sample&name=dubbo.io&organization=dubbo-go&owner=dubbo-go&peer=true&provided-by=&reference.filter=cshutdown®istry.role=0&release=dubbo-golang-3.2.0&remote.timestamp=&retries=&serialization=protobuf&side=consumer&sticky=false×tamp=1739355989&version=
2025-02-12 18:26:30	INFO	logger/logging.go:42	Greet response: hello world

注意到 java-server 无法从 go-server 传递的 attachment 获取 "remote.application" 键。

问题分析

在这个场景中涉及到 ClientAttachment 和 ServerAttachment:

  • ClientAttachmen:Go 客户端写入参数
  • ServerAttachment:Java 服务端读取参数

调用流程:

  • client 端:Method Invoke 发起调用 $\rightarrow$ 写入 RpcClientAttachment $\rightarrow$ 封装进 Invocation $\rightarrow$ 序列化传输
  • server 端:解析 Invocation $\rightarrow$ 生成 Method Invoke 参数和 RpcServerAttachment $\rightarrow$ 真实调用

$\rightarrow$ 处理后生成 Result(包含 Response 和 Context)$\rightarrow$ 序列化返回。

参考 官方文档:

图片

Triple 协议会将 attachment 转换为 HTTP header 传输,使用 Wireshark 抓包分析,确认 "remote.application" 确实被正确封装在 HTTP header 中,说明 Go 客户端的 ClientAttachment → Invocation → 网络传输链路是正常的。

关注 Java 服务端的 org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener 服务端是请求处理的核心类,负责接收和处理 HTTP 请求,并构建 RPC 调用对象。

// AbstractServerTransportListener.java
protected RpcInvocation buildRpcInvocation(RpcInvocationBuildContext context) {
    // ...
    // 关键转换点:将 HTTP Header 转为 RPC Attachment
    inv.setObjectAttachments(StreamUtils.toAttachments(httpMetadata.headers()));
    // ...
}

调用 StreamUtils.toAttachments() 之前,Header 中是包含 "remote.application" 的。

调用 StreamUtils.toAttachments() 之后,RpcInvocation 类型对象 inv 的成员变量 Map<String,Object>attachments 不包含 "remote.application"。

StreamUtils#toAttachments(HttpHeaders headers):

/**
 * Parse and convert headers to attachments. Ignore Http2 PseudoHeaderName and internal name
 *
 * @param headers the headers
 * @return the attachments
 */
public static Map<String, Object> toAttachments(HttpHeaders headers) {
    if (headers == null) {
        return Collections.emptyMap();
    }
    Map<String, Object> attachments = CollectionUtils.newHashMap(headers.size());
    for (Map.Entry<CharSequence, String> entry : headers) {
        String key = entry.getKey().toString();
        String value = entry.getValue();
        int len = key.length() - TripleConstants.HEADER_BIN_SUFFIX.length();
        if (len > 0 && TripleConstants.HEADER_BIN_SUFFIX.equals(key.substring(len))) {
            try {
                putAttachment(attachments, key.substring(0, len), value == null ? null : dec
            } catch (Exception e) {
                LOGGER.error(PROTOCOL_FAILED_PARSE, "", "", "Failed to parse response attach
            }
        } else {
            putAttachment(attachments, key, value);
        }
    }
    // try converting upper key
    String converted = headers.getFirst(TripleHeaderEnum.TRI_HEADER_CONVERT.getKey());
    if (converted == null) {
        return attachments;
    }
    String json = TriRpcStatus.decodeMessage(converted);
    Map<String, String> map = JsonUtils.toJavaObject(json, Map.class);
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String key = entry.getKey();
        Object value = attachments.remove(key);
        if (value != null) {
            putAttachment(attachments, entry.getValue(), value);
        }
    }
    return attachments;
}

StreamUtils#putAttachment(Map<String, Object> attachments, String key, Object value):

图片

进一步查看 TripleHeaderEnum :

图片

为了防止业务层误用或覆盖这个键值,Dubbo 将其加入了排除列表,导致即使在 attachment 中设置了这个键值对,在转换时也会被过滤掉。

责任编辑:姜华 来源: dubbogo示土区
相关推荐

2022-04-06 08:47:03

Dubbo服务协议

2009-09-14 17:08:02

WebFormView

2021-10-08 07:50:57

软件设计程序

2017-10-10 15:14:23

BUGiOS 11苹果

2024-09-14 14:14:26

Dubbo框架微服务

2014-12-17 09:40:22

dockerLinuxPaaS

2024-04-22 00:00:01

Redis集群

2022-05-16 08:42:26

Pandasbug

2022-06-15 08:14:40

Go线程递归

2015-08-24 10:07:13

程序员bug

2019-08-01 12:59:21

Bug代码程序

2023-03-13 08:09:03

Protobuffeature分割

2018-06-19 16:04:27

Dubbo应用Java

2024-08-08 08:09:38

2011-03-03 21:04:08

bug程序员

2021-09-11 19:00:54

Intro元素MemoryCache

2015-08-19 09:29:35

Git协议编写

2017-03-22 09:11:45

bugbug赏金计划众包项目

2010-11-17 15:43:55

软件测试Bug

2013-06-18 11:37:42

XFSRHEL 6.4
点赞
收藏

51CTO技术栈公众号