zipkin鏈路跟蹤

Zipkin是什么


Zipkin是一款開源的分布式實(shí)時(shí)數(shù)據(jù)追蹤系統(tǒng)(Distributed Tracking System)缺前,基于 Google Dapper的論文設(shè)計(jì)而來(lái)漂彤,由 Twitter 公司開發(fā)貢獻(xiàn)慨削。其主要功能是聚集來(lái)自各個(gè)異構(gòu)系統(tǒng)的實(shí)時(shí)監(jiān)控?cái)?shù)據(jù)旨涝。分布式跟蹤系統(tǒng)還有其他比較成熟的實(shí)現(xiàn),例如:Naver的Pinpoint驾锰、Apache的HTrace喊儡、阿里的鷹眼Tracing、京東的Hydra稻据、新浪的Watchman艾猜,美團(tuán)點(diǎn)評(píng)的CAT,skywalking等捻悯。

為什么使用 Zipkin


隨著業(yè)務(wù)越來(lái)越復(fù)雜匆赃,系統(tǒng)也隨之進(jìn)行各種拆分,特別是隨著微服務(wù)架構(gòu)和容器技術(shù)的興起今缚,看似簡(jiǎn)單的一個(gè)應(yīng)用算柳,后臺(tái)可能有幾十個(gè)甚至幾百個(gè)服務(wù)在支撐;一個(gè)前端的請(qǐng)求可能需要多次的服務(wù)調(diào)用最后才能完成姓言;當(dāng)請(qǐng)求變慢或者不可用時(shí)瞬项,我們無(wú)法得知是哪個(gè)后臺(tái)服務(wù)引起的,這時(shí)就需要解決如何快速定位服務(wù)故障點(diǎn)何荚,Zipkin分布式跟蹤系統(tǒng)就能很好的解決這樣的問(wèn)題囱淋。

Zipkin 架構(gòu)


zipkin架構(gòu)

如圖所示,Zipkin 主要由四部分構(gòu)成:收集器餐塘、數(shù)據(jù)存儲(chǔ)妥衣、查詢以及 Web 界面。Zipkin 的收集器負(fù)責(zé)將各系統(tǒng)報(bào)告過(guò)來(lái)的追蹤數(shù)據(jù)進(jìn)行接收戒傻;而數(shù)據(jù)存儲(chǔ)默認(rèn)使用 Cassandra税手,也可以替換為 MySQL;查詢服務(wù)用來(lái)向其他服務(wù)提供數(shù)據(jù)查詢的能力需纳,而 Web 服務(wù)是官方默認(rèn)提供的一個(gè)圖形用戶界面芦倒。

而各個(gè)異構(gòu)的系統(tǒng)服務(wù)向 Zipkin 報(bào)告數(shù)據(jù)的架構(gòu)如下圖。


跟蹤信息采集

Zipkin 客戶端 Brave


Brave 是用來(lái)裝備 Java 程序的類庫(kù)不翩,提供了面向 Standard Servlet兵扬、Spring MVC麻裳、Http Client、JAX RS周霉、Jersey、Resteasy 和 MySQL 等接口的裝備能力亚皂,可以通過(guò)編寫簡(jiǎn)單的配置和代碼俱箱,讓基于這些框架構(gòu)建的應(yīng)用可以向 Zipkin 報(bào)告數(shù)據(jù)。同時(shí) Brave 也提供了非常簡(jiǎn)單且標(biāo)準(zhǔn)化的接口灭必,在以上封裝無(wú)法滿足要求的時(shí)候可以方便擴(kuò)展與定制狞谱。

如下圖是Brave的結(jié)構(gòu)圖。Brave利用reporter向zipkin的Collector發(fā)送trace信息禁漓。


Brave

Zipkin安裝

docker-compose.yml

version: '2'

services:
  # The zipkin process services the UI, and also exposes a POST endpoint that
  # instrumentation can send trace data to. Scribe is disabled by default.
  zipkin:
    image: openzipkin/zipkin
    container_name: zipkin
    environment:
      #- STORAGE_TYPE=mem
      - STORAGE_TYPE=mysql
      # Point the zipkin at the storage backend
      - MYSQL_DB=zipkin
      - MYSQL_USER=root
      - MYSQL_PASS=123456
      - MYSQL_HOST=192.168.1.8
      - MYSQL_TCP_PORT=3306
      # Uncomment to enable scribe
      # - SCRIBE_ENABLED=true
      # Uncomment to enable self-tracing
      # - SELF_TRACING_ENABLED=true
      # Uncomment to enable debug logging
      # - JAVA_OPTS=-Dlogging.level.zipkin=DEBUG -Dlogging.level.zipkin2=DEBUG
    ports:
      # Port used for the Zipkin UI and HTTP Api
      - 9411:9411
      # Uncomment if you set SCRIBE_ENABLED=true
      # - 9410:9410
    #networks: 
    #  - default 
    #  - my_net #創(chuàng)建網(wǎng)路 docker network create my_net 刪除網(wǎng)絡(luò) docker network rm my_net
#networks: 
  #my_net: 
    #external: true

訪問(wèn)測(cè)試: http://localhost:9411

Dubbo 集成 Zipkin

    1. POM.xml
    <properties>
        <brave.version>4.19.2</brave.version>
        <zipkin-reporter.version>2.1.3</zipkin-reporter.version>
        <zipkin.version>2.8.1</zipkin.version>
    </properties>
    <dependencies>
        <!-- 非WEB啟動(dòng)器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- dubbo啟動(dòng)器 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-reporter</artifactId>
            <version>2.6.0</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-sender-okhttp3</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave</artifactId>
            <version>4.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-context-log4j2</artifactId>
            <version>4.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-http</artifactId>
            <version>4.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-http-tests</artifactId>
            <version>4.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-servlet</artifactId>
            <version>4.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-tests</artifactId>
            <version>4.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.zipkin2</groupId>
            <artifactId>zipkin</artifactId>
            <version>${zipkin.version}</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin</artifactId>
            <version>${zipkin.version}</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-junit</artifactId>
            <version>${zipkin.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.13</version>
        </dependency>
    </dependencies>
    1. JAVA配置類
package com.suoron.login.api.config;

import brave.Tracing;
import brave.servlet.TracingFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.Reporter;
import zipkin2.reporter.Sender;
import zipkin2.reporter.okhttp3.OkHttpSender;
import zipkin2.Span;
import javax.servlet.Filter;

@Configuration
public class TracingConfig {

    /**
     * 配置zipkin服務(wù)地址
     */
    @Value("${zipkin.tracing.endpoint:http://10.3.135.166:9411/api/v2/spans}")
    private String zipkinEndPoint;

    @Value("${zipkin.tracing.local-service-name:local-service-name}")
    private String localServiceName;

    /**
     * 配置sender
     * @return
     */
    @Bean
    public Sender sender(){
        OkHttpSender sender = OkHttpSender
                .newBuilder()
                .endpoint(zipkinEndPoint)
                .build();
        return sender;
    }

    /**
     * 配置reporter
     * @param sender
     * @return
     */
    @Bean
    public Reporter<Span> reporter(Sender sender){
        return AsyncReporter
                .builder(sender)
                .build();
    }

    /**
     * 配置dubbo-consumer tracing
     * @param reporter
     * @return
     */
    @Bean
    public Tracing tracing(Reporter reporter){
        return Tracing.newBuilder()
                .localServiceName(localServiceName)
                .spanReporter(reporter)
                .build();
    }

    /**
     * 配置http tracing
     * @param reporter
     * @return
     */
    @Bean
    public Tracing tracing2(Reporter reporter){
        return Tracing.newBuilder()
                .localServiceName(localServiceName + "_http")
                .spanReporter(reporter)
                .build();
    }

    /**
     * 配置servlet filter
     * @param tracing2
     * @return
     */
    @Bean
    public Filter filter(Tracing tracing2){
        return TracingFilter.create(tracing2);
    }

    /**
     * 注冊(cè)filter
     * @param filter
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistration(Filter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.addUrlPatterns("/*");
        registration.setName("zipkin-filter");
        registration.setOrder(1);
        return registration;
    }
}
    1. Dubbo過(guò)濾器
      DubboZipkinConsumerFilter.java
package com.suoron.login.api.filter;

import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContext;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.support.RpcUtils;
import com.suoron.login.api.utils.ZipkinHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

import static com.suoron.login.api.utils.ZipkinHelper.SETTER;

@Activate(group = Constants.CONSUMER)
public class DubboZipkinConsumerFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(DubboZipkinConsumerFilter.class);

    private SpringExtensionFactory springExtensionFactory = new SpringExtensionFactory();
    private Tracer tracer;

    // tracing上下文消息注入
    private TraceContext.Injector<Map<String, String>> injector;

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        log.info("dubbo zipkin consumer filter......");

        Tracing tracing = springExtensionFactory.getExtension(Tracing.class, "tracing");
        tracer = tracing.tracer();
        if (tracer == null){
            return invoker.invoke(invocation);
        }

        if (null == injector){
            injector = tracing.propagation().injector(SETTER);
        }

        RpcContext rpcContext = RpcContext.getContext();
        Span span = tracer.nextSpan();
        injector.inject(span.context(), invocation.getAttachments());

        ZipkinHelper.buildSpan(span, Span.Kind.CONSUMER, rpcContext.getRemoteAddress(), invoker.getInterface().getSimpleName(),
                RpcUtils.getMethodName(invocation));

        return ZipkinHelper.spanTracing(span, tracer, invoker, invocation, rpcContext);
    }

}

DubboZipkinProviderFilter.java

package com.suoron.login.api.filter;

import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.support.RpcUtils;
import com.suoron.login.api.utils.ZipkinHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

import static com.suoron.login.api.utils.ZipkinHelper.*;

@Activate(group = Constants.PROVIDER)
public class DubboZipkinProviderFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(DubboZipkinProviderFilter.class);

    private SpringExtensionFactory springExtensionFactory = new SpringExtensionFactory();
    private Tracer tracer;

    // tracing上下文消息提取
    private TraceContext.Extractor<Map<String, String>> extractor;

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        log.info("dubbo zipkin provider filter......");

        Tracing tracing = springExtensionFactory.getExtension(Tracing.class, "tracing");
        tracer = tracing.tracer();
        if (null == tracer){
            return invoker.invoke(invocation);
        }

        if (null == extractor){
            extractor = tracing.propagation().extractor(GETTER);
        }

        TraceContextOrSamplingFlags extracted = extractor.extract(invocation.getAttachments());
        Span span = extracted.context() != null
                ? tracer.joinSpan(extracted.context())
                : tracer.nextSpan(extracted);

        RpcContext rpcContext = RpcContext.getContext();
        ZipkinHelper.buildSpan(span, Span.Kind.SERVER, rpcContext.getRemoteAddress(), invoker.getInterface().getSimpleName(),
                RpcUtils.getMethodName(invocation));

        return ZipkinHelper.spanTracing(span, tracer, invoker, invocation, rpcContext);
    }
}

ZipkinHelper.java -- 輔助類

package com.suoron.login.api.utils;

import brave.Span;
import brave.Tracer;
import brave.propagation.Propagation;
import com.alibaba.dubbo.remoting.exchange.ResponseCallback;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.dubbo.FutureAdapter;
import com.alibaba.dubbo.rpc.support.RpcUtils;
import com.alibaba.fastjson.JSON;
import zipkin2.Endpoint;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.Future;

/**
 * @author Suoron
 * @Description:
 * @date 2018/6/22 10:44
 */
public class ZipkinHelper {

    public static final Propagation.Setter<Map<String, String>, String> SETTER =
            new Propagation.Setter<Map<String, String>, String>() {
                @Override
                public void put(Map<String, String> carrier, String key, String value) {
                    carrier.put(key, value);
                }
                @Override
                public String toString() {
                    return JSON.toJSONString(this);
                }
            };
    public static final Propagation.Getter<Map<String, String>, String> GETTER =
            new Propagation.Getter<Map<String, String>, String>() {
                @Override
                public String get(Map<String, String> carrier, String key) {
                    return carrier.get(key);
                }

                @Override
                public String toString() {
                    return JSON.toJSONString(this);
                }
            };

    public static void buildSpan(Span span, Span.Kind kind, InetSocketAddress remoteAddress, String service, String method){
        if (!span.isNoop()) {
            span.kind(kind).start();
            span.kind(kind);
            span.name(service + "/" + method);
            Endpoint.Builder remoteEndpoint = Endpoint.newBuilder().port(remoteAddress.getPort());
            if (!remoteEndpoint.parseIp(remoteAddress.getAddress())) {
                remoteEndpoint.parseIp(remoteAddress.getHostName());
            }
            span.remoteEndpoint(remoteEndpoint.build());
        }
    }

    public static Result spanTracing(Span span, Tracer tracer, Invoker<?> invoker, Invocation invocation, RpcContext rpcContext){
        boolean isOneway = false;
        boolean deferFinish = false;
        try (Tracer.SpanInScope scope = tracer.withSpanInScope(span)) {
            Result result = invoker.invoke(invocation);
            if (result.hasException()) {
                onError(result.getException(), span);
            }
            isOneway = RpcUtils.isOneway(invoker.getUrl(), invocation);
            Future<Object> future = rpcContext.getFuture(); // the case on async client invocation
            if (future instanceof FutureAdapter) {
                deferFinish = true;
                ((FutureAdapter) future).getFuture().setCallback(new ZipkinHelper.FinishSpanCallback(span));
            }
            return result;
        } catch (Exception e) {
            onError(e, span);
            throw e;
        } finally {
            if (isOneway) {
                span.flush();
            } else if (!deferFinish) {
                span.finish();
            }
        }
    }

    public static void onError(Throwable error, Span span) {
        span.error(error);
        if (error instanceof RpcException) {
            span.tag("dubbo.error_code", Integer.toString(((RpcException) error).getCode()));
        }
    }

    public static final class FinishSpanCallback implements ResponseCallback {
        final Span span;

        FinishSpanCallback(Span span) {
            this.span = span;
        }

        @Override
        public void done(Object response) {
            span.finish();
        }

        @Override
        public void caught(Throwable exception) {
            onError(exception, span);
            span.finish();
        }
    }
}
    1. 配置文件
      META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.suoron.login.api.config.TracingConfig

META-INF/dubbo/com.alibaba.dubbo.rpc.Filter

zipkinConsumerDubboFilter=com.suoron.login.api.filter.DubboZipkinConsumerFilter
zipkinProviderDubboFilter=com.suoron.login.api.filter.DubboZipkinProviderFilter

application.yml

# 消費(fèi)端配置
zipkin:
  tracing:
    local-service-name: login-consumer
# 提供者配置
zipkin:
  tracing:
    local-service-name: login-provider
    1. 過(guò)濾器使用
// 提供者接口實(shí)現(xiàn)類
@Service(version = "1.0.0",token = "12345678",filter = "zipkinProviderDubboFilter")
public class ILoginServiceImpl implements ILoginService {
    ...
}
// 消費(fèi)者調(diào)用類
    @Reference(version = "1.0.0",filter = "zipkinConsumerDubboFilter")
    public ILoginService iLoginService;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跟衅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子播歼,更是在濱河造成了極大的恐慌伶跷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秘狞,死亡現(xiàn)場(chǎng)離奇詭異叭莫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)烁试,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門雇初,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人减响,你說(shuō)我怎么就攤上這事靖诗。” “怎么了支示?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵刊橘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我颂鸿,道長(zhǎng)伤为,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任据途,我火速辦了婚禮绞愚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颖医。我一直安慰自己位衩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布熔萧。 她就那樣靜靜地躺著糖驴,像睡著了一般僚祷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贮缕,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天辙谜,我揣著相機(jī)與錄音,去河邊找鬼感昼。 笑死装哆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的定嗓。 我是一名探鬼主播蜕琴,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宵溅!你這毒婦竟也來(lái)了凌简?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恃逻,失蹤者是張志新(化名)和其女友劉穎雏搂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寇损,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畔派,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了润绵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片线椰。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尘盼,靈堂內(nèi)的尸體忽然破棺而出憨愉,到底是詐尸還是另有隱情,我是刑警寧澤卿捎,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布配紫,位于F島的核電站,受9級(jí)特大地震影響午阵,放射性物質(zhì)發(fā)生泄漏躺孝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一底桂、第九天 我趴在偏房一處隱蔽的房頂上張望植袍。 院中可真熱鬧,春花似錦籽懦、人聲如沸于个。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厅篓。三九已至秀存,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間羽氮,已是汗流浹背或链。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留档押,地道東北人澳盐。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像汇荐,于是被迫代替她去往敵國(guó)和親洞就。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盆繁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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