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