转载本文需注明出处:微信公众年夜众号EAWorld,违者必究。
现在越来越多的运用迁移到基于微做事的云原生的架构之上,微做事架构很强大,但是同时也带来了很多的寻衅,尤其是如何对运用进行调试,如何监控多个做事间的调用关系和状态。如何有效的对微做事架构进行有效的监控成为微做事架构运维成功的关键。用软件架构的措辞来说便是要增强微做事架构的可不雅观测性(Observability)。
微做事的监控紧张包含一下三个方面:

对付这天记和量度的网络和监控,大家会比较熟习。常见的日志网络架构包含利用Fluentd对系统日志进行网络,然后利用ELK或者Splunk进行日志剖析。而对付性能监控,Prometheus是常见的盛行的选择。
分布式追踪正在被越来越多的运用所采取。分布式追踪可以通过对微做事调用链的跟踪,构建一个从做事要求开始到各个微做事交互的全部调用过程的视图。用户可以从中理解到诸如运用调用的时延,网络调用(HTTP,RPC)的生命周期,系统的性能瓶颈等等信息。那么分布式追踪是如何实现的呢?
1.分布式追踪的观点谷歌在2010年4月揭橥了一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》(http://1t.click/6EB),先容了分布式追踪的观点。
对付分布式追踪,紧张有以下的几个观点:
追踪 Trace:便是由分布的微做事协作所支撑的一个事务。一个追踪,包含为该事务供应做事的各个做事要求。跨度 Span:Span是事务中的一个事情流,一个Span包含了韶光戳,日志和标签信息。Span之间包含父子关系,或者主从(Followup)关系。跨度高下文 Span Context:跨度高下文是支撑分布式追踪的关键,它可以在调用的做事之间通报,高下文的内容包括诸如:从一个做事通报到另一个做事的韶光,追踪的ID,Span的ID还有其它须要从上游做事通报到下贱做事的信息。2.OpenTracing 标准观点基于谷歌提出的观点OpenTracing(http://1t.click/6tC)定义了一个开放的分布式追踪的标准。
Span是分布式追踪的基本组成单元,表示一个分布式系统中的单独的事情单元。每一个Span可以包含其它Span的引用。多个Span在一起构成了Trace。
OpenTracing的规范定义每一个Span都包含了以下内容:
操作名(Operation Name),标志该操作是什么标签 (Tag),标签是一个名值对,用户可以加入任何对追踪故意义的信息日志(Logs),日志也定义为名值对。用于捕获调试信息,或者干系Span的干系信息跨度高下文呢 (SpanContext),SpanContext卖力子微做事系统边界通报数据。它紧张包含两部分:和实现无关的状态信息,例如Trace ID,Span ID行李项 (Baggage Item)。如果把微做事调用比做从一个城市到另一个城市的翱翔, 那么SpanContext就可以算作是飞机运载的内容。Trace ID和Span ID就像是航班号,而行李项就像是运送的行李。每次做事调用,用户都可以决定发送不同的行李。这里是一个Span的例子:
t=0 operation name: db_query t=x +-----------------------------------------------------+ | · · · · · · · · · · Span · · · · · · · · · · | +-----------------------------------------------------+Tags:- db.instance:\公众jdbc:mysql://127.0.0.1:3306/customers- db.statement: \"大众SELECT FROM mytable WHERE foo='bar';\"大众Logs:- message:\"大众Can't connect to mysql server on '127.0.0.1'(10061)\"大众SpanContext:- trace_id:\公众abc123\"大众- span_id:\"大众xyz789\公众- Baggage Items: - special_id:\"大众vsid1738\"大众
要实现分布式追踪,如何通报SpanContext是关键。OpenTracing定义了两个方法Inject和Extract用于SpanContext的注入和提取。
Inject 伪代码
span_context = ...outbound_request = ...# We'll use the (builtin) HTTP_HEADERS carrier format. We# start by using an empty map as the carrier prior to the# call to `tracer.inject`.carrier = {}tracer.inject(span_context, opentracing.Format.HTTP_HEADERS, carrier)# `carrier` now contains (opaque) key:value pairs which we pass# along over whatever wire protocol we already use.for key, value in carrier: outbound_request.headers[key] = escape(value)
这里的注入的过程便是把context的所有信息写入到一个叫Carrier的字典中,然后把字典中的所有名值对写入 HTTP Header。
Extract 伪代码
inbound_request = ...# We'll again use the (builtin) HTTP_HEADERS carrier format. Per the# HTTP_HEADERS documentation, we can use a map that has extraneous data# in it and let the OpenTracing implementation look for the subset# of key:value pairs it needs.## As such, we directly use the key:value `inbound_request.headers`# map as the carrier.carrier = inbound_request.headersspan_context = tracer.extract(opentracing.Format.HTTP_HEADERS, carrier)# Continue the trace given span_context. E.g.,span = tracer.start_span(\"大众...\"大众, child_of=span_context)# (If `carrier` held trace data, `span` will now be ready to use.)
抽取过程是注入的逆过程,从carrier,也便是HTTP Headers,构建SpanContext。
全体过程类似客户端和做事器通报数据的序列化和反序列化的过程。这里的Carrier字典支持Key为string类型,value为string或者Binary格式(Bytes)。
3.怎么用能?好了讲了一大堆的观点,作为程序猿的你早已经不耐烦了,不要讲那些有的没的,快上代码。不急我们这就看看详细如何利用Tracing。
我们用一个程序猿喜闻乐见的打印‘hello world’的Python运用来解释OpenTracing是如何事情的。
客户端代码
import requestsimport sysimport timefrom lib.tracing import init_tracerfrom opentracing.ext import tagsfrom opentracing.propagation import Formatdef say_hello(hello_to): with tracer.start_active_span('say-hello') as scope: scope.span.set_tag('hello-to', hello_to) hello_str = format_string(hello_to) print_hello(hello_str)def format_string(hello_to): with tracer.start_active_span('format') as scope: hello_str = http_get(8081, 'format', 'helloTo', hello_to) scope.span.log_kv({'event': 'string-format', 'value': hello_str}) return hello_strdef print_hello(hello_str): with tracer.start_active_span('println') as scope: http_get(8082, 'publish', 'helloStr', hello_str) scope.span.log_kv({'event': 'println'})def http_get(port, path, param, value): url = 'http://localhost:%s/%s' % (port, path) span = tracer.active_span span.set_tag(tags.HTTP_METHOD, 'GET') span.set_tag(tags.HTTP_URL, url) span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT) headers = {} tracer.inject(span, Format.HTTP_HEADERS, headers) r = requests.get(url, params={param: value}, headers=headers) assert r.status_code == 200 return r.text# mainassert len(sys.argv) == 2tracer = init_tracer('hello-world')hello_to = sys.argv[1]say_hello(hello_to)# yield to IOLoop to flush the spanstime.sleep(2)tracer.close()
客户端完成了以下的事情:
初始化Tracer,trace的名字是‘hello-world’创建以个客户端操作say_hello,该操作关联一个Span,取名‘say-hello’,并调用span.set_tag加入标签在操作say_hello中调用第一个HTTP 做事A,format_string, 该操作关联另一个Span取名‘format’,并调用span.log_kv加入日志之后调用另一个HTTP 做事B,print_hello, 该操作关联另一个Span取名‘println’,并调用span.log_kv加入日志对付每一个HTTP要求,在Span中都加入标签,标志http method,http url和span kind。并调用tracer.inject把SpanContext注入到http header 中。做事A代码
from flask import Flaskfrom flask import requestfrom lib.tracing import init_tracerfrom opentracing.ext import tagsfrom opentracing.propagation import Formatapp = Flask(__name__)tracer = init_tracer('formatter') @app.route(\公众/format\"大众)def format(): span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers) span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER} with tracer.start_active_span('format', child_of=span_ctx, tags=span_tags): hello_to = request.args.get('helloTo') return 'Hello, %s!' % hello_toif __name__ == \公众__main__\"大众: app.run(port=8081)
做事A相应format要求,调用tracer.extract从http headers中提取信息,构建spanContext。
做事B代码
from flask import Flaskfrom flask import requestfrom lib.tracing import init_tracerfrom opentracing.ext import tagsfrom opentracing.propagation import Formatapp = Flask(__name__)tracer = init_tracer('publisher')@app.route(\"大众/publish\"大众)def publish(): span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers) span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER} with tracer.start_active_span('publish', child_of=span_ctx, tags=span_tags): hello_str = request.args.get('helloStr') print(hello_str) return 'published'if __name__ == \"大众__main__\"大众: app.run(port=8082)
做事B和A类似。
之后在支持分布式追踪的软件UI上(下图是Jaeger UI),就可以看到类似下图的追踪信息。我们可以看到做事hello-word和三个操作say-hello/format/println的详细追踪信息。
当前有很多分布式追踪软件都供应了OpenTracing的支持,包括:Jaeger,LightStep,Instanna,Apache SkyWalking,inspectIT,stagemonitor,Datadog,Wavefront,Elastic APM等等。个中作为开源软件的Zipkin(http://1t.click/6Ec)和Jaeger(http://1t.click/6DY)最为盛行。
Zipkin
Zipkin(http://1t.click/6Ec)是Twitter基于Dapper开拓的分布式追踪系统。它的设计架构如下图:
蓝色实体是Zipkin要追踪的目标组件,Non-Intrumented Server表示不直接调用Tracing API的微做事。通过Intrumented Client从Non-Intrumented Server中网络信息并发送给Zipkin的网络器Collector。Intrumented Server 直接调用Tracing API,发送数据到Zipkin的网络器。Transport是传输通道,可以通过HTTP直接发送到Zipkin或者通过/事宜行列步队的办法。Zipkin本身是一个Java运用,包含了:网络器Collector卖力数据采集,对外供应数据接口;存储;API和UI。
Zipkin的用户界面像这个样子:
Zipkin官方支持以下几种措辞的客户端:C#,Go,Java,JavaScript,Ruby,Scala,PHP。开源社区也有其它措辞的支持。
Zipkin发展到现在有快4年的韶光,是一个相对成熟的项目。
Jaeger
Jaeger(http://1t.click/6DY)最早是由Uber开拓的分布式追踪系统,同样基于Dapper的设计理念。现在Jaeger是CNCF(Cloud Native Computing Foundation)的一个项目。如果你对CNCF这个组织有所理解,那么你可以推测出这个项目该当和Kubernetes有非常紧密的集成。
Jaeger基于分布式的架构设计,紧张包含以下几个组件:
Jaeger Client,卖力在客户端网络跟踪信息。Jaeger Agent,卖力和客户端通信,把网络到的追踪信息上报个网络器 Jaeger CollectorJaeger Colletor把网络到的数据存入数据库或者其它存储器Jaeger Query 卖力对追踪数据进行查询Jaeger UI卖力用户交互这个架构很像ELK,Collector之前类似Logstash卖力采集数据,Query类似Elastic卖力搜索,而UI类似Kibana卖力用户界面和交互。这样的分布式架构使得Jaeger的扩展性更好,可以根据须要,构建不同的支配。
Jaeger作为分布式追踪的后起之秀,随着云原生和K8s的广泛采取,正变得越来越盛行。利用官方给出的K8s支配模版(http://1t.click/6DU),用户可以快速的在自己的k8s集群上支配Jaeger。
4.分布式跟踪系统——产品比拟
当然除了支持OpenTracing标准的产品之外,还有其它的一些分布式追踪产品。这里引用一些其它博主的剖析,给大家一些参考:
调用链选型之Zipkin,Pinpoint,SkyWalking,CAT(http://1t.click/6tY)分布式调用链调研(pinpoint,skywalking,jaeger,zipkin等比拟)(http://1t.click/6DK)分布式跟踪系统——产品比拟(http://1t.click/6ug)5.总结
在微做事大行其道,云原天生为架构设计的主流的情形下,微做事系统监控,包含日志,指标和追踪成为了系统工程的重中之重。OpenTracing基于Dapper的分布式追踪设计理念,定义了分布式追踪的实现标准。在开源项目中,Zipkin和Jaeger是相对精良的选择。尤其是Jaeger,由于对云原生框架的良好集成,是构建微做事追踪系统的必备良器。
参考资料
http://1t.click/6tC
http://1t.click/6t7
http://1t.click/6tD
http://1t.click/6tK
http://1t.click/6tP
http://1t.click/6tS
https://dwz.cn/vBqhTHL1
关于作者:陶刚,Splunk资深软件工程师,架构师,毕业于北京邮电大学,现在在温哥华卖力Splunk机器学习云平台的开拓,曾经就职于SAP,EMC,Lucent等企业,拥有丰富的企业运用软件开拓履历,熟习软件开拓的各种技能,平台和开拓过程,在商务智能,机器学习,数据可视化,数据采集,网络管理等领域都有涉及。
关于EAWorld:微做事,DevOps,数据管理,移动架构原创技能分享。