Dubbo分布式调用链日志追踪

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

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

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

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中,实例如下:

@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,如下:

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,如下:

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();
        }
    }


(全文完)