關於鏈路追蹤,在微服務的趨勢下,一次調用的日誌信息分佈在不一樣的機器上或目錄下,當須要看一條鏈路調用全部的日誌信息時,這是個比較困難的地方,咱們雖然有ELK , Sentry等日誌異常收集分析工具, 可是如何把信息串起來也是一個關鍵的問題。 咱們通常的作法是在系統調用開始時生成一個traceId , 而且它伴隨着一次調用的整個生命週期 。 當一個服務調用另一個服務的時候,traceId 則向下透傳,全局使用惟一一個。html
咱們經過分析源碼能夠知道客戶端在調用服務段進行服務消費時,實際上發送的是封裝過的Request實體 ,Data爲Invocation實體對象(接口簽名,參數類型,參數值,及attachment附件)java
final class HeaderExchangeChannel implements ExchangeChannel { ... public ResponseFuture request(Object request, int timeout) throws RemotingException { if (closed) { throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!"); } // create request. Request req = new Request(); req.setVersion("2.0.0"); req.setTwoWay(true); req.setData(request); DefaultFuture future = new DefaultFuture(channel, req, timeout); try { channel.send(req); } catch (RemotingException e) { future.cancel(); throw e; } return future; } ... }
經過源碼可知 request是入參,其餘參數均爲固定,因此只能在request中作文章。apache
TraceIdUtil源碼以下dom
public class TraceIdUtil { private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<String>(); public static String getTraceId() { if(TRACE_ID.get() == null) { String s = UUID.randomUUID().toString(); setTraceId(s); } return TRACE_ID.get(); } public static void setTraceId(String traceId) { TRACE_ID.set(traceId); } }
由Dubbo線程模型圖示可知,Dubbo客戶端調用實際上經過JavassistProxyFactory獲取的是Proxy代理對象。 代碼以下ide
public class JavassistProxyFactory extends AbstractProxyFactory { ... @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } ... }
其中 InvokerInvocationHandler 調用數據的處理及rpc調用實體的封裝過程。因此咱們須要在調用的起始位置添加traceId信息 。 微服務
其次是服務提供者進行請求處理過程: DubboProtocol的requestHandler 請求處理器(不明白請詳讀服務Dubbo服務暴露過程源碼) 工具
將traceId 從RpcInvocation 的attachment屬性中取出 ,傳給TraceIdUtil 方面後續調用過程使用。this
建立Filter 服務提供者端擴展線程
/** * Created with IntelliJ IDEA. * * @author: bakerZhu * @description: * @time: 2018年09月09日 * @modifytime: */ @Activate(group = {Constants.CONSUMER, Constants.PROVIDER} , order = -9999) public class GlobalTraceFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { String traceId = invocation.getAttachment("traceId"); if(!StringUtils.isBlank(traceId)) { RpcContext.getContext().setAttachment("traceId",traceId); }else { // 第一次發起調用 RpcContext.getContext().setAttachment("traceId", UUID.randomUUID().toString()); } return invoker.invoke(invocation); } }
資源文件夾下建立 META-INF/dubbo 文件夾 建立com.alibaba.dubbo.rpc.Filter 文件,並編輯文件內容 gtrace=com.alibaba.dubbo.rpc.filter.GlobalTraceFilter3d
詳見 AbstractInvoker.invoke(Invocation inv) 方法
public abstract class AbstractInvoker<T> implements Invoker<T> { public Result invoke(Invocation inv) throws RpcException { ...... Map<String, String> context = RpcContext.getContext().getAttachments(); if (context != null) { invocation.addAttachmentsIfAbsent(context); } if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString()); } RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); ...... } }
將上下文的「附件信息」拷貝到RpcInvocation中