美文网首页分布式
分布式链路追踪系列四(Opentracing实战)

分布式链路追踪系列四(Opentracing实战)

作者: 一帅 | 来源:发表于2019-07-09 16:59 被阅读0次

    之前的几篇文章已经详细介绍了Opentracing产生的背景以及相关概念等,今天我们来使用Opentracing实战一把。

    Opentracing是个啥

    如果看过之前的文章的同学并且已经理解了Opentracing的同学可以忽略本小节。这一小节是给其他入门的人做一个简单的介绍,做到快速入门。

    我们首先来看看官方的定义

    Vendor-neutral APIs and instrumentation for distributed tracing

    中文翻译过来就是:为分布式追踪提供的厂商无关的api和工具

    厂商无关怎么理解?其实就是你可以任意添加(或更换)追踪系统的实现。说白了就是保证你有后悔药可以吃,用了一种追踪系统之后感觉不爽以后可以很爽快地换掉。

    如果你对于Opentracing还不了解的话,那我建议你可以阅读我之前的分布式链路追踪系列文章。如果实在是不想看的话,那我简单类比一下你肯定就懂了。

    Opentracing在分布式链路追踪领域中的作用就类似于JDBC在数据库访问中的作用一样,都是让使用者在面向接口编程,而不用管下层的实现,并且可以在几乎不修改代码的提前下非常简单地切换底层的实现。
    如下是两种标准在各自领域的作用和所处的层次。


    Opentracing
    JDBC

    啥?你还是不懂,那好吧,直接上代码吧。
    我们使用opentacing来埋个点

    // tracer可以替换为任意的追踪系统的实现,如Jaeger Tracer,Zipkin Tracer等,
    // tracer 提供的api就是厂商无关的api,tracer可任意替换
    Tracer tracer = new MockTracer();
    MockSpan span = tracer.buildSpan("foo").start();
    span.setTag(Tags.COMPONENT, "my-own-application");
    try{
        doSomething();
    }finally{
        span.finish();
    }
    

    埋点是个啥

    通俗点来讲,所谓埋点就是在一段代码的前后加上另外一段代码。那为啥要在一段代码上加上另外一段代码呢?无非就是想陌陌地干点"坏事"。


    那到底可能会干些啥坏事呢,比如监控某种业务的执行时间,执行次数,或者打印一些日志啥的。用Java里面的术语来讲就是面向切面编程。
    如下就是一段非常简单的埋点代码,用来记录方法的执行时间的。

    long start = System.currentTimeMillis();
    try{
      doSomething();
    }finally{
       log.info("time cost :"+(System.currentTimeMillis()-start));
    }
    

    啥叫用Opentracing埋点呢

    就是使用Opentracing的api在应用中植入分布式追踪功能

    埋点环境

    埋点说白了基本上跟上面的例子一样,就是前后加一段代码就OK了,非常简单。要真这么简单,那老夫今天还讲个毛啊。

    首先,埋点不是你想埋就能埋的,这受限于你埋点的环境。比如,被埋点代码是可以直接修改的,那当然可以直接修改源代码进行埋点。但是如果你拿不到源代码。比如一些开源的框架,你又该如何埋点呢?

    下面我们就来说说这两种场景下的埋点

    可直接修改源代码情况下的埋点

    一般来讲,可以拿到源代码的情况一般是自己的业务代码,或者是公司内部自己的框架。这种情况下的埋点相对来讲比较容易,因为代码都掌握在你自己手上了,还不是你想怎么埋就怎么埋。但是这个里面也有一个问题,就是如果你要埋点的地方比较多的话,那么最好在一个统一的收口的地方来埋点,不然不仅工作量大,而且容易漏掉一些地方。后面会详细讲这种情况的埋点策略

    不可修改源代码情况下的埋点

    如果你们使用的是开源的框架的话,那么几乎就是不可修改源代码的情况。那这种情况下又该如何埋点呢?这个问题其实不用担心,现在的开源框架基本都预留了扩展点来让用户实现自己特有的业务,所以对于这种情况就是寻找框架的扩展点,然后定制自己的埋点业务。比如SpringMvc的埋点就可以使用HandlerInterceptor来埋点,其他框架的埋点我们后面再详细地讲

    埋点方式

    在分布式追踪领域埋点基本可以分为两种方式:

    • 自动探针模式
    • 手动埋点
      自动探针模式在Java里面都是使用Java Intrumentation、Java agent技术来实现的,如国内的skywalking,韩国棒子的pinpoint,以及国内的商品 APM听云等都是自动探针模式来埋点的。不过这种埋点方式对于前期的投入比较大,研发难度相对而言比较高。我们今天暂时不讨论这种埋点方式。
      我们今天只讨论使用Opentracing手动埋点方式。
      手动

    那是不是说手动埋点方式一定比较low呢?也不一定。因为自动探针好就好在开发人员完全不需要关心分布式追踪,就自动有了分布式追踪功能,真正做到了零侵入地埋点接入到分布式追踪系统

    那手动埋点是不是就完全就做不到零侵入地接入呢?对,是的。但是我们可以做到几乎零侵入的埋点,尤其是现在有了SpringBoot这种自动装配的框架,更是更非常容易地实现几乎零侵入*地埋点功能。

    下面我们就来讲一下在SpringBoot环境下的埋点

    SpringBoot环境下的埋点

    发车

    首先SpringBoot为我们提供了很多的扩展点,使得我们可以任意地定制我们自己想要的功能,这为几乎零侵入的埋点提供了可能。

    我们来简单看一下SpringBoot环境下需要为哪些组件提供埋点功能

    • SpringMvc或者纯Java Web
    • Http访问(RestTemplate,Feign)
    • 数据库访问
    • Redis访问(RedisTemplate)
    • 消息中间件的埋点(rabbitmq,kafka等)

    对于SpringMvc来讲,我们可以通过定制HandlerInterceptor来拦截Http请求,如果没有使用SpringMvc,那么可以定制Filter来实现对http请求的埋点。对Http访问组件RestTemplate可以定制ClientHttpRequestInterceptor来实现埋点。具体可以参考java-spring-web。对于这种埋点方式就是要注意埋点组件在框架中的顺序。就是说如果是对于Springmvc来讲HandlerInterceptor的埋点组件一定是第一个执行的,不然如果有其他的用户自定义的HandlerInterceptor,那么就会丢失这一部分的执行时间。ClientHttpRequestInterceptor埋点也要注意这个问题。

    然后是对于数据库访问的埋点。如果是针对每种不同类型的数据库访问进行埋点,那么需要研究每种数据库驱动的扩展点,比如mysql5.x的驱动可以扩展com.mysql.jdbc.StatementInterceptorV2来实现对mysql数据库访问的埋点。那对于其他类型的数据库访问就需要研究其他驱动的扩展点,相对来讲比较繁琐特别是对于公司内使用了多种数据库的情况。所以我们可以在Jdbc层做埋点。这就是标准的好处。

    但是Jdbc层又没有合适的扩展点来做Jdbc的埋点,那怎么办呢?
    这种情况下就可以使用Spring AOP + 静态代理的方法。具体可以参考opentracing-spring-cloud-jdbc-starter。那这种方法总结起来就是:Spring Aop在运行时获取Connnection,然后再将原生的Connnection替换为Opentracing的Connnection。

    那其实还有另外一种比较吃力的做法,使用BeanFactoryPostProcessor来适配各种数据源连接池的方式,比如一开始sofa-tracer就是使用的这种方式,具体代码参考DataSourceBeanFactoryPostProcessor 。不过这种方式是有缺陷(DataSource埋点接入配置问题 )的,所以后来官方又提供了BeanPostProcessor的支持。

    所以个人不建议使用BeanFactoryPostProcessor来做埋点。

    对于RedisTemplate来讲也没有类似于RestTemplate的Interceptor扩展点,所以我们就可以采用静态代理的方式来把原生的RedisConnection替换为具有Opentracing功能的RedisConnection。关键是什么时机替换?可以使用Spring Aop在运行时替换,也可以在使用Spring容器提供的扩展点BeanPostProcessor来偷偷替换掉默认的RedisConnectionFactory。如果对BeanPostProcessor 不熟悉的同学,可以参考我的另外一篇文章Spring扩展点总结。不过对于BeanPostProcessor 的使用需要注意二次代理的问题### spring的二次代理原因及如何排查

    那对于消息中间件的埋点也基本类似。这里就不一一说明了,对于rabbitmq的埋点可以参考java-spring-rabbitmq

    非SpringBoot环境下的埋点

    如果不是使用Spring的组件,比如是自己公司的框架,或者一些工具类(比如httpclient)之类的,那么埋点也基本跟SpringBoot下的埋点类似,只不过SpringBoot下的埋点几乎可以做到零侵入,而非SpringBoot环境下的埋点大多数情况下需要用户修改一点代码。如
    java-apache-httpclient在使用的时候就需要讲HttpClientBuilder对象替换为TracingHttpClientBuilder对象。java-redis-client在使用原生jedis的时候就需要讲Jedis对象替换为TracingJedis对象。

    其基本思想都一样,都是静态代理

    那有没有其他的方式来做埋点呢?可以使用动态代理。比如你的框架定义了非常多的方法,而且有可能不停的增加方法,那么久会写非常多的代理方法,而且还存在反复修改的情况。所以如果有一个统一的访问入口来收口的话,那么只需要埋一次点就万事大吉了。我们公司的自研的redis访问组件就是采用的这种思想,因为redis的命令太多了,光写静态代理就得写个几百个方法,要累死人,所以这种动态代理(无论是jdk原生代理还是cglib代理都可以)的方式比较合适。

    但是静态代理的优点是参数含义比较确定。比如在对redis埋点的时候,静态代理是可以明确redis的key是什么值的。但是动态代理就不太能确定key到底是什么,所以动态代理无法明确每一个参数的含义,有可能会影响埋点的质量。需要做权衡。

    埋点方式总结

    总结
    埋点方式 适用场景 注意点 优先级
    定制框架内置的inteceptor,filter等扩展点 只要框架支持就优先考虑 组件顺序 最先考虑
    Spring aop + 静态代理 框架没有预留扩展点 重复埋点 第二选择
    Spring aop + BeanPostProcessor 框架没有预留扩展点 防止二次代理的问题 第二选择
    动态代理 静态代理需要代理的放非常多而且比较容易变动方法定义的时候 处理好参数含义 静态代理太麻烦的时候考虑

    埋点之坑

    使用Spring Aop + 静态代理的方法来埋点的时候要注意,被替换掉的对象可能存在多种代理,在切面中要处理重复代理的情况。如Bug Report jdbc generate duplicated span

    在使用Opentracing埋点的时候不要忘记对异常情况的处理。如when error occurs,it do not report error span

    如果对底层的api不熟悉的话,那么很容易在埋点的时候忽略了一些性能上的问题。如JdbcAspect.getConnection will execute SELECT USER() every time

    如果埋点做得比较完善的话,那么即使是各种极端情况,也不会对应用产生影响。如Redis keys length should be limited ,以及解析jdbc url出错的情况下的异常处理,如
    add jdbc url parser for adding for database information to the trace

    相关文章

      网友评论

        本文标题:分布式链路追踪系列四(Opentracing实战)

        本文链接:https://www.haomeiwen.com/subject/lpuiiqtx.html