【Zipkin】zipkin-dependencies計算 - 源碼解析

zipkin-dependencies離線計算拓撲圖依賴

本文分析mysql存儲(後續準備接入Doris直接計算依賴關係,所以分析不關注存儲)

查找main方法 ZipkinDependenciesJob

case "mysql":
        MySQLDependenciesJob.builder()
          .logInitializer(logInitializer)
          .jars(jarPath)
          .day(day)
          .conf(sparkConf)
          .build()
          .run();
        break;

zipkin2.dependencies.mysql.MySQLDependenciesJob裏面包含內部類Builder,分析run方法


  public void run() {
    //數據庫信息
    Map<String, String> options = new LinkedHashMap<>();
    options.put("driver", org.mariadb.jdbc.Driver.class.getName()); // prevents shade from skipping
    options.put("url", url);
    options.put("user", user);
    options.put("password", password);
    // 如果 trace_id_high == 1,則跟蹤使用128位tranceId取代64位
    // 如果 rance_id_high == 0,用64位   "select trace_id_high from zipkin_spans limit 1"
    boolean hasTraceIdHigh = hasTraceIdHigh();
    Function<Row, Long> rowTraceId = r -> r.getLong(hasTraceIdHigh ? 1 : 0);

    long microsLower = day * 1000;
    long microsUpper = (day * 1000) + TimeUnit.DAYS.toMicros(1) - 1;

    //查詢span信息
    String fields = "s.trace_id, s.parent_id, s.id, a.a_key, a.endpoint_service_name, a.a_type";
    if (hasTraceIdHigh) fields = "s.trace_id_high, " + fields;
    String groupByFields = fields.replace("s.parent_id, ", "");
    String linksQuery = String.format(
        "select distinct %s "+
            "from zipkin_spans s left outer join zipkin_annotations a on " +
            "  (s.trace_id = a.trace_id and s.id = a.span_id " +
            "     and a.a_key in ('lc', 'ca', 'cs', 'sa', 'sr', 'ma', 'ms', 'mr', 'error')) " +
            "where s.start_ts between %s and %s group by %s",
        fields, microsLower, microsUpper, groupByFields);

    options.put("dbtable", "(" + linksQuery + ") as link_spans");

    log.info("Running Dependencies job for {}: start_ts between {} and {}", dateStamp, microsLower,
        microsUpper);

    JavaSparkContext sc = new JavaSparkContext(conf);

    List<DependencyLink> links = new SQLContext(sc).read()
        .format("org.apache.spark.sql.execution.datasources.jdbc.JdbcRelationProvider")
        .options(options)
        .load()
        // RDD爲spark抽象
        .toJavaRDD()
        // 按照最高位分組 rowTraceId是一個function<Row,Long>
        .groupBy(rowTraceId)
        // 按照RowsToDependencyLinks邏輯生成新的RDD
        //(實際上是講row數據進行整合爲DependencyLink)
        .flatMapValues(new RowsToDependencyLinks(logInitializer, hasTraceIdHigh))
        // 只取其Value拋棄Key
        .values()
        // 對DependencyLink進行map操作,參數爲lamada函數
        // 數據格式爲 [ k->(k->l.parent,v->l.child) , v->l ]
        .mapToPair(l -> Tuple2.apply(Tuple2.apply(l.parent(), l.child()), l))
        // 對map操作的數據進行reduce操作,統計生成最終的DependencyLink
        .reduceByKey((l, r) -> DependencyLink.newBuilder()
          .parent(l.parent())
          .child(l.child())
          .callCount(l.callCount() + r.callCount())
          .errorCount(l.errorCount() + r.errorCount())
          .build())
        .values().collect();

    sc.stop();

    log.info("Saving with day=" + dateStamp);
    // 存儲倒mysql中
    saveToMySQL(links);
    log.info("Done");
  }

其中組裝數據的重點在 new RowsToDependencyLinks(logInitializer, hasTraceIdHigh)) 

RowsToDependencyLinks 對應的接口實現

final class RowsToDependencyLinks
    implements Serializable, Function<Iterable<Row>, Iterable<DependencyLink>>

關注 RowsToDependencyLinks 的call方法,把row信息整合成span,把span信息整合成DependencyLink


public Iterable<DependencyLink> call(Iterable<Row> rows) {
    if (logInitializer != null) logInitializer.run();
    // ByTraceId是DependencyLinkSpanIterator 的內部類
    // ByTraceId 實現了 Iterator<Iterator<Span>>
    Iterator<Iterator<Span>> traces = new DependencyLinkSpanIterator.ByTraceId(rows.iterator(), hasTraceIdHigh);

    if (!traces.hasNext()) return Collections.emptyList();

    DependencyLinker linker = new DependencyLinker();
    List<Span> nextTrace = new ArrayList<>();
    while (traces.hasNext()) {
      // ByTraceId的next方法
      Iterator<Span> i = traces.next();
      // DependencyLinkSpanIterator的next方法,實際是對row的信息整合成span
      while (i.hasNext()) nextTrace.add(i.next()); 
      // span整合成DependencyLinker
      linker.putTrace(nextTrace);
      nextTrace.clear();
    }
    // 返回List<DependencyLink>
    return linker.link();
  }

關注點放在 Iterator<Span> i = traces.next()  進入DependencyLinkSpanIterator.next代碼

    public Iterator<Span> next() {
      // next的Row數據
      Row peeked = delegate.peek();
      // 沒太搞清楚什麼意思,應該是高位tranceId和低位tranceId
      currentTraceIdHi = hasTraceIdHigh ? peeked.getLong(0) : 0L;
      currentTraceIdLo = peeked.getLong(traceIdIndex);
      return new DependencyLinkSpanIterator(
          delegate, traceIdIndex, currentTraceIdHi, currentTraceIdLo);
    }    

另一個關注點在 while (i.hasNext()) nextTrace.add(i.next()) 進入 DependencyLinkSpanIterator.indexfor的next方法

 @Override
  public Span next() {
    Row row = delegate.peek();
    // 從row裏面去取spanId
    long spanId = row.getLong(traceIdIndex + 2);
    // 定義一些錯誤\各個類型的服務名稱
    boolean error = false;
    String lcService = null, srService = null, csService = null, caService = null, saService = null,
      maService = null, mrService = null, msService = null;
    while (hasNext()) { // there are more values for this trace
      if (spanId != delegate.peek().getLong(traceIdIndex + 2) /* id */) {
        break; // if we are in a new span
      }

      Row next = delegate.next(); // row for the same span
      // 獲取key
      String key = emptyToNull(row, traceIdIndex + 3); // a_key
      // 獲取服務名稱
      String value = emptyToNull(row, traceIdIndex + 4); // a_service_name
      if (key == null || value == null) continue; // neither client nor server
      switch (key) {
        case "lc":
          lcService = value;
          break;
        case "ca":
          caService = value;
          break;
        case "cs":
          csService = value;
          break;
        case "sa":
          saService = value;
          break;
        case "ma":
          maService = value;
          break;
        case "mr":
          mrService = value;
          break;
        case "ms":
          msService = value;
          break;
        case "sr":
          srService = value;
          break;
        case "error":
          // 如果span有一個名爲“error”的標記,而不是註釋,則該span是錯誤的
          // a span is in error if it has a tag, not an annotation, of name "error"
          error = BINARY_ANNOTATION_TYPE_STRING == next.getInt(traceIdIndex + 5); // a_type
      }
    }

    // 客戶端地址比客戶端發送所有者更權威
    // The client address is more authoritative than the client send owner.
    if (caService == null) caService = csService;

    // Finagle標籤兩邊(“ca”、“sa”) 相同畫,則跳過客戶端這樣就不會被誤認爲是環回請求
    // Finagle labels two sides of the same socket ("ca", "sa") with the same name.
    // Skip the client side, so it isn't mistaken for a loopback request
    if (saService != null && saService.equals(caService)) caService = null;

    //parent ID 爲 traceIdIndex + 1
    long parentId = row.isNullAt(traceIdIndex + 1) ? 0L : row.getLong(traceIdIndex + 1);
    Span.Builder result =
        //組裝Span
        Span.newBuilder().traceId(traceIdHi, traceIdLo).parentId(parentId).id(spanId);

    if (error) {
      //實際值沒意義
      result.putTag("error", "" /* actual value doesn't matter */);
    }
    // 返回構建結果
    if (srService != null) {
      return result
          .kind(Span.Kind.SERVER)
          .localEndpoint(ep(srService))
          .remoteEndpoint(ep(caService))
          .build();
    } else if (saService != null) {
      Endpoint localEndpoint = ep(caService);
      // When span.kind is missing, the local endpoint is "lc" and the remote endpoint is "sa"
      if (localEndpoint == null) localEndpoint = ep(lcService);
      return result
          .kind(csService != null ? Span.Kind.CLIENT : null)
          .localEndpoint(localEndpoint)
          .remoteEndpoint(ep(saService))
          .build();
    } else if (csService != null) {
      return result.kind(Span.Kind.SERVER).localEndpoint(ep(caService)).build();
    } else if (mrService != null) {
      return result
        .kind(Span.Kind.CONSUMER)
        .localEndpoint(ep(mrService))
        .remoteEndpoint(ep(maService))
        .build();
    } else if (msService != null) {
      return result
        .kind(Span.Kind.PRODUCER)
        .localEndpoint(ep(msService))
        .remoteEndpoint(ep(maService))
        .build();
    }
    return result.build();
  }
另一個關注點在 linker.putTrace(nextTrace)  這個方法對Span進行整合成DependencyLinker
  public DependencyLinker putTrace(List<Span> spans) {
    if (spans.isEmpty()) return this;
    // 通過List<Span> spans 構建一棵樹
    // span信息,spanNode parent,List<SpanNode> child
    SpanNode traceTree = builder.build(spans);
    
    if (logger.isLoggable(FINE)) logger.fine("traversing trace tree, breadth-first");
    // traceTree.traverse() 進行廣度優先遍歷
    for (Iterator<SpanNode> i = traceTree.traverse(); i.hasNext(); ) {
      SpanNode current = i.next();
      Span currentSpan = current.span();
      if (logger.isLoggable(FINE)) {
        logger.fine("processing " + currentSpan);
      }

      Kind kind = currentSpan.kind();
      // When processing links to a client span, we prefer the server's name. If we have no child
      // spans, we proceed to use the name the client chose.
      if (Kind.CLIENT.equals(kind) && !current.children().isEmpty()) {
        continue;
      }

      String serviceName = currentSpan.localServiceName();
      String remoteServiceName = currentSpan.remoteServiceName();
      if (kind == null) {
        // Treat unknown type of span as a client span if we know both sides
        if (serviceName != null && remoteServiceName != null) {
          kind = Kind.CLIENT;
        } else {
          logger.fine("non remote span; skipping");
          continue;
        }
      }

      String child;
      String parent;
      switch (kind) {
        case SERVER:
        case CONSUMER:
          child = serviceName;
          parent = remoteServiceName;
          if (current == traceTree) { // we are the root-most span.
            if (parent == null) {
              logger.fine("root's client is unknown; skipping");
              continue;
            }
          }
          break;
        case CLIENT:
        case PRODUCER:
          parent = serviceName;
          child = remoteServiceName;
          break;
        default:
          logger.fine("unknown kind; skipping");
          continue;
      }

      boolean isError = currentSpan.tags().containsKey("error");
      if (kind == Kind.PRODUCER || kind == Kind.CONSUMER) {
        if (parent == null || child == null) {
          logger.fine("cannot link messaging span to its broker; skipping");
        } else {
          addLink(parent, child, isError);
        }
        continue;
      }

      // Local spans may be between the current node and its remote parent
      Span remoteAncestor = firstRemoteAncestor(current);
      String remoteAncestorName;
      if (remoteAncestor != null
        && (remoteAncestorName = remoteAncestor.localServiceName()) != null) {
        // Some users accidentally put the remote service name on client annotations.
        // Check for this and backfill a link from the nearest remote to that service as necessary.
        if (kind == Kind.CLIENT && serviceName != null && !remoteAncestorName.equals(serviceName)) {
          logger.fine("detected missing link to client span");
          addLink(remoteAncestorName, serviceName, false); // we don't know if there's an error here
        }

        if (kind == Kind.SERVER || parent == null) parent = remoteAncestorName;

        // When an RPC is split between spans, we skip the child (server side). If our parent is a
        // client, we need to check it for errors.
        if (!isError && Kind.CLIENT.equals(remoteAncestor.kind()) &&
          currentSpan.parentId() != null && currentSpan.parentId().equals(remoteAncestor.id())) {
          isError = remoteAncestor.tags().containsKey("error");
        }
      }

      if (parent == null || child == null) {
        logger.fine("cannot find remote ancestor; skipping");
        continue;
      }

      addLink(parent, child, isError);
    }
    return this;
  }

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章