Dubbo RPCContext存储一些通用数据,这个用手动清除吗?

Java教程 2025-10-14

文章内容收录到个人网站,方便阅读:hardyfish.top/

Dubbo 在以下情况下会自动清理 RpcContext

示例(同步调用,Dubbo 自动清理)

public void testRpcContext() {
    RpcContext.getContext().setAttachment("traceId", "abc123");
    
    // Dubbo 远程调用
    demoService.sayHello("Dubbo");

    // RPC 结束后,traceId 会被清除
    System.out.println(RpcContext.getContext().getAttachment("traceId")); // null
}

结果

Dubbo 什么时候不会自动清理 RpcContext

1. Dubbo 异步调用

public void asyncCall() {
    RpcContext.getContext().setAttachment("traceId", "abc123");

    CompletableFuture future = demoService.sayHelloAsync("Dubbo");
    future.whenComplete((result, ex) -> {
        System.out.println("异步返回:" + result);
        System.out.println("traceId:" + RpcContext.getContext().getAttachment("traceId")); //  可能还存在
    });

    // 手动清理
    RpcContext.getContext().clearAttachments();
}

如果不手动清理,异步线程可能复用上一个请求的 RpcContext 数据!

2. 使用线程池(如 ExecutorService

ExecutorService executorService = Executors.newFixedThreadPool(5);

executorService.submit(() -> {
    RpcContext.getContext().setAttachment("traceId", "abc123");
    demoService.sayHello("Dubbo");

    // 可能泄露到下一个任务
});

如果线程池中的线程未手动清理 RpcContext,后续任务可能复用 traceId,导致数据污染。

解决方案

  • 在任务结束时手动clearAttachments()

    try {
        RpcContext.getContext().setAttachment("traceId", "abc123");
        demoService.sayHello("Dubbo");
    } finally {
        RpcContext.getContext().clearAttachments();
    }
    
  • 使用 Dubbo InheritableThreadLocal(适用于 Dubbo 3.x)

    RpcContext.removeContext(true);
    

3. 在 Filter 或拦截器中传递 RpcContext

public class CustomFilter implements Filter {
    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        RpcContext.getContext().setAttachment("traceId", UUID.randomUUID().toString());

        try {
            return invoker.invoke(invocation);
        } finally {
            // 手动清理
            RpcContext.getContext().clearAttachments();
        }
    }
}

确保在 finally 里清理 RpcContext,防止数据污染。

解决方案:如何正确清理 RpcContext

手动 clearAttachments()

try {
    RpcContext.getContext().setAttachment("traceId", "abc123");
    demoService.sayHello("Dubbo");
} finally {
    RpcContext.getContext().clearAttachments();
}

使用 RpcContext.removeContext(true)(Dubbo 3.x 推荐)

RpcContext.removeContext(true);

适用于

  • 线程池中使用 RpcContext
  • Dubbo 异步调用
  • 需要彻底清理 RpcContext

在 AOP 或 Filter 中全局清理

方式 1:Spring AOP 自动清理

@Aspect
@Component
public class RpcContextCleanAspect {
    @AfterReturning("@annotation(org.apache.dubbo.config.annotation.Reference)")
    public void clearRpcContext() {
        RpcContext.getContext().clearAttachments();
    }
}

方式 2:在 Dubbo Filter 里自动清理

public class RpcContextCleanFilter implements Filter {
    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        try {
            return invoker.invoke(invocation);
        } finally {
            // 确保 RPC 调用结束后清理
            RpcContext.getContext().clearAttachments();
        }
    }
}

然后在 dubbo.xmldubbo.properties 里配置:

<dubbo:provider filter="rpcContextCleanFilter"/>

优点:全局自动清理 RpcContext,防止数据泄漏。

结论

情况Dubbo 是否自动清理?推荐解决方案
同步 RPC 调用默认安全,但建议 finally 里清理
异步调用(CompletableFuture)手动 clearAttachments()
线程池复用(ExecutorService)finally 里清理 RpcContext
Dubbo Filter 或 AOP拦截器里清理 RpcContext
Dubbo 3.x 版本RpcContext.removeContext(true)

最佳实践

  1. 同步调用,建议 try-finallyclearAttachments()
  2. 异步调用,必须 clearAttachments(),避免线程池数据污染。
  3. 线程池调用 Dubbo,使用 RpcContext.removeContext(true) 清理 ThreadLocal
  4. 全局清理可以用 Dubbo Filter 或 Spring AOP 自动处理。

结论