在基于dubbo的分布式应用集群中,调试会变得比较麻烦,不知道一个请求会被发送到哪一台机器上。我们可以通过dubbo的SPI扩展中的调用拦截扩展,来解决这个问题。
dubbo的调用拦截扩展可以对服务提供方和消费方的调用进行拦截,然后加入自己的处理逻辑。通过简单的三个步骤即可实现一个自定义的调用拦截扩展。
1、扩展配置
在dubbo的xml配置文件中,加入filter配置,如下所示:
<!-- 消费方调用过程拦截 -->
<dubbo:reference filter="xxx,yyy" />
<!-- 消费方调用过程缺省拦截器,将拦截所有reference -->
<dubbo:consumer filter="xxx,yyy"/>
<!-- 提供方调用过程拦截 -->
<dubbo:service filter="xxx,yyy" />
<!-- 提供方调用过程缺省拦截器,将拦截所有service -->
<dubbo:provider filter="xxx,yyy"/>
2、配置实现
在META-INF中创建dubbo文件夹,然后在dubbo文件夹下创建com.alibaba.dubbo.rpc.Filter文本文件,如下所示:
然后编辑该文本文件,指定filter的实现类,例如:
xxx=com.gameloft9.XxxFilter
3、编写实现类
通过实现dubbo的Filter接口,实现具体的拦截逻辑,例如:
package com.gameloft9;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
public class XxxFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// before filter ...
Result result = invoker.invoke(invocation);
// after filter ...
return result;
}
}
Slf4j接口里面有MDC,熟悉logback的同学应该不会陌生,我们可以通过MDC,将有用的信息打印到日志里面去。接下来我们就通过MDC和调用拦截扩展来实现分布式的调用追踪。
1、扩展配置
<!--配置filter,在filter里面处理traceId-->
<dubbo:provider filter="traceid"/>
<dubbo:consumer filter="traceid"/>
2、配置实现类
traceid=com.gameloft9.demo.filter.TraceDubboFilter
3、编写实现逻辑
逻辑很简单,当consumer调用时,将生成的traceId放入rpc context,provider接收到请求时,从traceId获取到traceId,并放入MDC。
package com.gameloft9.demo.filter;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.gameloft9.demo.util.TraceUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Activate(order = 1000, group = {Constants.PROVIDER, Constants.CONSUMER})
public class TraceDubboFilter implements Filter {
public TraceDubboFilter(){
super();
}
public Result invoke(Invoker<?> invoker, Invocation invocation)
throws RpcException {
RpcContext context = RpcContext.getContext();
if (context.isConsumerSide()) {
TraceUtil.putTraceInto(context);
} else if (context.isProviderSide()) {
TraceUtil.getTraceFrom(context);
}
return invoker.invoke(invocation);
}
}
对traceId的处理,封装到了一个工具类中:
package com.gameloft9.demo.util;
import com.alibaba.dubbo.rpc.RpcContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import java.util.UUID;
/**
* traceId工具类
* Created by gameloft9 on 2018/9/7.
*/
public class TraceUtil {
public final static String TRACE_ID = "trace_id";
/**
* 初始化traceId,由consumer调用
*/
public static void initTrace() {
String traceId = generateTraceId();
setTraceId(traceId);
}
/**
* 从Dubbo中获取traceId,provider调用
* @param context
*/
public static void getTraceFrom(RpcContext context) {
String traceId = (String) context.getAttachment(TRACE_ID);
if (traceId == null) {
traceId = generateTraceId();
}
setTraceId(traceId);
}
/**
* 把traceId放入dubbo远程调用中,consumer调用
* @param context
*/
public static void putTraceInto(RpcContext context) {
String traceId = getTraceId();
if (traceId != null) {
context.setAttachment(TRACE_ID, traceId);
}
}
/**
* 从MDC中清除traceId
*/
public static void clearTrace() {
MDC.remove(TRACE_ID);
}
/****************************私有方法区*********************************/
/**
* 从MDC中获取traceId
* */
private static String getTraceId() {
return MDC.get(TRACE_ID);
}
/**
* 将traceId放入MDC
* @param traceId
*/
private static void setTraceId(String traceId) {
if (StringUtils.isNotBlank(traceId)) {
traceId = StringUtils.left(traceId, 36);
}
MDC.put(TRACE_ID, traceId);
}
/**
* 生成traceId
* @return
*/
static private String generateTraceId() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
如何使用:
1、consumer端
ICalculator demo = (ICalculator)context.getBean("calculator");
TraceUtil.initTrace();
int res = demo.add(1,3);
log.info("测试结果:{}",res);
2、provider端
provider端不需要做任何操作,读取traceId和设置MDC都在filter里面做了。
3、logback配置
logback中,通过%X{trace_id}读取MDC 中的traceId,并打印出来。
<!--控制台打印-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%.15thread] [%X{trace_id}] %logger{36} - %.-4096msg%n
</pattern>
</encoder>
</appender>
测试:
我们一共有四个工程,dubbo-test-api,dubbo-test-consumer,dubbo-test-provider,dubbo-test-provider-another。api工程提供了一个简单的加法接口:
/**
* 接口
* */
public interface ICalculator {
int add(int a,int b);
}
dubbo-test-provider和dubbo-test-provider-another均实现了这个接口,并注册到了zookeeper(zookeeper需要自己搭建)。
package com.gameloft9.demo.serviceImp;
import com.gameloft9.demo.api.ICalculator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class Calculator implements ICalculator {
public int add(int a,int b){
log.info("收到加法请求:{}+{}",a,b);
return a+b;
}
}
dubbo-test-consumer调用加法:
ICalculator demo = (ICalculator)context.getBean("calculator");
TraceUtil.initTrace();
log.info("开始测试!");
int res = demo.add(1,3);
log.info("测试结果:{}",res);
运行截图:
1、consumer端日志
2、service端日志
这样我们就成功的在日志中通过traceId来跟踪请求了。