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;
}