dubbo 服務跟蹤 trace(轉)

本文的目標是改進dubbo破镰,在各個dubbo服務之間透傳traceId者冤,實現(xiàn)服務跟蹤

一薪寓、關于RPC


在大型系統(tǒng)中,一個對外http服務的背后往往隱匿了多個內(nèi)部服務之間的相互調(diào)用。因為性能雷绢、開發(fā)成本層面的考量,http協(xié)議并不適合內(nèi)部服務之間的調(diào)用,為此產(chǎn)生了thrift赘理、dubbo 等優(yōu)秀RPC框架。

thrift 的由facebook 開發(fā)扇单,跨語言支持豐富是其最大的亮點商模,thrift定義了一種接口定于語言,通過自動化工具蜘澜,生成client 端和server端代碼施流。

dubbo 是由一個由阿里開發(fā)的開源分布式服務框架,相對于于thrift鄙信,dubbo實現(xiàn)了完善的服務治理功能瞪醋,包括:服務發(fā)現(xiàn)、路由規(guī)則装诡、配置規(guī)則银受、服務降級、負載均衡等鸦采。

二蚓土、關于服務跟蹤


在微服務的趨勢下,一次調(diào)用產(chǎn)生的日志分布在不同的機器上赖淤,雖然可以使用ELK的技術蜀漆,將所有服務的日志灌入es中,但是如何將這寫日志“穿起來”是一個關鍵問題咱旱。

一般的做法是在系統(tǒng)的邊界生成一個traceId确丢,向調(diào)用鏈上的后繼服務傳遞traceId绷耍,后繼服務使用traceId 打印相應日志,并再向后繼服務傳遞traceId鲜侥。簡稱“traceId透傳”褂始。

在使用http協(xié)議作為服務協(xié)議的系統(tǒng)里,可以統(tǒng)一使用一個封裝好的http client做traceId透傳描函。但是dubbo實現(xiàn)traceId透傳就稍微復雜些了崎苗。

三、dubbo traceId 透傳測試


首先拋開實現(xiàn)舀寓,看一下實現(xiàn)透傳成功的測試case

評價指標:

(1) 調(diào)用鏈上的所有dubbo服務都有一個同樣的traceId
??(2) 對業(yè)務代碼無侵入胆数,來的業(yè)務代碼無需修改升級
??(3) 對性能無太大的影響

測試用例:

/**
 * 測試Service
 * Created by WuMingzhi on 2017/3/19.
 */
public interface GiftService {

    int getPrice(int giftId);
}
/**
 * provider端
 * Created by WuMingzhi on 2017/3/19.
 */
public class GiftServiceImpl implements GiftService{

    private static Random random = new Random();

    @Override
    public int getPrice(int i) {
        System.out.println("gift provider traceId:" + TraceIdUtil.getTraceId());
        int price = random.nextInt(i + 100);
        System.out.println("set gift price: " + price);
        return price;
    }
}
/**
 * consumer 端
 * Created by WuMingzhi on 2017/3/19.
 */
public class TestGiftTraceId {

    @Resource
    private GiftService giftService;

    @Test
    public void TestGiftTraceId() throws InterruptedException {

        while (true){
            // consumer 端設置一個 traceId
            TraceIdUtil.setTraceId(UUID.randomUUID().toString());
            System.out.println("gift consumer traceId:" + TraceIdUtil.getTraceId());

            int price = giftService.getPrice(100);
            System.out.println("get gift price: " + price);

            Thread.sleep(1000);
        }
    }
}

測試結果:

consumer端

gift consumer traceId:53bf37ca-6ce9-401a-b33f-87d6f3c96cfa
get gift price: 183
gift consumer traceId:a79a2a0a-29fa-48d4-b4f6-14bdd3504603
get gift price: 144
gift consumer traceId:cd058847-8683-452b-ac4c-43bd94557a06
get gift price: 178
gift consumer traceId:fd5a72a1-f060-4aef-8864-baf1d7ee1fac
get gift price: 187

provider端

gift provider traceId:53bf37ca-6ce9-401a-b33f-87d6f3c96cfa
set gift price: 183
gift provider traceId:a79a2a0a-29fa-48d4-b4f6-14bdd3504603
set gift price: 144
gift provider traceId:cd058847-8683-452b-ac4c-43bd94557a06
set gift price: 178
gift provider traceId:fd5a72a1-f060-4aef-8864-baf1d7ee1fac
set gift price: 187

結論 consumer 和provider 具有相同的traceId, 透傳成功

四互墓、dubbo 源碼分析


下圖為dubbo的線程派發(fā)模型:

Proxy 是dubbo 使用javassist為consumer 端service生成的動態(tài)代理instance必尼。
?? Implement 是provider端的service實現(xiàn)instance。

traceId透傳即要求Proxy 和 Implement具有相同的traceId篡撵。dubbo具有良好的分層特征判莉,transport的對象是RPCInvocation。所以Proxy將traceId放入RPCInvocation育谬,交由Client進行序列化和TCP傳輸券盅,Server反序列化得到RPCInvocation,取出traceId膛檀,交由Implement即可渗饮。

這里寫圖片描述

下圖為consumer端 JavassistProxyFactory 的代碼分析

這里寫圖片描述

下圖為consumer端 InvokerInvocationHandler 的代碼分析

這里寫圖片描述

下圖為provider端 DubboProtocol 的代碼分析

這里寫圖片描述

五、dubbo 透傳traceId的實現(xiàn)


修改了dubbo 的兩個類宿刮,添加了一個類互站,只列出關鍵代碼。

package com.alibaba.dubbo.rpc.proxy;

/**
 * traceId工具類這個類是新添加的
 * Created by WuMingzhi on 17/3/18.
 */
public class TraceIdUtil {

    private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<String>();

    public static String getTraceId() {
        return TRACE_ID.get();
    }

    public static void setTraceId(String traceId) {
        TRACE_ID.set(traceId);
    }

}
package com.alibaba.dubbo.rpc.proxy;

/**
 * InvokerHandler 這個類 是修改的
 * @author william.liangf
 */
public class InvokerInvocationHandler implements InvocationHandler {

    private final Invoker<?> invoker;

    public InvokerInvocationHandler(Invoker<?> handler){
        this.invoker = handler;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return invoker.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return invoker.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return invoker.equals(args[0]);
        }
        // 這里將cosumer 端的traceId放入RpcInvocation
        RpcInvocation rpcInvocation = new RpcInvocation(method, args);
        rpcInvocation.setAttachment("traceId", TraceIdUtil.getTraceId());
        return invoker.invoke(rpcInvocation).recreate();
    }

}

package com.alibaba.dubbo.rpc.protocol.dubbo;

/**
 * dubbo protocol support.
 *
 * @author qian.lei
 * @author william.liangf
 * @author chao.liuc
 */
public class DubboProtocol extends AbstractProtocol {

    private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

        public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                Invocation inv = (Invocation) message;
                Invoker<?> invoker = getInvoker(channel, inv);
                //如果是callback 需要處理高版本調(diào)用低版本的問題
                if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))){
                    String methodsStr = invoker.getUrl().getParameters().get("methods");
                    boolean hasMethod = false;
                    if (methodsStr == null || methodsStr.indexOf(",") == -1){
                        hasMethod = inv.getMethodName().equals(methodsStr);
                    } else {
                        String[] methods = methodsStr.split(",");
                        for (String method : methods){
                            if (inv.getMethodName().equals(method)){
                                hasMethod = true;
                                break;
                            }
                        }
                    }
                    if (!hasMethod){
                        logger.warn(new IllegalStateException("The methodName "+inv.getMethodName()+" not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) +" ,invocation is :"+inv );
                        return null;
                    }
                }
                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                // 這里將收到的consumer端的traceId放入provider端的thread local
                TraceIdUtil.setTraceId(inv.getAttachment("traceId"));
                return invoker.invoke(inv);
            }
            throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
        }

    }
}

六僵缺、更多思考

本文只介紹了如何在dubbo系統(tǒng)之間透傳traceId胡桃,但沒有介紹使用traceId進行日志跟蹤的case。建議讀者實現(xiàn)兩個層次的日志跟蹤:
??(1)線程日志跟蹤:在調(diào)用的入口處設置線程的traceId(來著上游線程或者自動生成)磕潮;在業(yè)務代碼里使用統(tǒng)一的LogFactory 獲取自動打印traceId的 logger 打印函數(shù)的調(diào)用信息翠胰。通過traceId 即可grep 出一次調(diào)用的所有日志。
??(2)dubbo日志跟蹤:類似上文自脯,dubbo日志跟蹤連接了不同服務之間的線程日志之景,使得在dubbo下實現(xiàn)服務跟蹤成為可能。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膏潮,一起剝皮案震驚了整個濱河市锻狗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖轻纪,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件油额,死亡現(xiàn)場離奇詭異,居然都是意外死亡刻帚,警方通過查閱死者的電腦和手機潦嘶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崇众,“玉大人掂僵,你說我怎么就攤上這事∏旮瑁” “怎么了锰蓬?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衙吩。 經(jīng)常有香客問我,道長溪窒,這世上最難降的妖魔是什么坤塞? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮澈蚌,結果婚禮上摹芙,老公的妹妹穿的比我還像新娘。我一直安慰自己宛瞄,他們只是感情好浮禾,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著份汗,像睡著了一般盈电。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杯活,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天匆帚,我揣著相機與錄音,去河邊找鬼旁钧。 笑死吸重,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的歪今。 我是一名探鬼主播嚎幸,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寄猩!你這毒婦竟也來了嫉晶?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎车遂,沒想到半個月后封断,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡舶担,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年坡疼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衣陶。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡柄瑰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剪况,到底是詐尸還是另有隱情教沾,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布译断,位于F島的核電站授翻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏孙咪。R本人自食惡果不足惜堪唐,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翎蹈。 院中可真熱鬧淮菠,春花似錦、人聲如沸荤堪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澄阳。三九已至拥知,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碎赢,已是汗流浹背举庶。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揩抡,地道東北人户侥。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像峦嗤,于是被迫代替她去往敵國和親蕊唐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1烁设、準備 在分析探索Dubbo架構原理之前替梨,我們需要準備一下環(huán)境钓试,用于后面我們來分析dubbo的架構。 1.1 Z...
    墨淵丶閱讀 2,601評論 1 20
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理副瀑,服務發(fā)現(xiàn)弓熏,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 0 準備 安裝注冊中心:Zookeeper糠睡、Dubbox自帶的dubbo-registry-simple挽鞠;安裝Du...
    七寸知架構閱讀 13,989評論 0 88
  • 周末了均抽,跟閨蜜吃午飯嫁赏,飲茶聊天。陽光明媚油挥,天氣晴朗潦蝇,心情愉快,正好可以口若懸河地大談特談啦深寥。 閨蜜從小聰明伶俐攘乒。她...
    小城市里的正能量閱讀 414評論 0 1
  • #幸福是需要修出來的~每天進步1%~幸福實修08班~11賈春芬-杭州# 20170622(4/99) 【幸福三朵玫...
    chfenj閱讀 239評論 3 2