zoukankan      html  css  js  c++  java
  • [源码分析] OpenTracing之跟踪Redis

    [源码分析] OpenTracing之跟踪Redis

    0x00 摘要

    本文将通过研究OpenTracing的Redis埋点插件来进一步深入理解OpenTracing。

    0x01 总体逻辑

    1.1 相关概念

    Tracer是用来管理Span的统筹者,负责创建span和传播span。它表示从头到尾的一个请求的调用链,是一次完整的跟踪,从请求到服务器开始,服务器返回response结束,跟踪每次rpc调用的耗时。它的标识符是“traceID”。

    Span是一个更小的单位,表示一个RPC调用过程。一个“trace”包含有许多跨度(span),每个跨度捕获调用链内的一个工作单元(系统或服务节点),并由“spanId”标识。 每个跨度具有一个父跨度,并且一个“trace”的所有跨度形成有向无环图(DAG)。

    1.2 埋点插件

    对一个应用的跟踪要关注的无非就是 客户端——>web 层——>rpc 服务——>dao 后端存储、cache 缓存、消息队列 mq 等这些基础组件。OpenTracing 插件的作用实际上也就是对不同组件进行埋点,以便基于这些组件采集应用的链路数据。

    不同组件有不同的应用场景和扩展点,因此针对不同的框架,需要开发对应的OpenTracing API 插件用来实现自动埋点。

    对于Redis来说各种插件更是层出不穷,所以OpenTracing 对与 Redis 各种插件也做了不同处理,比如 Jedis,Redisson,Spring Data Redis 2.x。本文主要是以 Redisson 为例说明,最后用spring-cloud-redis进行补充对照**。

    1.3 总体逻辑

    总体思路是使用代理模式。因为 Redis 并没有提供像 Servlet 那样的过滤器或者拦截器,所以 Redis OpenTracing 插件没有进行常规埋点,而是通过组合的方式自定义若干代理类,比如 TracingRedissonClient 和 TracingRList .....。

    • TracingRedissonClient 代理了 Redis Client。
    • TracingRList 代理了Redis List 数据结构。
    • 还有其他类代理其他Redis数据结构,比如TracingRMap。

    这些代理类将具体完成Tracing 功能。比如代理类 TracingRedissonClient 包含了两个成员变量:

    • private final RedissonClient redissonClient; 是真正的 Redis Client。
    • private final TracingRedissonHelper tracingRedissonHelper; 是具体针对 Redission 的Tracing 功能类,比如构建Span。

    最后各种代理对 Redis 进行拦截:

    • 在执行具体的连接操作之前创建相关的 Span。
    • 在操作结束之后结束 Span,并进行上报。

    具体可以见下图

    +--------------------------+ +-------------------------+ +-------------------------+
    |  TracingRedissonClient   | |       TracingRMap       | |      TracingRList       |
    | +----------------------+ | | +---------------------+ | | +---------------------+ |
    | |   RedissonClient     | | | |       RMap          | | | |      RList          | | ....
    | |                      | | | |                     | | | |                     | |
    | | TracingRedissonHelper| | | |TracingRedissonHelper| | | |TracingRedissonHelper| |
    | +----------------------+ | | +---------------------+ | | +---------------------+ |
    +--------------------------+ +-------------------------+ +-------------------------+
                |                             |                            |
                |                             |                            |
                |                             |                            |
                |                             |                            |
                |                             v                            |
                |             +---------------+-----------------+          |
                +-----------> |      TracingRedissonHelper      | <--------+
                              | +-----------------------------+ |
                              | |         Tracer              +-----+
                              | +-----------------------------+ |   |
                              +---------------------------------+   |
                                                                    |
                              +---------------------------------+   |
                              |       TracingConfiguration      |   |
                              |  +----------------------------+ |   |
                              |  |        Tracer            <-------+
                              |  +----------------------------| |
                              +---------------------------------+
    

    下图是为了手机观看。

    0x02 示例代码

    我们使用代码自带的test来做说明。我们可以看到有两个代理类 TracingRedissonClientTracingRList

    • beforeClass 起到了系统启动的作用。
      • 首先定义了一个tracer(这里是MockTracer,真正使用时候会用到其他Tracer)。
      • 然后使用这个Tracer来构建一个代理类 TracingRedissonClient
    • 后续各种测试操作都是使用这个client在进行Redis操作。
      • 会通过代理类 TracingRedissonClient 得到一个 org.redisson.api.RList 以备后续操作。这个 RList 实际是OpenTracing 进行修改的另一个代理类 TracingRList
      • 会对这个 TracingRList 进行操作 :list.add("key");
      • 针对 Redisson 的异步操作,也进行了操作测试。

    具体代码如下:

    public class TracingRedissonTest {
      private static final MockTracer tracer = new MockTracer();
      private static RedisServer redisServer;
      private static RedissonClient client;
      
      @BeforeClass
      public static void beforeClass() {
        redisServer = RedisServer.builder().setting("bind 127.0.0.1").build();
        redisServer.start();
    
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
    
        client = new TracingRedissonClient(Redisson.create(config),
            new TracingConfiguration.Builder(tracer).build());
      }
      
      @Test
      public void test_list() {
        RList<Object> list = client.getList("list");
    
        list.add("key");
        assertTrue(list.contains("key"));
    
        List<MockSpan> spans = tracer.finishedSpans();
        assertEquals(2, spans.size());
        checkSpans(spans);
        assertNull(tracer.activeSpan());
      }  
      
      @Test
      public void test_config_span_name() throws Exception {
        ......
    
        final MockSpan parent = tracer.buildSpan("test").start();
        try (Scope ignore = tracer.activateSpan(parent)) {
          RMap<String, String> map = customClient.getMap("map_config_span_name");
          map.getAsync("key").get(15, TimeUnit.SECONDS);
        }
        parent.finish();
    
        ......
      }  
    }
    

    0x03 Redis代理

    前面我们提到了对于Redis是使用了代理来完成功能,下面我们具体来讲解。

    3.1 Client 代理类

    TracingRedissonClient 实现了 Redis Client 代理功能,其包含两个成员变量。

    • RedissonClient redissonClient; 是真正的Redis Client,代理类最终是通过此Client进行Redis操作。
    • TracingRedissonHelper tracingRedissonHelper; 完成了Tracing 功能。

    具体在使用中,比如在测试代码会通过 Client 代理类得到一个 TracingRList 以备后续操作(这是另一个代理类)。

    • TracingRList 实现了 org.redisson.api.RList 接口。
    • 在构建 TracingRList 会把 TracingRedissonHelper 作为参数传递进去。
    RList<Object> list = client.getList("list");
    

    具体代码如下:

    public class TracingRedissonClient implements RedissonClient {
      private final RedissonClient redissonClient;
      private final TracingRedissonHelper tracingRedissonHelper;
    
      public TracingRedissonClient(RedissonClient redissonClient, TracingConfiguration configuration) {
        this.redissonClient = redissonClient;
        this.tracingRedissonHelper = new TracingRedissonHelper(configuration);
      }
      
      @Override
      public <V> RList<V> getList(String name) {
        // 通过代理生成
        return new TracingRList<>(redissonClient.getList(name), tracingRedissonHelper);
      }
    
      // 其他操作
      ......
    }
    

    3.2 List 代理类

    TracingRList 是Redis List代理类(Redis插件还有其他代理类,代理其他Redis数据结构)。里面也是两个变量:

    • RList list; 数据类型是 org.redisson.api.RList,实现了真正的 Redis功能。
    • TracingRedissonHelper 完成 Tracing 功能。

    在具体 add 函数中:

    • 在执行具体的命令前先通过 tracingRedissonHelper.buildSpan 构建 Span 进行埋点操作。
    • 然后添加 Tag。
    • 最后通过 tracingRedissonHelper.decorate 进行实际 Redis 操作。

    具体代码如下:

    public class TracingRList<V> extends TracingRExpirable implements RList<V> {
      private final RList<V> list;
      private final TracingRedissonHelper tracingRedissonHelper;
      
      @Override
      public boolean add(V element) {
        Span span = tracingRedissonHelper.buildSpan("add", list);
        span.setTag("element", nullable(element));
        return tracingRedissonHelper.decorate(span, () -> list.add(element));
      }
    
    	// 其他操作
      .....
    }
    

    0x04 Tracing功能类

    前面一直在提TracingRedissonHelper是Tracing功能类,下面我们就深入研究下 tracingRedissonHelper.decorate(span, () -> list.add(element)); 做了什么。

    4.1 配置类

    在初始化 Redis Client时候,生成了 TracingConfiguration。

    client = new TracingRedissonClient(Redisson.create(config),
        new TracingConfiguration.Builder(tracer).build());
    

    TracingConfiguration 之中就定义了io.opentracing.Tracer以及其他配置项。

    具体类定义如下:

    public class TracingConfiguration {
      static final int DEFAULT_KEYS_MAX_LENGTH = 100;
      private final Tracer tracer;
      private final boolean traceWithActiveSpanOnly;
      private final int keysMaxLength;
      private final Function<String, String> spanNameProvider;
      private final Map<String, String> extensionTags;
      
      // 其他操作
      ......
    }
    

    4.2 Tracing基础功能类

    io.opentracing.contrib.redis.common.TracingHelperOpenTracing 通用的 Redis Tracing 功能类,我们看到里面有 Tracer 变量(就是TracingConfiguration之中的Tracer),也有 SpanBuilder 这样的helper 函数。

    业务逻辑具体在 decorate 函数中有体现。参数 Supplier supplier 是 java.util.function.Supplier 类型。

    return tracingRedissonHelper.decorate(span, () -> list.add(object));
    

    Supplier 是 JAVA8 提供的接口,这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法。get方法返回一个泛型T,这就是一个创建对象的工厂。

    所以decorate的作用在我们这里就是:

    • tracer.scopeManager().activate(span) 来激活当前span。
    • 返回对象,执行Redis操作,我们例子就是 () -> list.add(element)
    • 调用 span.finish(); 完成了结束操作,如果采样就会上报。

    测试代码 执行流程图如下:

    TracingRList                  TracingHelper
          +                            +
      +---+--+                         |
      |  add | begin                   |
      +---+--+                         |
          |                            |
          |      invoke                |
          |                            v
          | ---------------->  +-------+------+
          |                    |   buildSpan  |
          | <---------------+  +-------+------+
          |       Return               |
          |                            |
      +---+-------+                    |
      |span.setTag|                    |
      +---+-------+                    |
          |                            |
          |                            |
          |                            |
          |     invoke   +-------------v-------------------------+
          | -----------> |decorate(span, () -> list.add(element))|
          |              +-------------+-------------------------+
          |                            |
          |                            |
          |                            |
          |                            v  begin tracing
          |              +-------------+----------------------+
          |              |tracer.scopeManager().activate(span)|
          |              +-------------+----------------------+
          |                            |
          |                            |
          |                            |
          |                            v  Real Redis action
    +-----+------------+  <----+ +-----+--------+
    | list.add(element)|         |supplier.get()|
    +-----+------------+  +----> +-----+--------+
          |                            |
          |                            |
          |                            v  end tracing
          |  decorate Return     +-----+-------+
          |  <----------------+  |span.finish()|
          |                      +-------------+
       +--+---+
       | add  | end
       +--+---+
          |
          |
          |
          v
    

    具体 TracingHelper 代码如下:

    public class TracingHelper {
    
      public static final String COMPONENT_NAME = "java-redis";
      public static final String DB_TYPE = "redis";
      protected final Tracer tracer;
      private final boolean traceWithActiveSpanOnly;
      private final Function<String, String> spanNameProvider;
      private final int maxKeysLength;
      private final Map<String, String> extensionTags;
      
      public Span buildSpan(String operationName) {
        if (traceWithActiveSpanOnly && tracer.activeSpan() == null) {
          return NoopSpan.INSTANCE;
        } else {
          return builder(operationName).start();
        }
      }
      
      private SpanBuilder builder(String operationName) {
        SpanBuilder sb = tracer.buildSpan(spanNameProvider.apply(operationName))
            .withTag(Tags.COMPONENT.getKey(), COMPONENT_NAME)
            .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
            .withTag(Tags.DB_TYPE.getKey(), DB_TYPE);
        extensionTags.forEach(sb::withTag);
        return sb;
      }  
      
      public <T> T decorate(Span span, Supplier<T> supplier) {
        try (Scope ignore = tracer.scopeManager().activate(span)) { // 激活span
          return supplier.get();  // 执行Redis操作
        } catch (Exception e) {
          onError(e, span);
          throw e;
        } finally {
          span.finish();// 完成了结束操作,如果采样就会上报。
        }
      }
      
      // 其他操作
      .....
    }
    

    4.3 Redission专用Tracing功能类

    TracingRedissonHelper 是具体实现了Redission 的 Tracing 功能,主要是针对异步操作。

    4.3.1 测试代码

    官方测试代码如下

    final MockSpan parent = tracer.buildSpan("test").start();
    try (Scope ignore = tracer.activateSpan(parent)) {
      RMap<String, String> map = customClient.getMap("map_config_span_name");
      map.getAsync("key").get(15, TimeUnit.SECONDS); // Redis异步操作
    }
    parent.finish();
    

    能看到,测试的思路是:

    • 生成一个parent Span
    • 然后使用redis map进行异步操作,getAsync这里会生成一个 client span。
    • parent span结束

    具体下面我们会讲解。

    4.3.2 TracingRedissonHelper

    TracingRedissonHelper 需要针对 Redisson 来进行特殊设置,就是因为Redisson同时还为分布式锁提供了异步执行的相关方法

    所以需要对异步操作进行处理。其中:

    • RFuture 是 org.redisson.api 包下面的类,
    • CompletableRFuture 是 io.opentracing.contrib.redis.redisson 包下面的类,针对 RFuture 做了特殊处理。

    prepareRFuture函数是执行Redis具体操作的函数,其作用如下:

    • 通过 futureSupplier.get(); 获取redisFuture( prepareRFuture的参数span是之前 getAsync 生成的child span )。
    • 设置 redisFuture 的 whenComplete函数,在whenComplete函数中会对传入的Span进行 finish操作 ,这个span 其实是child span。这样异步的Tracing通过Client Span完成
    • 继续操作,恢复parent span,在redisFuture基础上生成CompletableRFuture,然后继续设置redisFuture.whenComplete,如果redisFuture完成,则调用 customRedisFuture.complete。
    • 返回,外界测试函数会finish parent span

    针对官方测试代码,执行流程图如下:

    +------------+
    | Parent Span|
    +-----+------+
          |
          v
     TracingRMap                          TracingRedissonHelper
          +                                      +
          |                                      |
          |                                      v
     +----+-----+       Invoke              +----+------+
     | getAsync | +-----------------------> | buildSpan |create Child Span
     +----+-----+                           +----+------+
          |                                      |
          |                                      v
          |                                +-----+--------+
          |                                |prepareRFuture|
          |                                +-----+--------+
          |                                      |
          |                 Real redis action    v
    +-----+----------------+  <--------+ +-------+-------------+
    |() -> map.getAsync(key|             | futureSupplier.get()|
    +-----+----------------+  +--------> +-------+-------------+
          |                     Future           |
          |                                      |
          |                              +-------v---------+
          |                              |setCompleteAction|
          |                              +-------+---------+
          |                                      |
          |                                      |
          |                               +------v-------+
          |                               | whenComplete |
          |                               +------+-------+
          |                                      |
          |                                      v
          |                               +------+-------+
          |                               | span.finish()|  Child Span
          |                               +------+-------+
          |                                      |
          |                                      v
          |                             +--------+----------+
          |                             | continueScopeSpan |
          |                             +--------+----------+
          |                                      |
          |                                      v
          |                            +---------+----------+
          |                            | tracer.activeSpan()|
          |                            +---------+----------+
          |                                      | Parent Span
          |                                      |
          |                                      v
          |                                +-----+--------+
          |                                |activate(span)|
          |                                +-----+--------+
          |                                      |
          |                                      |
          |                                      v
          |      return             +------------+-----------------+
          |  <--------------------  | customRedisFuture.complete(v)|
          |                         +------------------------------+
          |
     +----v----------+
     |parent.finish()|
     +---------------+
    
    

    具体代码如下:

    class TracingRedissonHelper extends TracingHelper {
    
      TracingRedissonHelper(TracingConfiguration tracingConfiguration) {
        super(tracingConfiguration);
      }
    
      Span buildSpan(String operationName, RObject rObject) {
        return buildSpan(operationName).setTag("name", rObject.getName());
      }
    
      private <T> RFuture<T> continueScopeSpan(RFuture<T> redisFuture) {
        Span span = tracer.activeSpan();
        CompletableRFuture<T> customRedisFuture = new CompletableRFuture<>(redisFuture);
        redisFuture.whenComplete((v, throwable) -> {
          try (Scope ignored = tracer.scopeManager().activate(span)) {
            if (throwable != null) {
              customRedisFuture.completeExceptionally(throwable);
            } else {
              customRedisFuture.complete(v);
            }
          }
        });
        return customRedisFuture;
      }
    
      private <V> RFuture<V> setCompleteAction(RFuture<V> future, Span span) {
        future.whenComplete((v, throwable) -> {
          if (throwable != null) {
            onError(throwable, span);
          }
          span.finish();
        });
    
        return future;
      }
    
      <V> RFuture<V> prepareRFuture(Span span, Supplier<RFuture<V>> futureSupplier) {
        RFuture<V> future;
        try {
          future = futureSupplier.get();
        } catch (Exception e) {
          onError(e, span);
          span.finish();
          throw e;
        }
    
        return continueScopeSpan(setCompleteAction(future, span));
      }
    }
    

    4.4 TracingRMap代理类的异步处理

    TracingRMap 实现了 org.redisson.api.RMap。这里就使用了上述的异步相关的功能,比如 getAsync。

    所以调用了 prepareRFuture 的功能。

    public class TracingRMap<K, V> extends TracingRExpirable implements RMap<K, V> {
      private final RMap<K, V> map;
      private final TracingRedissonHelper tracingRedissonHelper;  
      
      @Override
      public RFuture<V> getAsync(K key) {
        Span span = tracingRedissonHelper.buildSpan("getAsync", map);
        span.setTag("key", nullable(key));
        return tracingRedissonHelper.prepareRFuture(span, () -> map.getAsync(key));
      }  
      
      // 其他操作
      ......
    }
    

    0x05 spring-cloud-redis

    opentracing-spring-cloud-redis-starter 实现了对 spring-cloud-redis 的Tracing功能。

    Spring Cloud 埋点实现主要实现原理是利用Spring AOP切片技术抽象埋点行为,比如TraceAsyncAspect 切面类,使用@Around 声明拦截规则,后面的逻辑与手动埋点类似,创建一个span,将业务逻辑包围起来即可。

    5.1 Bean

    首先,利用注解生成一些Bean,比如。

    @Configuration
    @AutoConfigureAfter({TracerRegisterAutoConfiguration.class, org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.class})
    @ConditionalOnBean(RedisConnectionFactory.class)
    @ConditionalOnProperty(name = "opentracing.spring.cloud.redis.enabled", havingValue = "true", matchIfMissing = true)
    @EnableConfigurationProperties(RedisTracingProperties.class)
    public class RedisAutoConfiguration {
      @Bean
      public RedisAspect openTracingRedisAspect(Tracer tracer, RedisTracingProperties properties) {
        return new RedisAspect(tracer, properties);
      }
    }
    

    5.2 拦截规则

    其次,使用 @Around 和 @Pointcut 声明拦截规则。具体是通过一层代理来是实现的拦截。对所有的 Redis connection 都通过 TracingRedisConnection 进行了一层包装

    @Aspect
    public class RedisAspect {
    
      private final Tracer tracer;
    
      private final RedisTracingProperties properties;
    
      RedisAspect(Tracer tracer, RedisTracingProperties properties) {
        this.tracer = tracer;
        this.properties = properties;
      }
    
      @Pointcut("target(org.springframework.data.redis.connection.RedisConnectionFactory)")
      public void connectionFactory() {}
    
      @Pointcut("execution(org.springframework.data.redis.connection.RedisConnection *.getConnection(..))")
      public void getConnection() {}
    
      @Pointcut("execution(org.springframework.data.redis.connection.RedisClusterConnection *.getClusterConnection(..))")
      public void getClusterConnection() {}
    
      @Around("getConnection() && connectionFactory()")
      public Object aroundGetConnection(final ProceedingJoinPoint pjp) throws Throwable {
        final RedisConnection connection = (RedisConnection) pjp.proceed();
    
        final String prefixOperationName = this.properties.getPrefixOperationName();
        final TracingConfiguration tracingConfiguration = new TracingConfiguration.Builder(tracer)
            .withSpanNameProvider(RedisSpanNameProvider.PREFIX_OPERATION_NAME(prefixOperationName))
            .build();
    
        return new TracingRedisConnection(connection, tracingConfiguration);
      }
    
      @Around("getClusterConnection() && connectionFactory()")
      public Object aroundGetClusterConnection(final ProceedingJoinPoint pjp) throws Throwable {
        final RedisClusterConnection clusterConnection = (RedisClusterConnection) pjp.proceed();
    
        final String prefixOperationName = this.properties.getPrefixOperationName();
        final TracingConfiguration tracingConfiguration = new TracingConfiguration.Builder(tracer)
            .withSpanNameProvider(RedisSpanNameProvider.PREFIX_OPERATION_NAME(prefixOperationName))
            .build();
    
        return new TracingRedisClusterConnection(clusterConnection, tracingConfiguration);
      }	
    }
    

    5.2 埋点

    在执行具体的命令前后通过自己提供的 API 进行埋点操作,基本上就是:redisTemplate的操作会在每个操作中调用connect做操作,比如 set操作中调用 connection.set(rawKey, rawValue) ,所以就通过 TracingRedisConnection来做一个封装,在做真正connection操作前后进行tracing

    流程图如下:

            redisTemplate                             TracingRedisConnection
                   +                                            +
                   |                                            |
                   |                                            |
                   v                                            |
    +--------------+-----------------+                          |
    |redisTemplate.opsForValue().set |                          |
    +--------------+-----------------+                          |
                   |                                            |
                   |                                            |
                   |                                            |
                   |                                            |
                   v                                            |
       +-----------+-----------+                                |
       | RedisTemplate.execute |                                |
       +-----------+-----------+                                |
                   |                                            |
                   |                                            |
                   v                                            v
     +-------------+-------------+        invoke              +-+---+
     | DefaultValueOperations.set|  +---------------------->  | set |
     +-------------+-------------+                            +-+---+
                   |                                            |
                   |                                            |
                   |                                            v  begin tracing
                   |                                  +---------+--------------+
                   |                                  | TracingHelper.doInScope|
                   |                                  +---------+--------------+
                   |                                            |
                   |                                            v
                   |                                        +---+-----+
                   |                                        |buildSpan|
                   |                                        +---+-----+
                   |                                            |
                   |                                            v
                   |                                  +---------+-----------+
                   |                                  |activateAndCloseSpan |
                   |                                  +---------+-----------+
                   |                                            |
                   |                                            |
                   v  Real redis action                         |
    +--------------+-------------------+   <-----------------   |
    | () -> connection.set(key, value) |                        |
    +--------------+-------------------+   +----------------->  |
                   |                                            |
                   |                                            |  end tracing
                   |         return                      +------v--------+
                   | <--------------------------------+  |span.finish(); |
                   |                                     +---------------+
                   |
                   |
                   v
    

    代码如下:

    public class TracingRedisConnection implements RedisConnection {
      private final RedisConnection connection;
      private final TracingConfiguration tracingConfiguration;
      private final TracingHelper helper;
    
      public TracingRedisConnection(RedisConnection connection,
          TracingConfiguration tracingConfiguration) {
        this.connection = connection;
        this.tracingConfiguration = tracingConfiguration;
        this.helper = new TracingHelper(tracingConfiguration);
      }
    
    // 在 span 的生命周期内执行具体命令
      @Override
      public Object execute(String command, byte[]... args) {
        // 执行命令
        return helper.doInScope(command, () -> connection.execute(command, args));
      }
      
      // 其他操作
      .....
    }
    

    具体Span是在TracingHelper中完成。

    public class TracingHelper {
        public static final String COMPONENT_NAME = "java-redis";
        public static final String DB_TYPE = "redis";
        protected final Tracer tracer;
        private final boolean traceWithActiveSpanOnly;
        private final Function<String, String> spanNameProvider;
        private final int maxKeysLength;
        private final Map<String, String> extensionTags;
    
        public <T> T doInScope(String command, Supplier<T> supplier) {
            Span span = this.buildSpan(command);
            return this.activateAndCloseSpan(span, supplier);
        }
      
        // 其他操作
      	.....
    }
    

    0xFF 参考

    分布式链路组件 SOFATracer 埋点机制解析

    蚂蚁金服开源分布式链路跟踪组件 SOFATracer 埋点机制剖析

    https://github.com/opentracing/opentracing-java

    https://github.com/opentracing-contrib/java-redis-client

    opentracing-spring-cloud-redis-starter

  • 相关阅读:
    Linux Centos7配置mysql8.0数据库
    Linux Centos7配置ftp服务器
    线程池工具ThreadPoolExecutor
    Layui 实现input 输入和选择
    canvas验证码实现
    弹性布局flex 介绍
    java EE 新手入门了解
    java web工程web.xml介绍
    js 数组常用的一些方法
    详解为什么需要重写hashcode 和 equals 方法
  • 原文地址:https://www.cnblogs.com/rossiXYZ/p/13656264.html
Copyright © 2011-2022 走看看