现在越来越多的应用正在向基于微服务的云原生架构迁移。微服务架构非常强大,但也带来了很多挑战,尤其是如何调试应用,如何监控多个服务之间的调用关系和状态。如何对微服务架构进行有效监控成为微服务架构运维成功的关键。用软件架构的语言来说,就是增强微服务架构的可观察性(Observability)。微服务的监控主要包括以下三个方面:采集日志,监控系统和各个服务的运行状态采集指标,监控系统和各个服务的性能通过分布式追踪,追踪服务请求是如何处理的细节在每个分布式组件中都会更加熟悉日志和指标的收集和监控。常见的日志采集架构包括使用Fluentd采集系统日志,然后使用ELK或Splunk进行日志分析。对于性能监控,Prometheus是常见且流行的选择。越来越多的应用程序正在采用分布式跟踪。分布式追踪可以通过追踪微服务调用链,构建从服务请求到各个微服务交互的整个调用过程的视图。用户可以了解到应用调用的延迟、网络调用(HTTP、RPC)的生命周期、系统的性能瓶颈等信息。那么分布式追踪是如何实现的呢?1.分布式追踪的概念谷歌在2010年4月发表了一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》(http://1t.click/6EB),介绍了分布式追踪的概念。对于分布式追踪,主要有以下几个概念:TrackingTrace:是分布式微服务协同支持的事务。包含为该事务提供服务的单个服务请求的跟踪。SpanSpan:Span是事务中的一个工作流,一个Span包含时间戳、日志和标签信息。Span包含父子关系,或者说主从(Followup)关系。SpanContextSpanContext:span上下文是支持分布式追踪的关键,它可以在被调用的服务之间传递,上下文的内容包括如:从??一个服务传递到另一个服务的时间,trace的ID,以及Span的ID还有其他信息需要从上游服务传递给下游服务。2.OpenTracing标准概念基于Google提出的OpenTracing(http://1t.click/6tC)概念定义了一个开放的分布式跟踪标准。Span是分布式跟踪的基本单元,代表分布式系统中的单个工作单元。每个Span都可以包含对其他Span的引用。多个Span一起形成一个Trace。OpenTracing的规范定义了每个Span包含以下内容:操作名称(OperationName),表示操作是什么(Tag),tag是一个名值对。用户可以添加任何有意义的信息日志(Logs),日志也被定义为名称-值对。用于捕获调试信息,或者Span上下文(SpanContext)的相关信息,SpanContext负责在子微服务系统的边界传递数据。主要包括两部分:与实现无关的状态信息,如TraceID、SpanID、BaggageItem。如果把微服务调用比作从一个城市飞到另一个城市,那么SpanContext可以看作是飞机承载的内容。TraceID和SpanID就像航班号,BaggageItems就像托运的行李。对于每次服务呼叫,用户可以决定发送不同的包。下面是一个Span的例子:t=0operationname:db_queryt=x+-------------------------------------------------+|·······················||+---------------------------------------------------+标签:-db.instance:"jdbc:mysql://127.0.0.1:3306/customers-db.statement:"SELECT*FROMmytableWHEREfoo='bar';"Logs:-message:"Can'tconnecttomysqlserveron'127.0.0.1'(10061)"SpanContext:-trace_id:"abc123"-span_id:"xyz789"-BaggageItems:-special_id:"vsid1738"要实现分布式跟踪,如何传递SpanContext是关键。OpenTracing为SpanContext定义了两个方法Inject和Extract注入和提取。注入伪代码span_context=...outbound_request=...#We'llusethe(builtin)HTTP_HEADERScarrierformat.We#startbyusinganemptymapasthecarrierpriortothe#callto`tracer.inject`.carrier={}tracer.inject(span_context,opentracing.Format.HTTP_HEADERS,carrier)#`carrier`nowcontains(opaque)key:valuepairswhichwepass#alongoverwhateverwireprotocolwealreadyuse.forkey,valueincarrier:outbound_request.headers[key]=escape(value)这里的注入过程是将context的所有信息写入一个carrier,叫做Carrierdictionary,然后将字典中所有的名值对写入HTTPHeader。提取代码inbound_request=...#We'llagainusethe(builtin)HTTP_HEADERScarrierformat.Perthe#HTTP_HEADERSdocumentation,wecanuseamapthathasextraneousdata#initandlettheOpenTracingimplementationlookforthesubset#ofkey:valuepairsitneeds.##Assuch,wedirectlyusethekey:value`inbound_request`carrierpan.headerspan.headersExtract过程是注入的逆过程,从载体即HTTPHeaders构造SpanContext。整个过程类似于客户端和服务器之间传递的数据序列化和反序列化的过程。这里的Carrier字典支持Key为字符串类型,value为字符串或二进制格式(字节)。3.如何使用?好了,讲了很多概念。作为程序员,你已经不耐烦了。不说你有什么没有,赶紧码字。别着急,让我们来看看如何使用Tracing。我们使用一个Python应用程序来打印程序员喜欢的“helloworld”来说明OpenTracing的工作原理。客户端代码importrequestsimportsysimporttimefromlib.tracingimportinit_tracerfromopentracing.extimporttagsfromopentracing.propagationimportFormatdefsay_hello(hello_to):withtracer.start_active_span('say-hello')asscope:scope.span.set_tag('hello-to',hello_to)hello_str=format_string(hello_str=format_string)hello_str)defformat_string(hello_to):withtracer.start_active_span('format')asscope:hello_str=http_get(8081,'format','helloTo',hello_to)scope.span.log_kv({'event':'string-format','value':hello_str})returnhello_strdefprint_hello(hello_str):withtracer.start_active_span('println')asscope:http_get(8082,'publish','helloStr',hello_str)scope.span.log_kv({'event':'println'})defhttp_get(port,path,param,value):url='http://localhost:%s/%s'%(port,path)span=tracer.active_spanspan.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)assertr.status_code==200returnr.text#mainassertlen(sys.argv)==2tracer=init_tracer('hello-world')hello_to=sys.argv[1]say_hello(hello_to)#yieldtoIOLooptoflushthespanstime.sleep(2)tracer.close()客户端完成以下工作:初始化Tracer,trace名称为'hello-world'创建一个客户端操作say_hello,关联一个名为'say-hello'的Span,调用span.set_tag添加tag。在操作say_hello中,首先调用HTTP服务A,format_string,操作关联另一个名为'format'的Span,并调用span.log_kv加入日志,然后调用另一个HTTP服务B,print_hello,这个操作是与另一个名为'println'的Span关联,并调用span.log_kv加入日志对于每个HTTP请求,为Span添加标签,标记http方法,httpurl和span种类并调用tracer.inject将SpanContext注入http头.服务代码fromflaskiimportFlaskfromflaskimportrequestfromlib.tracingimportinit_tracerfromopentracing.extimporttagsfromopentracing.propagationimportFormatapp=Flask(__name__)tracer=init_tracer('formatter')@app.route("/format")defformat():quespan_ADERctx=matForer.HTTPreact.headers)span_tags={tags.SPAN_KIND:tags.SPAN_KIND_RPC_SERVER}withtracer.start_active_span('format',child_of=span_ctx,tags=span_tags):hello_to=request.args.get('helloTo')return'你好,%s!'%hello_toif__name__==”__main__”:app.run(port=8081)ServiceA响应格式请求,调用tracer.extract从http头中提取信息,构建spanContext。服务B代码fromflaskiimportFlaskfromflaskimportrequestfromlib.tracingimportinit_tracerfromopentracing.extimporttagsfromopentracing.propagationimportFormatapp=Flask(__name__)tracer=init_tracer('publisher')@app.route("/publish")defpublish():que_ctx(ADhextr=tracer.)span_tags={tags.SPAN_KIND:tags.SPAN_KIND_RPC_SERVER}withtracer.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上(下图为JaegerUI),可以看到类似下图的追踪信息。我们可以看到服务hello-word和三个操作say-hello/format/println的详细跟踪信息。目前很多分布式追踪软件都提供对OpenTracing的支持,包括:Jaeger、LightStep、Instanna、ApacheSkyWalking、inspectIT、stagemonitor、Datadog、Wavefront、ElasticAPM等。其中Zipkin(http://1t.click/6Ec)和Jaeger(http://1t.click/6DY)是最受欢迎的开源软件。ZipkinZipkin(http://1t.click/6Ec)是Twitter基于Dapper开发的分布式追踪系统。其设计架构如下图所示:蓝色实体为Zipkin要追踪的目标组件,Non-IntrumentedServer代表不直接调用TracingAPI的微服务。通过IntrumentedClient从Non-IntrumentedServer收集信息,并发送给Zipkin的Collector。IntrumentedServer直接调用TracingAPI,将数据发送给Zipkin的收集器。Transport是一种传输通道,可以通过HTTP或通过消息/事件队列直接发送到Zipkin。Zipkin本身是一个Java应用,包括:Collector负责数据收集,对外提供数据接口;贮存;API和用户界面。Zipkin的用户界面如下所示:Zipkin官方支持以下语言的客户端:C#、Go、Java、JavaScript、Ruby、Scala、PHP。开源社区也支持其他语言。Zipkin已经发展了快4年了,是一个比较成熟的项目。JaegerJaeger(http://1t.click/6DY)最早是由Uber作为分布式跟踪系统开发的,也是基于Dapper的设计理念。现在Jaeger是CNCF(CloudNativeComputingFoundation)的一个项目。如果你对CNCF组织有所了解,那么你可以推断这个项目应该与Kubernetes有非常紧密的集成。Jaeger基于分布式架构设计,主要包含以下组件:JaegerClient负责收集客户端的trace信息。JaegerAgent,负责与客户端通信,并将收集到的跟踪信息报告给收集器JaegerCollectorJaegerCollector将收集到的数据存储在数据库或其他存储中。JaegerQuery负责查询跟踪数据。JaegerUI负责用户交互。这个架构很像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.总结微服务大行其道,云原生成为架构设计主流的今天,微服务系统监控,包括日志记录、指标和跟踪已成为系统工程的重中之重。基于Dapper的分布式追踪设计理念,OpenTracing定义了分布式追踪的实现标准。在开源项目中,Zipkin和Jaeger是比较好的选择。尤其是Jaeger,由于与云原生框架的良好结合,是构建微服务追踪系统的必备工具。
