1. 程式人生 > >dubbo基於呼叫攔截擴充套件實現分散式呼叫追蹤

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來跟蹤請求了。