dubbo基於呼叫攔截擴充套件實現分散式呼叫追蹤
在基於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來跟蹤請求了。