Dubbo分布式调用链日志追踪

为了在分布式系统快速定位问题及进行性能问题定位,我们需要知道引起性能问题的具体操作,然而在分布式系统中,一个请求往往会调用很多的服务(service),如何快速的定位,这个时候我们就需要分布式链路跟踪系统,如ZipKin,它会帮助我们快速定位问题根源。
然而,本文不使用zipkin作为链路跟踪,通过自己实现,从Nginx—>Tomcat—->Spring MVC Controller—>Dubbo consumer—->Dubbo provider的链路跟踪,通过TraceId将一次请求的服务器所有执行日志串起来,可以帮助我们快速的定位问题根源,

一、Nginx每个请求生成唯一的ID

Nginx生成唯一的$request_id作为日志追踪的trace_id,可以参考文章进行设置:点击查看

1
proxy_set_header X-Request-Id $request_id;

二、Spring MVC Controller处理

1、在Spring MVC Controller调用之前,可以通过设置一个拦截器,获取HTTP请求Header中的trace_id参数,通过MDC设置log4j日志对象中,再配置一下logback.xml就可以在controller执行的时打印出trace_id。
2、同样的可以使用AOP在Controller调用之前将值设置到MDC中,实例如下:

1
2
3
4
5
6
7
8
9
10
11
@Component
@Aspect
public class LogAopAction {

@Pointcut("execution(* com.cdms.controller..*.*(..))")
private void controllerAspect(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 设置trace_id
MDC.set("trace_id", request.getHeader("XXXX"));
}
}

三、Dubbo consumer filter配置

重写Dubbo ConsumerContextFilter,在重写方法中的MDC设置trace_id,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//在当前的RpcContext中记录本地调用的一次状态信息
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {
((RpcInvocation)invocation).setInvoker(invoker);
}
try {
return invoker.invoke(invocation);
} finally {
RpcContext.getContext().clearAttachments();
}
}

四、Dubbo provider filter配置

其实,provider的配置与consumer配置差不多一致,只需在每次provider调用前设置一个filter将trace_id提前设置到MDC中,在service执行过程中trace_id将会被打印出来。

重写ContextFilter,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Map<String, String> attachments = invocation.getAttachments();
if (attachments != null) {
//隐式参数重剔除一些核心消息
attachments = new HashMap<String, String>(attachments);
attachments.remove(Constants.PATH_KEY);
attachments.remove(Constants.GROUP_KEY);
attachments.remove(Constants.VERSION_KEY);
attachments.remove(Constants.DUBBO_VERSION_KEY);
attachments.remove(Constants.TOKEN_KEY);
attachments.remove(Constants.TIMEOUT_KEY);
}
//这里又重新将invocation和attachments信息设置到RpcContext,这里设置以后provider的代码就可以获取到consumer端传递的一些隐式参数了
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setAttachments(attachments)
.setLocalAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {
((RpcInvocation)invocation).setInvoker(invoker);
}
try {
return invoker.invoke(invocation);
} finally {
RpcContext.removeContext();
}
}


(全文完)