文章内容收录到个人网站,方便阅读: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.xml 或 dubbo.properties 里配置:
<dubbo:provider filter="rpcContextCleanFilter"/>
优点:全局自动清理 RpcContext,防止数据泄漏。
结论
| 情况 | Dubbo 是否自动清理? | 推荐解决方案 |
|---|---|---|
| 同步 RPC 调用 | 是 | 默认安全,但建议 finally 里清理 |
| 异步调用(CompletableFuture) | 否 | 手动 clearAttachments() |
| 线程池复用(ExecutorService) | 否 | finally 里清理 RpcContext |
Dubbo Filter 或 AOP | 否 | 拦截器里清理 RpcContext |
| Dubbo 3.x 版本 | 否 | RpcContext.removeContext(true) |
最佳实践
- 同步调用,建议
try-finally里clearAttachments()。 - 异步调用,必须
clearAttachments(),避免线程池数据污染。 - 线程池调用 Dubbo,使用
RpcContext.removeContext(true)清理ThreadLocal。 - 全局清理可以用 Dubbo
Filter或 Spring AOP 自动处理。
结论