Dubbo Filter + SLF4J MDC 实现分布式日志追踪

  |  
阅读次数
  |  
字数 1,211
  |  
时长 ≈ 6 分钟

在使用dubbo进行分布式服务开发时,一个大系统会拆分成很多领域以及很多子服务,而且部署时会部署在不同服务器里面,这在我们使用日志排查问题时将会十分麻烦。

这里我们使用Dubbo的Filter以及SLF4J的MDC功能,来共同完成这个事情。

主要思路是:

  1. 消费端在调用调用服务端时,生成请求ID,然后将请求ID、服务器IP等参数在调用服务前进行注入,将参数带到服务提供方。
  2. 提供方在执行方法前,获取消费端设置的参数,包括请求ID、主机IP等需要用到的参数,然后设置到MDC里面,供打印日志使用,再将参数往下游服务进行传递。

代码实现:

由于dubbo在2.7.*的版本以及之前的版本包结构有所变更,
为了让大家明白这不同版本的配置差异,
这里服务端使用2.7.3的版本,消费端使用2.6.5的版本,来演示下不同版本下配置的异同。

1.定义上下游服务DubboFilter

服务消费端:ConsumerRpcTraceFilter

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.faq.filter;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.utils.NetUtils;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.util.UUID;

/**
* 服务消费端TraceFilter
*
* @author tidy
* @date 2019/11/15 11:23
*/
@Activate(group = {Constants.CONSUMER})
@Slf4j
public class ConsumerRpcTraceFilter implements Filter {

/**
* @param invoker
* @param invocation
*
* @return
*
* @throws RpcException
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 如果连续调用多个服务,则会使用同个线程里之前设置的traceId
String traceId = MDC.get("traceId");
if (StringUtils.isBlank(traceId)) {
// 线程内,首次调用远端服务
traceId = UUID.randomUUID().toString();
}

// 设置日志traceId变量
MDC.put("traceId", traceId);

// 设置传递到提供端的参数
RpcContext.getContext().setAttachment("host", NetUtils.getLocalHost());
RpcContext.getContext().setAttachment("trace_id", traceId);

return invoker.invoke(invocation);
}

}

服务提供端:ProviderRpcTraceFilter

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package cn.tisson.wechat.template.rpc.filter;

import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.slf4j.MDC;

import java.util.UUID;

/**
* 服务提供端TraceFilter
*
* @author tidy
* @date 2019/11/15 11:21
*/
@Activate(group = {CommonConstants.PROVIDER})
public class ProviderRpcTraceFilter implements Filter {

/**
* @param invoker
* @param invocation
*
* @return
*
* @throws RpcException
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获取消费端设置的参数
String host = RpcContext.getContext().getAttachment("host");
String traceId = RpcContext.getContext().getAttachment("trace_id");

if (StringUtils.isBlank(traceId)) {
traceId = UUID.randomUUID().toString();
}

// 设置日志traceId变量
MDC.put("host", host);
MDC.put("traceId", traceId);

// 继续设置下游参数,供在提供方里面作为消费端时,其他服务提供方使用这些参数
RpcContext.getContext().setAttachment("host", NetUtils.getLocalHost());
RpcContext.getContext().setAttachment("trace_id", traceId);

try {
return invoker.invoke(invocation);
} finally {
// 调用完成后移除MDC属性
MDC.remove("host");
MDC.remove("traceId");
}
}
}

2.配置上下游服务DubboFilter

服务消费端:
在该目录下创建DubboSPI描述文件
/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
里面写入以下内容:

1
consumerRpcTraceFilter=com.faq.filter.ConsumerRpcTraceFilter

修改配置文件:

1
2
3
dubbo:
consumer:
filter: consumerRpcTraceFilter

服务提供端:
在该目录下创建DubboSPI描述文件
/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter
里面写入以下内容:

1
providerRpcTraceFilter=cn.tisson.wechat.template.rpc.filter.ProviderRpcTraceFilter

修改配置文件:

1
2
3
dubbo:
provider:
filter: providerRpcTraceFilter

3.设置上下游服务logback.xml的日志输出格式

服务消费端:

1
2
<property name="logger.pattern"
value="%d{yy-MM-dd HH:mm:ss} %highlight(%p) %yellow(%t) %cyan(%c.%M\\(%L\\)) %X{traceId} | %m%n"/>

服务提供端:

1
2
<property name="logger.pattern"
value="%d{yy-MM-dd HH:mm:ss} %highlight(%p) %yellow(%t) %cyan(%c.%M\\(%L\\)) %X{host} %X{traceId} | %m%n"/>

4.启动应用、测试、验证

服务消费端:

1
19-11-18 11:18:20 INFO XNIO-2 task-1 com.faq.service.impl.SendMessageServiceImpl.getReturnJson(188) f141529f-a650-4100-8ffd-db8baa63c2dc | {"msg":"成功","id":381040355136503808,"status":0}

服务提供端:

1
2
3
19-11-18 11:18:19 INFO DubboServerHandler-192.168.123.164:20881-thread-2 cn.tisson.wechat.template.rpc.component.RedisComponent.sendChannelMsg(28) 192.168.123.164 f141529f-a650-4100-8ffd-db8baa63c2dc | Push data ****** to topic ts-tmpl-svc-msgusrinf-192.168.123.164

19-11-18 11:18:20 INFO DubboServerHandler-192.168.123.164:20881-thread-2 cn.tisson.wechat.template.rpc.component.RedisComponent.sendChannelMsg(28) 192.168.123.164 f141529f-a650-4100-8ffd-db8baa63c2dc | Push data ****** to topic ts-tmpl-svc-msgusrinf-192.168.123.164