zipkin 的Span有兩個版本V1及V2,但是最終再代碼的運轉亦或是ES中存儲的所體現的其實都是V2的Span,下面我們來分析分析這兩個Span有什麼異同。
V1的Span 應該是我們所熟悉的,它就是來源與谷歌的那篇論文,擁有CR,CS,SR,SS等Annotation,同時還擁有BinaryAnnotation。我們來看看其主要成員。
public final long traceIdHigh;
public final long traceId;
public final String name;
@Nullable
public final Long parentId;
@Nullable
public final Long timestamp;
@Nullable
public final Long duration;
public final List<Annotation> annotations;
public final List<BinaryAnnotation> binaryAnnotations;
@Nullable
public final Boolean debug;
來看一個存儲的Span
兩個SPAN
Trace e8d779e43243cd5b
[
{
"traceId": "e8d779e43243cd5b",
"id": "e8d779e43243cd5b",
"name": "ttt",
"timestamp": 1553414640395000,
"duration": 96581,
"binaryAnnotations": [
{
"key": "chane",
"value": "1234",
"endpoint": {
"serviceName": "shopservicename",
"ipv4": "192.168.88.103"
}
},
{
"key": "lc",
"value": "tttttt",
"endpoint": {
"serviceName": "shopservicename",
"ipv4": "192.168.88.103"
}
}
]
},
{
"traceId": "e8d779e43243cd5b",
"id": "ac75f268aab05339",
"name": "queryflowprcpln",
"parentId": "e8d779e43243cd5b",
"timestamp": 1553414640399000,
"duration": 94000,
"annotations": [
{
"timestamp": 1553414640399000,
"value": "cs",
"endpoint": {
"serviceName": "shopservicename",
"ipv4": "192.168.88.103"
}
},
{
"timestamp": 1553414640493000,
"value": "cr",
"endpoint": {
"serviceName": "shopservicename",
"ipv4": "192.168.88.103"
}
}
]
}
]
我們再來看看V2 的Span
// Custom impl to reduce GC churn and Kryo which cannot handle AutoValue subclass
// See https://github.com/openzipkin/zipkin/issues/1879
final String traceId, parentId, id;
final Kind kind;
final String name;
final long timestamp, duration; // zero means null, saving 2 object references
final Endpoint localEndpoint, remoteEndpoint;
final List<Annotation> annotations;
final Map<String, String> tags;
final int flags; // bit field for timestamp and duration, saving 2 object references
實際存儲
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 20,
"successful": 20,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0,
"hits": [
{
"_index": "zipkin:span-2019-03-24",
"_type": "span",
"_id": "xHu6rmkB4ZW4b3UBVS-t",
"_score": 0,
"_source": {
"traceId": "e8d779e43243cd5b",
"duration": 94000,
"localEndpoint": {
"serviceName": "shopservicename",
"ipv4": "192.168.88.103"
},
"timestamp_millis": 1553414640399,
"kind": "CLIENT",
"name": "queryflowprcpln",
"id": "ac75f268aab05339",
"parentId": "e8d779e43243cd5b",
"timestamp": 1553414640399000
}
},
{
"_index": "zipkin:span-2019-03-24",
"_type": "span",
"_id": "xXu6rmkB4ZW4b3UBVS-t",
"_score": 0,
"_source": {
"traceId": "e8d779e43243cd5b",
"duration": 96581,
"localEndpoint": {
"serviceName": "shopservicename",
"ipv4": "192.168.88.103"
},
"timestamp_millis": 1553414640395,
"name": "ttt",
"id": "e8d779e43243cd5b",
"timestamp": 1553414640395000,
"tags": {
"chane": "1234",
"lc": "tttttt"
}
}
}
]
}
}
兩者有比較大的不一樣。當zipkin Server 接收到V1 時會有一個轉換器將其轉換成V2。那這兩者是怎麼轉換的呢。
CR 及 CS 的Annoation 會轉換成Kind 爲Client。其次CR 及CS 下的EndPoint 會被轉換爲localEndpoint。
至於我們經常用來存儲key-value 的BinaryAnnoation 也會被轉換成tags中的一條Entry<String,String>。
zipkin 採用的ES來進行存儲,我們來看看其在ES中的索引是怎麼定義的。
{
"zipkin:span-2019-03-24": {
"aliases": {},
"mappings": {
"span": {
"_source": {
"excludes": [
"_q"
]
},
"dynamic_templates": [
{
"strings": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"ignore_above": 256,
"norms": false,
"type": "keyword"
}
}
}
],
"properties": {
"_q": {
"type": "keyword"
},
"annotations": {
"type": "object",
"enabled": false
},
"duration": {
"type": "long"
},
"id": {
"type": "keyword",
"ignore_above": 256
},
"kind": {
"type": "keyword",
"ignore_above": 256
},
"localEndpoint": {
"dynamic": "false",
"properties": {
"serviceName": {
"type": "keyword"
}
}
},
"name": {
"type": "keyword"
},
"parentId": {
"type": "keyword",
"ignore_above": 256
},
"remoteEndpoint": {
"dynamic": "false",
"properties": {
"serviceName": {
"type": "keyword"
}
}
},
"tags": {
"type": "object",
"enabled": false
},
"timestamp": {
"type": "long"
},
"timestamp_millis": {
"type": "date",
"format": "epoch_millis"
},
"traceId": {
"type": "keyword"
}
}
},
"_default_": {
"dynamic_templates": [
{
"strings": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"ignore_above": 256,
"norms": false,
"type": "keyword"
}
}
}
]
}
},
"settings": {
"index": {
"number_of_shards": "5",
"provided_name": "zipkin:span-2019-03-24",
"mapper": {
"dynamic": "false"
},
"creation_date": "1553400823203",
"requests": {
"cache": {
"enable": "true"
}
},
"analysis": {
"filter": {
"traceId_filter": {
"type": "pattern_capture",
"preserve_original": "true",
"patterns": [
"([0-9a-f]{1,16})$"
]
}
},
"analyzer": {
"traceId_analyzer": {
"filter": "traceId_filter",
"type": "custom",
"tokenizer": "keyword"
}
}
},
"number_of_replicas": "1",
"uuid": "eRkq_bCyTuuPByMREL4M_w",
"version": {
"created": "6020399"
}
}
}
}
}
有一個需要主要的是zipkin 會將annoation 及tags。一起寫如es的一個字段“_q”,方便查詢
* <p>Ex {@code curl -s localhost:9200/zipkin:span-2017-08-11/_search?q=_q:error=500}
*/
static byte[] prefixWithTimestampMillisAndQuery(Span span, long timestampMillis) {
Buffer query = new Buffer();
JsonWriter writer = JsonWriter.of(query);
try {
writer.beginObject();
if (timestampMillis != 0L) writer.name("timestamp_millis").value(timestampMillis);
if (!span.tags().isEmpty() || !span.annotations().isEmpty()) {
writer.name("_q");
writer.beginArray();
for (Annotation a : span.annotations()) {
if (a.value().length() > 255) continue;
writer.value(a.value());
}
for (Map.Entry<String, String> tag : span.tags().entrySet()) {
if (tag.getKey().length() + tag.getValue().length() + 1 > 255) continue;
writer.value(tag.getKey()); // search is possible by key alone
writer.value(tag.getKey() + "=" + tag.getValue());
}
writer.endArray();
}
writer.endObject();
} catch (IOException e) {
// very unexpected to have an IOE for an in-memory write
assert false : "Error indexing query for span: " + span;
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Error indexing query for span: " + span, e);
}
return SpanBytesEncoder.JSON_V2.encode(span);
}
byte[] document = SpanBytesEncoder.JSON_V2.encode(span);
if (query.rangeEquals(0L, ByteString.of(new byte[] {'{', '}'}))) {
return document;
}
byte[] prefix = query.readByteArray();
byte[] newSpanBytes = new byte[prefix.length + document.length - 1];
int pos = 0;
System.arraycopy(prefix, 0, newSpanBytes, pos, prefix.length);
pos += prefix.length;
newSpanBytes[pos - 1] = ',';
// starting at position 1 discards the old head of '{'
System.arraycopy(document, 1, newSpanBytes, pos, document.length - 1);
return newSpanBytes;
}
由於tags。是不可搜索的,所以針對tags.我們可以利用_q進行搜索。針對文中的tag。可以這麼搜索
"term": {
"_q": "chane"
}
"term": {
"_q": "chane=1234"
}