![1014abcfbd42e476da2f5caf9f7975ec.png](https://i-blog.csdnimg.cn/blog_migrate/69aba1b3361cf9fa0b231d06150b4eac.jpeg)
Zipkin 是基于 Java 的分布式追踪系统,用于收集和查找来自分布式系统的数据。伴随着 HTTP 请求的发出,有太多事情可能发生了:它们包括对数据库引擎,缓存服务器或任何其他微服务组件(如另一个微服务)的调用。每当此时, Zipkin 就能派上用场了。针对这种微服务拓扑结构非常复杂的情况,您在进行故障排除时就可以使用 Zipkin 进行可视化等诸多功能。
在本文中,您将了解 Zipkin 是什么,以及它在微服务或分布式系统中的重要性。按照《 OpenCensus指南》中使用的示例应用程序,您将学习如何在Zipkin中发送和查看跟踪。
这篇文章是由 Christian Meléndez 撰写的。 Christian 是一名技术专家,最初是一名软件开发人员,之后成为一名云架构师。
原文链接: https://www. scalyr.com/blog/zipkin- tutorial-distributed-tracing/
翻译By : 田同学 || Contact me :leontian1024@gmail.com|| https:// github.com/XinyaoTian
注明原出处及译者信息,欢迎转载。
什么是 Zipkin ?
Zipkin 是一个根据 Google 发表的论文“ Dapper” 进行开源实现的分布式跟踪系统。 Dapper是Google 公司内部的分布式追踪系统,用于生产环境中的系统分布式跟踪。 Google在其论文中对此进行了解释,他们“构建了Dapper,以向Google开发人员提供有关复杂分布式系统行为的更多信息。”从不同角度观察系统对于故障排除至关重要,在系统复杂且分布式的情况下更是如此。
Zipkin可帮助您准确确定对应用程序的请求在哪里花费了更多时间。无论是代码内部的调用,还是对另一服务的内部或外部API调用,您都可以对系统进行检测以共享上下文。微服务通常通过将请求与唯一ID相关联来共享上下文。此外,在系统太复杂的情况下,您可以选择仅使用样本追踪 (sample trace ,一种占用资源比例更低的追踪方式) 来减少系统开销。
举个例子,如果您的微服务应用的响应时间过长,使用Zipkin的话,您就可以轻松了解应用程序是否花费了大部分时间来查询数据库。通过 Zipkin 提供的请求延迟时间可视化,您可能会发现导致请求时间过长的原因,是由于高速缓存服务器已关闭,故所有调用都直接发送到数据库,从而增加了微服务的延迟
现在,让我们看一下Zipkin的组件,以进一步了解追踪数据如何到达Zipkin。
Zipkin体系结构组件
Zipkin不仅仅是一种以可视化方式查看系统跟踪的工具。了解更多有关Zipkin组件的信息可能有助于了解为什么Zipkin不会降低整体应用程序的性能。首先,您需要了解API的请求发送到Zipkin时会发生什么。使用 Zipkin 的前提是需要先向代码添加 Instrumentation(找不到合适的翻译词汇,这里就用英文原词了。译者注)。 Zipkin 具有用于绝大多数编程语言添加 Instrumentation 的官方和社区库。您只需要学习如何将其添加到代码中即可。下面,就是见证奇迹的时刻。
一旦请求结束(在用户收到响应之前),Zipkin就会以单个 span (Zipkin数据追踪的最小计量单位) 的形式将跟踪数据异步发送到 Zipkin 的数据追踪收集器。通常,跟踪是通过带有 context header 的 HTTP 协议发送的。如果代码调用了使用 OpenCensus 和 Zipkin 的其他服务,则该服务将接收这个 HTTP header。 Zipkin 就是这样进行追踪的。收集器就是一个 daemon,用于验证和索引存储中的数据。在 Zipkin 中,持久化组件是可选配的组件,您可以选用 Cassandra,Elasticsearch 或 MySQL。在追踪数据被存储后,Zipkin 的查询服务将提供一个JSON API,任何人都可以使用它来获取追踪数据。Zipkin 自带有一个可访问的用户界面(Web UI),任何人在进行进行故障排除时都可以使用它。
您现在可能会害怕 Zipkin 进行系统追踪会占用很多计算资源。但其实说真的,相比于您的业务应用,它只会占用非常小的一丁点资源。我在这里建议您再每次大范围更新您的业务应用之前,都先在 Zipkin 的监测下跑一遍性能测试,以便心里有 balance(笑)。
现在,让我们看看如何使用OpenCensus和Zipkin将分布式跟踪集成到应用程序中。
使用OpenCensus和Zipkin进行分布式跟踪
我将继续使用OpenCensus帖子中使用的简单的Go HTTP API。您可以在Github的https://github.com/christianhxc/opencensus-golang-tutorial 中找到代码的最新版本。我将解释将跟踪发送到Zipkin所必须添加的代码的每个部分。
1.先决条件:安装和配置
为了使本指南更易于上手,我决定使用以下工具:
使用 Docker,它将帮助您更轻松地运行其他工具。
使用 Go tools 来构建和运行示例应用程序。
在我们安装Go之后,请在您的本地环境中获取 OpenCensus 库。运行以下命令:
go get -u -v go.opencensus.io/...
现在,您可以导入 Zipkin 的库并将其添加到应用程序代码中。
2.使用OpenCensus编写Go HTTP API
我将在本节末尾提供代码的整体版本(所有这些代码段的组合),以防在使用过程中造成混淆。或者,您也可以在本地克隆Github存储库并运行该应用程序。
在main.go文件中,将以下代码添加到import部分:
"go.opencensus.io/exporter/zipkin"
"go.opencensus.io/trace"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinHTTP "github.com/openzipkin/zipkin-go/reporter/http"
上面的代码包括用于Zipkin和跟踪的OpenCensus库,以及将跟踪发送到Zipkin的官方库。
为了提高代码的易读性,我们要为作报告组件 (exporter) 的 Zipkin 创建一个独立函数:
func registerZipkin(){
localEndpoint, err := openzipkin.NewEndpoint("golangsvc", "192.168.1.61:8080")
if err != nil {
log.Fatalf("Failed to create Zipkin exporter: %v", err)
}
reporter := zipkinHTTP.NewReporter("http://localhost:9411/api/v2/spans")
exporter := zipkin.NewExporter(reporter, localEndpoint)
trace.RegisterExporter(exporter)
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
}
在 main() 中调用这个函数。如您所见,该函数不需要任何参数。当您运行应用程序时,每当访问 url 的“ /list” 路径时,exporter 都会向 Zipkin 发送一条跟踪记录。除此之外,您无需在代码中添加任何其他内容。
如果您想要使用样本追踪(sample trace),则删除代码中的“ trace.ApplyConfig(trace.Config {DefaultSampler:trace.AlwaysSample()})”。
如果您想要了解每个请求的详细信息,该怎么办呢?如果是对外部 API 的调用,则需要确保其他服务将跟踪数据发送到 Zipkin 。如果不是,只是出于娱乐目的,我添加了一些其他虚拟函数来模拟对另一个微服务组件(例如缓存,数据库或另一个微服务)的调用。
例如,仔细查看缓存功能:
func cache(r *http.Request) {
_, span := trace.StartSpan(r.Context(), "cache")
defer span.End()
time.Sleep(time.Duration(rand.Intn(100)+1) * time.Millisecond)
}
您会看到我添加了以下代码来报告 span:“ _, span := trace.StartSpan(r.Context(), “cache”)”。如果已有Zipkin header,则该 span 将被视为同一 context 的一部分。当您在 Zipkin 的用户界面中看到完整的代码和轨迹时,就会理解我在说什么了。
# “ main.go”的完整代码将如下所示:
package main
import (
"log"
"math/rand"
"net/http"
"strings"
"time"
"go.opencensus.io/exporter/prometheus"
"go.opencensus.io/exporter/zipkin"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinHTTP "github.com/openzipkin/zipkin-go/reporter/http"
)
func registerPrometheus() *prometheus.Exporter {
pe, err := prometheus.NewExporter(prometheus.Options{
Namespace: "golangsvc",
})
if err != nil {
log.Fatalf("Failed to create Prometheus exporter: %v", err)
}
view.RegisterExporter(pe)
return pe
}
func registerZipkin(){
localEndpoint, err := openzipkin.NewEndpoint("golangsvc", "192.168.1.61:8080")
if err != nil {
log.Fatalf("Failed to create Zipkin exporter: %v", err)
}
reporter := zipkinHTTP.NewReporter("http://localhost:9411/api/v2/spans")
exporter := zipkin.NewExporter(reporter, localEndpoint)
trace.RegisterExporter(exporter)
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
}
func main() {
pe := registerPrometheus()
registerZipkin()
mux := http.NewServeMux()
mux.HandleFunc("/list", list)
mux.Handle("/metrics", pe)
h := &ochttp.Handler{Handler: mux}
if err := view.Register(ochttp.DefaultServerViews...); err != nil {
log.Fatal("Failed to register ochttp.DefaultServerViews")
}
log.Printf("Server listening! ...")
log.Fatal(http.ListenAndServe(":8080", h))
}
func list(w http.ResponseWriter, r *http.Request) {
log.Printf("Serving request: %s", r.URL.Path)
database(r)
serviceb(r)
res := strings.Repeat("o", rand.Intn(99971)+1)
time.Sleep(time.Duration(rand.Intn(977)+1) * time.Millisecond)
w.Write([]byte("Hello, w" + res + "rld!"))
}
func database(r *http.Request) {
cache(r)
_, span := trace.StartSpan(r.Context(), "database")
defer span.End()
time.Sleep(time.Duration(rand.Intn(977)+300) * time.Millisecond)
}
func cache(r *http.Request) {
_, span := trace.StartSpan(r.Context(), "cache")
defer span.End()
time.Sleep(time.Duration(rand.Intn(100)+1) * time.Millisecond)
}
func serviceb(r *http.Request) {
_, span := trace.StartSpan(r.Context(), "serviceb")
defer span.End()
time.Sleep(time.Duration(rand.Intn(800)+200) * time.Millisecond)
servicec(r)
}
func servicec(r *http.Request) {
_, span := trace.StartSpan(r.Context(), "servicec")
defer span.End()
time.Sleep(time.Duration(rand.Intn(700)+100) * time.Millisecond)
}
3.在本地安装Zipkin
让我们使用Docker在本地运行Zipkin。为此,请运行以下命令(请确保您的主机装有 Docker):
docker run -d -p 9411:9411 openzipkin/zipkin
现在,打开您的浏览器访问 http://localhost:9411/
4.从HTTP API生成跟踪
准备好代码并运行Zipkin后,请使用以下命令运行应用程序:
go run main.go
在浏览器中,打开URL http:// localhost:8080/list,并在每次单击“ /list”端点时刷新页面以生成跟踪。
5.查看Zipkin中的监测情况
返回Zipkin浏览器窗口,您应该看到类似以下内容:
![6dd7b4c25752d1d62bb3abb16ac61ea2.png](https://i-blog.csdnimg.cn/blog_migrate/a6b70201d713741f2bd3799c104fffe1.jpeg)
您可以像我一样使用过滤器仅查看路径“ /list”的情况。然后,单击任何追踪轨迹(trace),您将看到类似以下的内容:
![d15ae37b541d4530c750e414325d4501.png](https://i-blog.csdnimg.cn/blog_migrate/a673871d7a409c6c990b578fcf5860e2.jpeg)
另一个可行的选择是,您可以在应用程序的响应有效负载中包含跟踪ID,以便请确定位错误位置,以最快的速度解决客户们的投诉。
您可以精确地看到每个请求在每个功能上具体花费了多少时间。例如,您可以发现问题出在对某个微服务的调用上,并首先关注减少该微服务组件的延迟。或者,您可以使用这些跟踪来了解请求的工作流程是什么。如果多次调用依赖项(例如缓存)怎么办?使用Zipkin,可以轻松发现此类问题。
使用 Zipkin 进行故障排除
不要浪费宝贵的时间在生产环境中调试你的应用程序,要用 Zipkin!您可以添加 Instrumentation 来检测代码,从而轻松发现几乎所有问题,或者至少能了解问题可能出在哪里。我曾经使用这种方法,将 Instrumentation 插入到代码并获取每个请求的跟踪,最终发现微服务组件之间的调用产生了死循环,从而增加了服务的延迟。
Zipkin UI 可以帮助开发者,甚至是那些非专业人士提供很好的发现问题的手段,并为解决问题做出贡献。拥有这种简单又详细的可视化功能将会对故障排除很有帮助。使用 Zipkin ,在对分布式系统进行故障排除时,您将不会再像大海捞针一样陷入迷茫。
感谢您的阅读,希望对您的工作有所帮助,谢谢。