基于Opentracing+Jaeger全鏈路灰度調(diào)用鏈

當(dāng)網(wǎng)關(guān)和服務(wù)在實施全鏈路分布式灰度發(fā)布和路由時候,我們需要一款追蹤系統(tǒng)來監(jiān)控網(wǎng)關(guān)和服務(wù)走的是哪個灰度組讨阻,哪個灰度版本芥永,哪個灰度區(qū)域,甚至監(jiān)控從Http Header頭部全程傳遞的灰度規(guī)則和路由策略钝吮。這個功能意義在于:

  • 不僅可以監(jiān)控全鏈路中基本的調(diào)用信息埋涧,也可以監(jiān)控額外的灰度信息,有助于我們判斷灰度發(fā)布和路由是否執(zhí)行準確奇瘦,一旦有問題棘催,也可以快速定位
  • 可以監(jiān)控流量何時切換到新版本,或者新的區(qū)域耳标,或者新的機器上
  • 可以監(jiān)控灰度規(guī)則和路由策略是否配置準確
  • 可以監(jiān)控網(wǎng)關(guān)和服務(wù)灰度上下級樹狀關(guān)系
  • 可以監(jiān)控全鏈路流量拓撲圖

筆者嘗試調(diào)研了一系列分布式追蹤系統(tǒng)和中間件醇坝,包括Opentracing、Uber Jaeger次坡、Twitter Zipkin呼猪、Apache Skywalking、Pinpoint砸琅、CAT等郑叠,最后決定采用Opentracing + Uber Jaeger方式來實現(xiàn),重要原因除了易用性和可擴展性外明棍,Opentracing支持WebMvc和WebFlux兩種方式乡革,業(yè)界的追蹤系統(tǒng)能支持WebFlux相對較少

[OpenTracing] OpenTracing已進入CNCF,正在為全球的分布式追蹤系統(tǒng)提供統(tǒng)一的概念摊腋、規(guī)范沸版、架構(gòu)和數(shù)據(jù)標準。它通過提供平臺無關(guān)兴蒸、廠商無關(guān)的API视粮,使得開發(fā)人員能夠方便的添加(或更換)追蹤系統(tǒng)的實現(xiàn)。對于存在多樣化的技術(shù)棧共存的調(diào)用鏈中橙凳,Opentracing適配Java蕾殴、C、Go和.Net等技術(shù)棧岛啸,實現(xiàn)全鏈路分布式追蹤功能钓觉。迄今為止,Uber Jaeger坚踩、Twitter Zipkin和Apache Skywalking已經(jīng)適配了Opentracing規(guī)范

筆者以Nepxion社區(qū)的Discovery開源框架(對該開源框架感興趣的同學(xué)荡灾,請訪問如下鏈接)為例子展開整合

源碼主頁,請訪問
https://github.com/Nepxion/Discovery

指南主頁,請訪問
https://github.com/Nepxion/DiscoveryGuide

文檔主頁批幌,請訪問
https://pan.baidu.com/s/1i57rXaNKPuhGRqZ2MONZOA#list/path=%2FNepxion

整合的效果圖





基本概念

灰度調(diào)用鏈主要包括如下11個參數(shù)础锐。使用者可以自行定義要傳遞的調(diào)用鏈參數(shù),例如:traceId, spanId等荧缘;也可以自行定義要傳遞的業(yè)務(wù)調(diào)用鏈參數(shù)皆警,例如:mobile, user等

1. n-d-service-group - 服務(wù)所屬組或者應(yīng)用
2. n-d-service-type - 服務(wù)類型,分為“網(wǎng)關(guān)”和“服務(wù)”
3. n-d-service-id - 服務(wù)ID
4. n-d-service-address - 服務(wù)地址截粗,包括Host和Port
5. n-d-service-version - 服務(wù)版本
6. n-d-service-region - 服務(wù)所屬區(qū)域
7. n-d-version - 版本路由值
8. n-d-region - 區(qū)域路由值
9. n-d-address - 地址路由值
10. n-d-version-weight - 版本權(quán)重路由值
11. n-d-region-weight - 區(qū)域權(quán)重路由值

核心實現(xiàn)

Opentracing通用模塊

源碼參考
https://github.com/Nepxion/Discovery/tree/master/discovery-plugin-strategy-opentracing

由于OpenTracing擴展需要兼顧到Spring Cloud Gateway耀怜、Zuul和服務(wù),它的核心邏輯存在著一定的可封裝性桐愉,所以筆者抽取出一個公共模塊discovery-plugin-strategy-opentracing财破,包含configuration、operation从诲、context等模塊左痢,著重闡述operation模塊,其它比較簡單系洛,不一一贅述了

在闡述前俊性,筆者需要解釋一個配置,該配置將決定核心實現(xiàn)以及終端界面的顯示

  1. 如果開啟描扯,灰度信息輸出到獨立的Span節(jié)點中定页,意味著在界面顯示中,灰度信息通過獨立的GRAY Span節(jié)點來顯示绽诚。優(yōu)點是信息簡潔明了典徊,缺點是Span節(jié)點會增長一倍。我們可以稱呼它為【模式A】
  2. 如果關(guān)閉恩够,灰度信息輸出到原生的Span節(jié)點中卒落,意味著在界面顯示中,灰度信息會和原生Span節(jié)點的調(diào)用信息蜂桶、協(xié)議信息等混在一起儡毕,缺點是信息龐雜混合,優(yōu)點是Span節(jié)點數(shù)不會增長扑媚。我們可以稱呼它為【模式B】
# 啟動和關(guān)閉調(diào)用鏈的灰度信息在Opentracing中以獨立的Span節(jié)點輸出腰湾,如果關(guān)閉,則灰度信息輸出到原生的Span節(jié)點中疆股。缺失則默認為true
spring.application.strategy.trace.opentracing.separate.span.enabled=true

Opentracing公共操作類 - StrategyOpentracingOperation.java

  • 裝配注入Opentracing的Tracer對象
  • opentracingInitialize方法费坊,提供給網(wǎng)關(guān)和服務(wù)的Span節(jié)點初始化
    • 【模式A】下,tracer.buildSpan(...).start()實現(xiàn)新建一個Span押桃,并把它放置到存儲上下文的StrategyOpentracingContext的ThreadLocal里
    • 【模式B】下葵萎,不需要做任何工作
  • opentracingHeader方法,提供給網(wǎng)關(guān)的灰度調(diào)用鏈輸出
    • 【模式A】下唱凯,首先從StrategyOpentracingContext的ThreadLocal里獲取Span對象羡忘,其次把customizationMap(自定義的調(diào)用鏈參數(shù))的元素都放入到Tag中,最后把灰度調(diào)用鏈主11個參數(shù)(通過strategyContextHolder.getHeader(...)獲瓤闹纭)和更多上下文信息放入到Tag中
    • 【模式B】下卷雕,跟【模式A】類似,唯一區(qū)別的是Tags.COMPONENT的處理票从,由于原生的Span節(jié)點已經(jīng)帶有該信息漫雕,所以不需要放入到Tag中
  • opentracingLocal方法,提供給服務(wù)的灰度調(diào)用鏈輸出
    • 【模式A】下峰鄙,首先從StrategyOpentracingContext的ThreadLocal里獲取Span對象浸间,其次把customizationMap(自定義的調(diào)用鏈參數(shù))的元素都放入到Tag中,最后把灰度調(diào)用鏈主11個參數(shù)(通過pluginAdapter.getXXX()獲纫髁瘛)和更多上下文信息放入到Tag中
    • 【模式B】下魁蒜,跟【模式A】類似,唯一區(qū)別的是Tags.COMPONENT的處理吩翻,由于原生的Span節(jié)點已經(jīng)帶有該信息兜看,所以不需要放入到Tag中
  • opentracingError方法,提供給服務(wù)的灰度調(diào)用鏈異常輸出
    • 【模式A】下狭瞎,首先從StrategyOpentracingContext的ThreadLocal里獲取Span對象细移,其次span.log(...)方法實現(xiàn)異常輸出
    • 【模式B】下,不需要做任何工作
  • opentracingClear方法熊锭,灰度調(diào)用鏈的Span上報和清除
    • 【模式A】下弧轧,首先從StrategyOpentracingContext的ThreadLocal里獲取Span對象,其次span.finish()方法實現(xiàn)Span上報碗殷,最后StrategyOpentracingContext.clearCurrentContext()方法實現(xiàn)Span清除
    • 【模式B】下劣针,不需要做任何工作
  • getCurrentSpan方法
    • 【模式A】下,返回StrategyOpentracingContext.getCurrentContext().getSpan()亿扁,即opentracingInitialize新建的Span對象
    • 【模式B】下捺典,返回tracer.activeSpan(),即原生的Span對象
public class StrategyOpentracingOperation {
    private static final Logger LOG = LoggerFactory.getLogger(StrategyOpentracingOperation.class);

    @Autowired
    protected PluginAdapter pluginAdapter;

    @Autowired
    protected StrategyContextHolder strategyContextHolder;

    @Autowired
    private Tracer tracer;

    @Value("${" + StrategyOpentracingConstant.SPRING_APPLICATION_STRATEGY_TRACE_OPENTRACING_ENABLED + ":false}")
    protected Boolean traceOpentracingEnabled;

    @Value("${" + StrategyOpentracingConstant.SPRING_APPLICATION_STRATEGY_TRACE_OPENTRACING_SEPARATE_SPAN_ENABLED + ":true}")
    protected Boolean traceOpentracingSeparateSpanEnabled;

    public void opentracingInitialize() {
        if (!traceOpentracingEnabled) {
            return;
        }

        if (!traceOpentracingSeparateSpanEnabled) {
            return;
        }

        Span span = tracer.buildSpan(DiscoveryConstant.SPAN_VALUE).start();
        StrategyOpentracingContext.getCurrentContext().setSpan(span);

        LOG.debug("Trace chain for Opentracing initialized...");
    }

    public void opentracingHeader(Map<String, String> customizationMap) {
        if (!traceOpentracingEnabled) {
            return;
        }

        Span span = getCurrentSpan();
        if (span == null) {
            LOG.error("Span not found in context to opentracing header");

            return;
        }

        if (MapUtils.isNotEmpty(customizationMap)) {
            for (Map.Entry<String, String> entry : customizationMap.entrySet()) {
                span.setTag(entry.getKey(), entry.getValue());
            }
        }

        if (traceOpentracingSeparateSpanEnabled) {
            span.setTag(Tags.COMPONENT.getKey(), DiscoveryConstant.TAG_COMPONENT_VALUE);
        }
        span.setTag(DiscoveryConstant.PLUGIN, DiscoveryConstant.PLUGIN_VALUE);
        span.setTag(DiscoveryConstant.TRACE_ID, span.context().toTraceId());
        span.setTag(DiscoveryConstant.SPAN_ID, span.context().toSpanId());
        span.setTag(DiscoveryConstant.N_D_SERVICE_GROUP, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_GROUP));
        ...

        String routeVersion = strategyContextHolder.getHeader(DiscoveryConstant.N_D_VERSION);
        if (StringUtils.isNotEmpty(routeVersion)) {
            span.setTag(DiscoveryConstant.N_D_VERSION, routeVersion);
        }
        ...

        LOG.debug("Trace chain information outputs to Opentracing...");
    }

    public void opentracingLocal(String className, String methodName, Map<String, String> customizationMap) {
        if (!traceOpentracingEnabled) {
            return;
        }

        Span span = getCurrentSpan();
        if (span == null) {
            LOG.error("Span not found in context to opentracing local");

            return;
        }

        if (MapUtils.isNotEmpty(customizationMap)) {
            for (Map.Entry<String, String> entry : customizationMap.entrySet()) {
                span.setTag(entry.getKey(), entry.getValue());
            }
        }

        if (traceOpentracingSeparateSpanEnabled) {
            span.setTag(Tags.COMPONENT.getKey(), DiscoveryConstant.TAG_COMPONENT_VALUE);
        }
        span.setTag(DiscoveryConstant.PLUGIN, DiscoveryConstant.PLUGIN_VALUE);
        span.setTag(DiscoveryConstant.CLASS, className);
        span.setTag(DiscoveryConstant.METHOD, methodName);
        span.setTag(DiscoveryConstant.TRACE_ID, span.context().toTraceId());
        span.setTag(DiscoveryConstant.SPAN_ID, span.context().toSpanId());
        span.setTag(DiscoveryConstant.N_D_SERVICE_GROUP, pluginAdapter.getGroup());
        ...

        String routeVersion = strategyContextHolder.getHeader(DiscoveryConstant.N_D_VERSION);
        if (StringUtils.isNotEmpty(routeVersion)) {
            span.setTag(DiscoveryConstant.N_D_VERSION, routeVersion);
        }
        ...

        LOG.debug("Trace chain information outputs to Opentracing...");
    }

    public void opentracingError(String className, String methodName, Throwable e) {
        if (!traceOpentracingEnabled) {
            return;
        }

        if (!traceOpentracingSeparateSpanEnabled) {
            return;
        }

        Span span = getCurrentSpan();
        if (span == null) {
            LOG.error("Span not found in context to opentracing error");

            return;
        }

        span.log(new ImmutableMap.Builder<String, Object>()
                .put(DiscoveryConstant.CLASS, className)
                .put(DiscoveryConstant.METHOD, methodName)
                .put(DiscoveryConstant.EVENT, Tags.ERROR.getKey())
                .put(DiscoveryConstant.ERROR_OBJECT, e)
                .build());

        LOG.debug("Trace chain error outputs to Opentracing...");
    }

    public void opentracingClear() {
        if (!traceOpentracingEnabled) {
            return;
        }

        if (!traceOpentracingSeparateSpanEnabled) {
            return;
        }

        Span span = getCurrentSpan();
        if (span != null) {
            span.finish();
        } else {
            LOG.error("Span not found in context to opentracing clear");
        }
        StrategyOpentracingContext.clearCurrentContext();

        LOG.debug("Trace chain context of Opentracing cleared...");
    }

    public Span getCurrentSpan() {
        return traceOpentracingSeparateSpanEnabled ? StrategyOpentracingContext.getCurrentContext().getSpan() : tracer.activeSpan();
    }

    public String getTraceId() {
        if (!traceOpentracingEnabled) {
            return null;
        }

        Span span = getCurrentSpan();
        if (span != null) {
            return span.context().toTraceId();
        }

        return null;
    }

    public String getSpanId() {
        if (!traceOpentracingEnabled) {
            return null;
        }

        Span span = getCurrentSpan();
        if (span != null) {
            return span.context().toSpanId();
        }

        return null;
    }
}

Opentracing Service模塊

源碼參考
https://github.com/Nepxion/Discovery/tree/master/discovery-plugin-strategy-starter-service-opentracing

實現(xiàn)OpenTracing對服務(wù)的擴展从祝,包含configuration襟己、tracer等模塊,著重闡述tracer模塊牍陌,其它比較簡單擎浴,不一一贅述了

Opentracing的服務(wù)追蹤類 - DefaultServiceStrategyOpentracingTracer.java

  • 繼承DefaultServiceStrategyTracer,并注入StrategyOpentracingOperation
  • trace方法里先執(zhí)行opentracingInitialize初始化Span毒涧,這樣可以讓后面的邏輯都可以從Span中拿到traceId和spanId贮预,執(zhí)行opentracingLocal實現(xiàn)服務(wù)的灰度調(diào)用鏈輸出
  • error方法里執(zhí)行opentracingError實現(xiàn)服務(wù)的灰度調(diào)用鏈異常輸出
  • release方法里執(zhí)行opentracingClear實現(xiàn)灰度調(diào)用鏈的Span上報和清除
public class DefaultServiceStrategyOpentracingTracer extends DefaultServiceStrategyTracer {
    @Autowired
    private StrategyOpentracingOperation strategyOpentracingOperation;

    @Override
    public void trace(ServiceStrategyTracerInterceptor interceptor, MethodInvocation invocation) {
        strategyOpentracingOperation.opentracingInitialize();

        super.trace(interceptor, invocation);

        strategyOpentracingOperation.opentracingLocal(interceptor.getMethod(invocation).getDeclaringClass().getName(), interceptor.getMethodName(invocation), getCustomizationMap());
    }

    @Override
    public void error(ServiceStrategyTracerInterceptor interceptor, MethodInvocation invocation, Throwable e) {
        super.error(interceptor, invocation, e);

        strategyOpentracingOperation.opentracingError(interceptor.getMethod(invocation).getDeclaringClass().getName(), interceptor.getMethodName(invocation), e);
    }

    @Override
    public void release(ServiceStrategyTracerInterceptor interceptor, MethodInvocation invocation) {
        super.release(interceptor, invocation);

        strategyOpentracingOperation.opentracingClear();
    }

    @Override
    public String getTraceId() {
        return strategyOpentracingOperation.getTraceId();
    }

    @Override
    public String getSpanId() {
        return strategyOpentracingOperation.getSpanId();
    }
}

Opentracing Spring Cloud Gateway模塊

源碼參考
https://github.com/Nepxion/Discovery/tree/master/discovery-plugin-strategy-starter-gateway-opentracing

實現(xiàn)OpenTracing對Spring Cloud Gateway的擴展,跟discovery-plugin-strategy-starter-service-opentracing模塊類似,不一一贅述了

Opentracing Zuul模塊

源碼參考
https://github.com/Nepxion/Discovery/tree/master/discovery-plugin-strategy-starter-zuul-opentracing

實現(xiàn)OpenTracing對Zuul的擴展仿吞,跟discovery-plugin-strategy-starter-service-opentracing模塊類似滑频,不一一贅述了

使用說明

示例參考
https://github.com/Nepxion/DiscoveryGuide

使用方式

Opentracing輸出方式以Uber Jaeger為例來說明,步驟非常簡單

  1. https://pan.baidu.com/s/1i57rXaNKPuhGRqZ2MONZOA#list/path=%2FNepxion獲取Jaeger-1.14.0.zip唤冈,Windows操作系統(tǒng)下解壓后運行jaeger.bat峡迷,Mac和Lunix操作系統(tǒng)請自行研究
  2. 執(zhí)行Postman調(diào)用后,訪問http://localhost:16686查看灰度調(diào)用鏈
  3. 灰度調(diào)用鏈支持WebMvc和WebFlux兩種方式你虹,以GRAY字樣的標記來標識

開關(guān)控制

對于Opentracing調(diào)用鏈功能的開啟和關(guān)閉绘搞,需要通過如下開關(guān)做控制:

# 啟動和關(guān)閉調(diào)用鏈。缺失則默認為false
spring.application.strategy.trace.enabled=true
# 啟動和關(guān)閉調(diào)用鏈的Opentracing輸出傅物,支持F版或更高版本的配置夯辖,其它版本不需要該行配置。缺失則默認為false
spring.application.strategy.trace.opentracing.enabled=true
# 啟動和關(guān)閉調(diào)用鏈的灰度信息在Opentracing中以獨立的Span節(jié)點輸出董饰,如果關(guān)閉楼雹,則灰度信息輸出到原生的Span節(jié)點中。缺失則默認為true
spring.application.strategy.trace.opentracing.separate.span.enabled=true

可選功能

自定義調(diào)用鏈上下文參數(shù)的創(chuàng)建(該類不是必須的)尖阔,繼承DefaultStrategyTracerAdapter

// 自定義調(diào)用鏈上下文參數(shù)的創(chuàng)建
// 對于getTraceId和getSpanId方法贮缅,在Opentracing等調(diào)用鏈中間件引入的情況下,由調(diào)用鏈中間件決定介却,在這里定義不會起作用谴供;在Opentracing等調(diào)用鏈中間件未引入的情況下,在這里定義才有效齿坷,下面代碼中表示從Http Header中獲取桂肌,并全鏈路傳遞
// 對于getCustomizationMap方法,表示輸出到調(diào)用鏈中的定制化業(yè)務(wù)參數(shù)永淌,可以同時輸出到日志和Opentracing等調(diào)用鏈中間件崎场,下面代碼中表示從Http Header中獲取,并全鏈路傳遞
public class MyStrategyTracerAdapter extends DefaultStrategyTracerAdapter {
    @Override
    public String getTraceId() {
        return StringUtils.isNotEmpty(strategyContextHolder.getHeader(DiscoveryConstant.TRACE_ID)) ? strategyContextHolder.getHeader(DiscoveryConstant.TRACE_ID) : StringUtils.EMPTY;
    }

    @Override
    public String getSpanId() {
        return StringUtils.isNotEmpty(strategyContextHolder.getHeader(DiscoveryConstant.SPAN_ID)) ? strategyContextHolder.getHeader(DiscoveryConstant.SPAN_ID) : StringUtils.EMPTY;
    }

    @Override
    public Map<String, String> getCustomizationMap() {
        return new ImmutableMap.Builder<String, String>()
                .put("mobile", StringUtils.isNotEmpty(strategyContextHolder.getHeader("mobile")) ? strategyContextHolder.getHeader("mobile") : StringUtils.EMPTY)
                .put("user", StringUtils.isNotEmpty(strategyContextHolder.getHeader("user")) ? strategyContextHolder.getHeader("user") : StringUtils.EMPTY)
                .build();
    }
}

在配置類里@Bean方式進行調(diào)用鏈類創(chuàng)建遂蛀,覆蓋框架內(nèi)置的調(diào)用鏈類

@Bean
public StrategyTracerAdapter strategyTracerAdapter() {
    return new MyStrategyTracerAdapter();
}

本文作者

任浩軍谭跨, 10 多年開源經(jīng)歷,Github ID:@HaojunRen李滴,Nepxion 開源社區(qū)創(chuàng)始人螃宙,Nacos Group Member,Spring Cloud Alibaba & Nacos & Sentinel Committer

請聯(lián)系我

微信所坯、公眾號和文檔

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布谆扎!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芹助,隨后出現(xiàn)的幾起案子堂湖,更是在濱河造成了極大的恐慌闲先,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件无蜂,死亡現(xiàn)場離奇詭異伺糠,居然都是意外死亡,警方通過查閱死者的電腦和手機酱讶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門退盯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彼乌,“玉大人泻肯,你說我怎么就攤上這事∥空眨” “怎么了灶挟?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毒租。 經(jīng)常有香客問我稚铣,道長,這世上最難降的妖魔是什么墅垮? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任惕医,我火速辦了婚禮,結(jié)果婚禮上算色,老公的妹妹穿的比我還像新娘抬伺。我一直安慰自己,他們只是感情好灾梦,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布峡钓。 她就那樣靜靜地躺著,像睡著了一般若河。 火紅的嫁衣襯著肌膚如雪能岩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天萧福,我揣著相機與錄音拉鹃,去河邊找鬼。 笑死鲫忍,一個胖子當(dāng)著我的面吹牛芳撒,可吹牛的內(nèi)容都是我干的意乓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼勾扭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起含懊,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤品追,失蹤者是張志新(化名)和其女友劉穎腻脏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體银锻,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡永品,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了击纬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鼎姐。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖更振,靈堂內(nèi)的尸體忽然破棺而出炕桨,到底是詐尸還是另有隱情,我是刑警寧澤肯腕,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布献宫,位于F島的核電站,受9級特大地震影響实撒,放射性物質(zhì)發(fā)生泄漏姊途。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一知态、第九天 我趴在偏房一處隱蔽的房頂上張望捷兰。 院中可真熱鬧,春花似錦负敏、人聲如沸贡茅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽友扰。三九已至,卻和暖如春庶柿,著一層夾襖步出監(jiān)牢的瞬間村怪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工甚负, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人审残。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像病涨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子璧坟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

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

  • [TOC] 鏈路追蹤 當(dāng)代互聯(lián)網(wǎng)服務(wù)既穆,通常都是用復(fù)雜,大規(guī)模分布式集群來實現(xiàn)幻工,微服務(wù)化,這些軟件模塊分布在不同的機...
    orientlu閱讀 17,260評論 0 4
  • 在微服務(wù)架構(gòu)中囊颅,調(diào)用鏈是漫長而復(fù)雜的当悔,要了解其中的每個環(huán)節(jié)及其性能,你需要全鏈路跟蹤踢代。 它的原理很簡單盲憎,你可以在每...
    倚天碼農(nóng)閱讀 1,039評論 0 0
  • 注:本文轉(zhuǎn)自好友吳晟的兩篇譯文 奸鬓,譯文原文如下:https://github.com/opentracing-co...
    小程故事多閱讀 12,932評論 4 18
  • 前言:ZIPKIN作為當(dāng)前“分布式服務(wù)鏈路跟蹤”問題的流行解決方案之一掸读,正在被越來越多的公司和個人學(xué)習(xí)使用。其中很...
    PioneerYi閱讀 6,537評論 8 10
  • 有一次上課儿惫,一位學(xué)員問我,老師您怎么看蘇格拉底肾请?我說我不認識他呀,啊铛铁,那您怎么上課會提他,因為這樣會讓你們覺的我有...
    小二梅閱讀 511評論 0 6