扩展dubbo的filter接口实现链路信息上报给zipkin

一, 链路追踪的意义

一个分布式的系统, 由后台多个服务组成, 而一次请求会涉及到调用许多服务, 各服务又是分布式部署在各个节点, 这样对出了问题定位问题, 线上问题的处理是非常复杂, 费劲的. 通过分布式链路追踪可以很好的定位一次请求的调用链, 服务调用的流程信息, 服务响应时间, 服务的执行时间, 服务是否超时等.

比较好的一些分布式链路追踪框架有, zipkin, 美团点评的cat, skywalking, 京东的hydra(与dubbo结合密切). 本文将扩展dubbo的filter接口使用brave向zipkin上报数据, 以图形的形式展示链路调用信息.

二, 下载zipkin并启动.

wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec' java -jar zipkin.jar

zipkin.jar是一个spring-boot项目, 可以直接通过java -jar zipkin.jar命令启动, 访问http://ip:port/9411即可. 如下图, 即表示zipkin启动成功:

注意: zipkin可以使用三种方式启动, 一是, in-memory内存形式启动, 重启后上一次的链路追踪信息丢失; 二是, MySQL方式, 将链路追踪信息保存在数据库; 三是, Elasticsearch方式; 这里采用的是简单的in-memory内存方式启动.

三, 扩展dubbo的filter接口

1 在classpath下创建META-INF/dubbo文件, 并添加tracing=com.xxz.dubbo.DubboTracingFilter其实现类.

2 创建DubboTracingFilter类

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("===>进入链路追踪..");
        RpcContext rpcContext = RpcContext.getContext();
        //  判断此次调用是作为服务端还是客户端
        Span.Kind kind = rpcContext.isProviderSide() ? Span.Kind.SERVER : Span.Kind.CLIENT;
        final Span span;
        if (kind.equals(Span.Kind.CLIENT)) {
            // 若是rpc的客户端调用会从ThreadLocal中获取parent的 TraceContext ,
            // 为新生成的Span指定traceId及 parentId如果没有parent traceContext 
            // 则生成的Span为 root span
            span = tracer.nextSpan();
            // 将Span绑定的TraceContext中 属性信息复制到 Invocation中, 达到远程参数透传的作用
            injector.inject(span.context(), invocation.getAttachments());
        } else {
            // 若此次是rpc的服务提供端 , 从invocation中提取TraceContext相关信息及采样数据信息
            TraceContextOrSamplingFlags extracted = extractor.extract(invocation.getAttachments());
            // 生成span , 并且判断此次是否是首次调用(判断TraceContext == null)
            span = extracted.context() != null ? tracer.joinSpan(extracted.context()) : tracer.nextSpan(extracted);
        }
        
        if (!span.isNoop()) {
            span.kind(kind).start();
            // 记录接口信息及远程IP Port, 用于在zipkin的web页面上展示具体服务节点调用信息
            String service = invoker.getInterface().getSimpleName();
            String method = RpcUtils.getMethodName(invocation);
            span.kind(kind);
            span.name(service + "/" + method);
            InetSocketAddress remoteAddress = rpcContext.getRemoteAddress();
            span.remoteIpAndPort(
                    remoteAddress.getAddress() != null ? remoteAddress.getAddress().getHostAddress() : remoteAddress.getHostName(),remoteAddress.getPort());
        }
        
        boolean isOneway = false, deferFinish = false;
        try (Tracer.SpanInScope scope = tracer.withSpanInScope(span)){
            // 将创建的Span 作为当前Span (可以通过Tracer.currentSpan 访问到它) 并设置查询范围
            collectArguments(invocation, span, kind);
            // 执行真正的调用逻辑
            Result result = invoker.invoke(invocation);

            if (result.hasException()) {
                onError(result.getException(), span);
            }
            // oneway调用即只请求不接受结果
            isOneway = RpcUtils.isOneway(invoker.getUrl(), invocation);
            // 如果future不为空则为 async 调用 在回调中finish span
            Future<Object> future = rpcContext.getFuture();

            if (future instanceof FutureAdapter) {
                deferFinish = true;
                // 设置异步回调,回调代码执行span finish() .
                ((FutureAdapter) future).getFuture().setCallback(new FinishSpanCallback(span));
            }
            return result;
        } catch (Error | RuntimeException e) {
            onError(e, span);
            throw e;
        } finally {
            // oneway调用 因为不需等待返回值 即没有 cr (Client Receive) 需手动flush()
            if (isOneway) {
                span.flush();
            } 
            // 同步调用 业务代码执行完毕后需手动finish()
            else if (!deferFinish) {
                span.finish();
            }
        }
    }

3 使用

项目中直接引用该模块, 并在classpath下创建tracing_properties文件, 配置zipkin地址以及名称.

    <!-- 实现dubbo-tracing-filter -->
  	<dependency>
  		<groupId>com.xxz</groupId>
  		<artifactId>dubbo-tracing-filter</artifactId>
  		<version>0.0.1-SNAPSHOT</version>
  	</dependency>

 

四, 结果展示

1 调用链 : dubbo-web调用dubbo-serviceA服务, cs=null, sr=112, ss=243, cr=244;

服务调用者ip:port = 192.168.99.1:50094, 服务提供者ip:port=192.168.99.1:20881

一次调用产生唯一的traceId = 6b0581012154dc9d, 首次调用产生spanId = 6b0581012154dc9d, 没有parentId.

2 调用链 : serviceA -->serviceB, cs=220, sr=232, ss=236, cr=237;

服务调用者ip:port = 192.168.99.1:50085, 服务提供者ip:port=192.168.99.1:20882

traceId: 6b0581012154dc9d,  一次调用是唯一的traceId

spanId: ad279a1a9e7b0511, 调用一个新服务, 产生一个新spanId

parentId : 6b0581012154dc9d, 上一个服务调用者的spanId

类似的, serviceB-->serviceC, 同样也是上面这种展示形式, zipkin能按照层级, 将服务所在的节点ip:port, 以及服务的执行时间, 响应时间以图表的形式展示出来, 便于分析系统的性能瓶颈, 问题排错. 这对于大型分布式系统的链路稳定性定位, 排错是具有非常大的意义的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值