ajax请求是宏任务还是微任务_微服务-如何解决链路追踪问题
一、鏈路追蹤
微服務(wù)架構(gòu)是將單個(gè)應(yīng)用程序被劃分成各種小而連接的服務(wù),每一個(gè)服務(wù)完成一個(gè)單一的業(yè)務(wù)功能,相互之間保持獨(dú)立和解耦,每個(gè)服務(wù)都可以獨(dú)立演進(jìn)。相對(duì)于傳統(tǒng)的單體服務(wù),微服務(wù)具有隔離性、技術(shù)異構(gòu)性、可擴(kuò)展性以及簡(jiǎn)化部署等優(yōu)點(diǎn)。
? 同樣的,微服務(wù)架構(gòu)在帶來(lái)諸多益處的同時(shí),也為系統(tǒng)增加了不少?gòu)?fù)雜性。它作為一種分布式服務(wù),通常部署于由不同的數(shù)據(jù)中心、不同的服務(wù)器組成的集群上。而且,同一個(gè)微服務(wù)系統(tǒng)可能是由不同的團(tuán)隊(duì)、不同的語(yǔ)言開(kāi)發(fā)而成。通常一個(gè)應(yīng)用由多個(gè)微服務(wù)組成,微服務(wù)之間的數(shù)據(jù)交互需要通過(guò)遠(yuǎn)過(guò)程調(diào)用的方式完成,所以在一個(gè)由眾多微服務(wù)構(gòu)成的系統(tǒng)中,請(qǐng)求需要在各服務(wù)之間流轉(zhuǎn),調(diào)用鏈路錯(cuò)綜復(fù)雜,一旦出現(xiàn)問(wèn)題,是很難進(jìn)行問(wèn)題定位和追查異常的。
? 鏈路追蹤系統(tǒng)就是為解決上述問(wèn)題而產(chǎn)生的,它用來(lái)追蹤每一個(gè)請(qǐng)求的完整調(diào)用鏈路,記錄從請(qǐng)求開(kāi)始到請(qǐng)求結(jié)束期間調(diào)用的任務(wù)名稱、耗時(shí)、標(biāo)簽數(shù)據(jù)以及日志信息,并通過(guò)可視化的界面進(jìn)行分析和展示,來(lái)幫助技術(shù)人員準(zhǔn)確地定位異常服務(wù)、發(fā)現(xiàn)性能瓶頸、梳理調(diào)用鏈路以及預(yù)估系統(tǒng)容量。
? 鏈路追蹤系統(tǒng)的理論模型幾乎都借鑒了 Google 的一篇論文”Dapper, a Large-Scale Distributed Systems Tracing Infrastructure”,典型產(chǎn)品有Uber jaeger、Twitter zipkin、淘寶鷹眼等。這些產(chǎn)品的實(shí)現(xiàn)方式雖然不盡相同,但核心步驟一般都有三個(gè):數(shù)據(jù)采集、數(shù)據(jù)存儲(chǔ)和查詢展示。
? 鏈路追蹤系統(tǒng)第一步,也是最基本的工作就是數(shù)據(jù)采集。在這個(gè)過(guò)程中,鏈路追蹤系統(tǒng)需要侵入用戶代碼進(jìn)行埋點(diǎn),用于收集追蹤數(shù)據(jù)。但是由于不同的鏈路追蹤系統(tǒng)的API互不兼容,所以埋點(diǎn)代碼寫法各異,導(dǎo)致用戶在切換不同鏈路追蹤產(chǎn)品時(shí)需要做很大的改動(dòng)。為了解決這類問(wèn)題,于是誕生了OpenTracing規(guī)范,旨在統(tǒng)一鏈路追蹤系統(tǒng)的API。
二、OpenTracing規(guī)范
? OpenTracing 是一套分布式追蹤協(xié)議,與平臺(tái)和語(yǔ)言無(wú)關(guān),具有統(tǒng)一的接口規(guī)范,方便接入不同的分布式追蹤系統(tǒng)。
? OpenTracing語(yǔ)義規(guī)范詳見(jiàn):https://github.com/opentracing/specification/blob/master/specification.md2.1 數(shù)據(jù)模型(Data Model)
? OpenTracing語(yǔ)義規(guī)范中定義的數(shù)據(jù)模型有 Trace、Sapn以及Reference。
2.1.1 Trace
? Trace表示一條完整的追蹤鏈路,例如:一個(gè)事務(wù)或者一個(gè)流程的執(zhí)行過(guò)程。一個(gè) Trace 是由一個(gè)或者多個(gè) Span 組成的有向無(wú)環(huán)圖(DAG)。
下圖表示一個(gè)由8個(gè)Span組成的Trace:
[Span A] ←←←(the root span)|+------+------+| |[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)| |[Span D] +---+-------+| |[Span E] [Span F] >>> [Span G] >>> [Span H]↑↑↑(Span G `FollowsFrom` Span F)按照時(shí)間軸方式更為直觀地展現(xiàn)該Trace:
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time[Span A···················································][Span B··············································][Span D··········································][Span C········································][Span E·······] [Span F··] [Span G··] [Span H··]2.1.2 Span
? Span表示一個(gè)獨(dú)立的工作單元,它是一條追蹤鏈路的基本組成要素。例如:一次RPC調(diào)用、一次函數(shù)調(diào)用或者一次Http請(qǐng)求。
每個(gè)Span封裝了如下?tīng)顟B(tài):
- 操作名稱用于表示該Span的任務(wù)名稱。 例如:一個(gè) RPC方法名, 一個(gè)函數(shù)名,或者大型任務(wù)中的子任務(wù)名稱。
- 開(kāi)始時(shí)間戳任務(wù)開(kāi)始時(shí)間。
- 結(jié)束時(shí)間戳。任務(wù)結(jié)束時(shí)間。通過(guò)Span的結(jié)束時(shí)間戳和開(kāi)始時(shí)間戳,就能夠計(jì)算出該Span的整體耗時(shí)。
- 一組Span標(biāo)簽每一個(gè)Span標(biāo)簽是一個(gè)鍵值對(duì)。鍵必須是字符串,值可以是字符串、布爾或數(shù)值類型。
一組Span日志每一條Span日志由一個(gè)鍵值對(duì)和一個(gè)相應(yīng)的時(shí)間戳組成。鍵必須是字符串,值可以是任何類型。
常見(jiàn)日志鍵參考:https://github.com/opentracing/specification/blob/master/semantic_conventions.md2.1.3 Reference
? 一個(gè)Span可以與一個(gè)或者多個(gè)Span存在因果關(guān)系,這種關(guān)系稱為Reference。OpenTracing目前定義了兩種關(guān)系:ChildOf(父子)關(guān)系 和 FollowsFrom(跟隨)關(guān)系。
- ChildOf關(guān)系父Span的執(zhí)行依賴子Span的執(zhí)行結(jié)果,此時(shí)子Span對(duì)父Span的Reference關(guān)系是ChildOf。比如對(duì)于一次RPC調(diào)用,服務(wù)端的Span(子Span)與客戶端調(diào)用的Span(父Span)就是ChildOf關(guān)系。
- FollowsFrom關(guān)系父Span的執(zhí)行不依賴子Span的執(zhí)行結(jié)果,此時(shí)子Span對(duì)父Span的Reference關(guān)系是FollowFrom。FollowFrom常用于表示異步調(diào)用,例如消息隊(duì)列中Consumer Span與Producer Span之間的關(guān)系。
2.2 應(yīng)用接口(API)
2.2.1 Tracer
? Tracer接口用于創(chuàng)建Span、跨進(jìn)程注入數(shù)據(jù)和提取數(shù)據(jù)。通常具有以下功能:
- Start a new span
創(chuàng)建并啟動(dòng)一個(gè)新的Span。 - Inject
將SpanContext注入載體(Carrier)。 - Extract
從載體(Carrier)中提取SpanContext。
2.2.2 Span
- Retrieve a SpanContext
返回Span對(duì)應(yīng)的SpanContext。 - Overwrite the operation name
更新操作名稱。 - Set a span tag
設(shè)置Span標(biāo)簽數(shù)據(jù)。 - Log structured data
記錄結(jié)構(gòu)化數(shù)據(jù)。 - Set a baggage item
baggage item是字符串型的鍵值對(duì),它對(duì)應(yīng)于某個(gè) Span,隨Trace一起傳播。由于每個(gè)鍵值都會(huì)被拷貝到每一個(gè)本地及遠(yuǎn)程的子Span,這可能導(dǎo)致巨大的網(wǎng)絡(luò)和CPU開(kāi)銷。 - Get a baggage item
獲取baggage item的值。 - Finish
結(jié)束一個(gè)Span。
2.2.3 Span Context
? 用于攜帶跨越服務(wù)邊界的數(shù)據(jù),包括trace ID、Span ID以及需要傳播到下游Span的baggage數(shù)據(jù)。在OpenTracing中,強(qiáng)制要求SpanContext實(shí)例不可變,以避免在Span完成和引用時(shí)出現(xiàn)復(fù)雜的生命周期問(wèn)題。
2.2.4 NoopTracer
? 所有對(duì)OpenTracing API的實(shí)現(xiàn),必須提供某種形式的NoopTracer,用于標(biāo)記控制OpenTracing或注入對(duì)測(cè)試無(wú)害的東西。
三、Jaeger
? Jaeger是Uber開(kāi)源的分布式追蹤系統(tǒng),它的應(yīng)用接口完全遵循OpenTracing規(guī)范。jaeger本身采用go語(yǔ)言編寫,具有跨平臺(tái)跨語(yǔ)言的特性,提供了各種語(yǔ)言的客戶端調(diào)用接口,例如c++、java、go、python、ruby、php、nodejs等。
項(xiàng)目地址:https://github.com/jaegertracing3.1 Jaeger組件
- jaeger-clientjaeger的客戶端代碼庫(kù),它實(shí)現(xiàn)了OpenTracing協(xié)議。當(dāng)我們的應(yīng)用程序?qū)⑵溲b配后,負(fù)責(zé)收集數(shù)據(jù),并發(fā)送到j(luò)aeger-agent。這是我們唯一需要編寫代碼的地方。
- jaeger-agent負(fù)責(zé)接收從jaeger-client發(fā)來(lái)的Trace/Span信息,并批量上傳到j(luò)aeger-collector。
- jaeger-collector負(fù)責(zé)接收從jaeger-agent發(fā)來(lái)的Trace/Span信息,并經(jīng)過(guò)校驗(yàn)、索引等處理,然后寫入到后端存儲(chǔ)。
- data store負(fù)責(zé)數(shù)據(jù)存儲(chǔ)。Jaeger的數(shù)據(jù)存儲(chǔ)是一個(gè)可插拔的組件,目前支持Cassandra、ElasticSearch和Kafka。
- jaeger-query & ui負(fù)責(zé)數(shù)據(jù)查詢,并通過(guò)前端界面展示查詢結(jié)果。
3.2 快速入門
? Jaeger官方提供了all-in-one鏡像,方便快速進(jìn)行測(cè)試:
# 拉取鏡像 $docker pull jaegertracing/all-in-one:latest# 運(yùn)行鏡像 $docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 14268:14268 -p 9411:9411 -p 16686:16686 jaegertracing/all-in-one:latest? 通過(guò)all-in-one鏡像啟動(dòng),我們發(fā)現(xiàn)Jaeger占據(jù)了很多端口。以下是端口使用說(shuō)明:
? 啟動(dòng)后,我們可以訪問(wèn) http://localhost:16686 ,在瀏覽器中查看和查詢收集的數(shù)據(jù)。? 由于通過(guò)all-in-one鏡像方式收集的數(shù)據(jù)都存儲(chǔ)在docker中,無(wú)法持久保存,所以只能用于開(kāi)發(fā)或者測(cè)試環(huán)境,無(wú)法用于生產(chǎn)環(huán)境。生產(chǎn)環(huán)境中需要依據(jù)實(shí)際情況,分別部署各個(gè)組件。
四、Jaeger在業(yè)務(wù)代碼中的應(yīng)用
? 系統(tǒng)中使用Jaeger非常簡(jiǎn)單,只需要在原有程序中插入少量代碼。以下代碼模擬了一個(gè)查詢用戶賬戶余額,執(zhí)行扣款的業(yè)務(wù)場(chǎng)景:
4.1 初始化jaeger函數(shù)
? 主要是按照實(shí)際需要配置有關(guān)參數(shù),例如服務(wù)名稱、采樣模式、采樣比例等等。
func initJaeger() (tracer opentracing.Tracer, closer io.Closer, err error) {// 構(gòu)造配置信息cfg := &config.Configuration{// 設(shè)置服務(wù)名稱ServiceName: "ServiceAmount",// 設(shè)置采樣參數(shù)Sampler: &config.SamplerConfig{Type: "const", // 全采樣模式Param: 1, // 開(kāi)啟狀態(tài)},}// 生成一條新tracertracer, closer, err = cfg.NewTracer()if err == nil {// 設(shè)置tracer為全局單例對(duì)象opentracing.SetGlobalTracer(tracer)}return }4.2 檢測(cè)用戶余額函數(shù)
? 用于檢測(cè)用戶余額,模擬一個(gè)子任務(wù)Span。
func CheckBalance(request string, ctx context.Context) {// 創(chuàng)建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "CheckBalance")// 模擬系統(tǒng)進(jìn)行一系列的操作,耗時(shí)1/3秒time.Sleep(time.Second / 3)// 示例:將需要追蹤的信息放入tagspan.SetTag("request", request)span.SetTag("reply", "CheckBalance reply")// 結(jié)束當(dāng)前spanspan.Finish()log.Println("CheckBalance is done") }4.3 從用戶賬戶扣款函數(shù)
? 從用戶賬戶扣款,模擬一個(gè)子任務(wù)span。
func Reduction(request string, ctx context.Context) {// 創(chuàng)建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "Reduction")// 模擬系統(tǒng)進(jìn)行一系列的操作,耗時(shí)1/2秒time.Sleep(time.Second / 2)// 示例:將需要追蹤的信息放入tagspan.SetTag("request", request)span.SetTag("reply", "Reduction reply")// 結(jié)束當(dāng)前spanspan.Finish()log.Println("Reduction is done") }4.4 主函數(shù)
? 初始化jaeger環(huán)境,生成tracer,創(chuàng)建父span,以及調(diào)用查詢余額和扣款兩個(gè)子任務(wù)span。
package mainimport ("context""fmt""github.com/opentracing/opentracing-go""github.com/uber/jaeger-client-go/config""io""log""time" )func main() {// 初始化jaeger,創(chuàng)建一條新tracertracer, closer, err := initJaeger()if err != nil {panic(fmt.Sprintf("ERROR: cannot init Jaeger: %vn", err))}defer closer.Close()// 創(chuàng)建一個(gè)新span,作為父span,開(kāi)始計(jì)費(fèi)過(guò)程span := tracer.StartSpan("CalculateFee")// 生成父span的contextctx := opentracing.ContextWithSpan(context.Background(), span)// 示例:設(shè)置一個(gè)span標(biāo)簽信息span.SetTag("db.instance", "customers")// 示例:輸出一條span日志信息span.LogKV("event", "timed out")// 將父span的context作為參數(shù),調(diào)用檢測(cè)用戶余額函數(shù)CheckBalance("CheckBalance request", ctx)// 將父span的context作為參數(shù),調(diào)用扣款函數(shù)Reduction("Reduction request", ctx)// 結(jié)束父spanspan.Finish() }五、Jaeger在gRPC微服務(wù)中的應(yīng)用
? 我們依然模擬了一個(gè)查詢用戶賬戶余額,執(zhí)行扣款的業(yè)務(wù)場(chǎng)景,并把查詢用戶賬戶余額和執(zhí)行扣款功能改造為gRPC微服務(wù):
5.1 gRPC Server端代碼
main.go:
? 代碼使用了第三方依賴庫(kù)http://github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing,該依賴庫(kù)將OpenTracing封裝為通用的gRPC中間件,并通過(guò)gRPC攔截器無(wú)縫嵌入gRPC服務(wù)中。
package mainimport ("fmt""github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing""github.com/opentracing/opentracing-go""github.com/uber/jaeger-client-go/config""google.golang.org/grpc""google.golang.org/grpc/reflection""grpc-jaeger-server/account""io""log""net" )// 初始化jaeger func initJaeger() (tracer opentracing.Tracer, closer io.Closer, err error) {// 構(gòu)造配置信息cfg := &config.Configuration{// 設(shè)置服務(wù)名稱ServiceName: "ServiceAmount",// 設(shè)置采樣參數(shù)Sampler: &config.SamplerConfig{Type: "const", // 全采樣模式Param: 1, // 開(kāi)啟全采樣模式},}// 生成一條新tracertracer, closer, err = cfg.NewTracer()if err == nil {// 設(shè)置tracer為全局單例對(duì)象opentracing.SetGlobalTracer(tracer)}return }func main() {// 初始化jaeger,創(chuàng)建一條新tracertracer, closer, err := initJaeger()if err != nil {panic(fmt.Sprintf("ERROR: cannot init Jaeger: %vn", err))}defer closer.Close()log.Println("succeed to init jaeger")// 注冊(cè)gRPC account服務(wù)server := grpc.NewServer(grpc.UnaryInterceptor(grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(tracer))))account.RegisterAccountServer(server, &AccountServer{})reflection.Register(server)log.Println("succeed to register account service")// 監(jiān)聽(tīng)gRPC account服務(wù)端口listener, err := net.Listen("tcp", ":8080")if err != nil {log.Println(err)return}log.Println("starting register account service")// 開(kāi)啟gRpc account服務(wù)if err := server.Serve(listener); err != nil {log.Println(err)return} }計(jì)費(fèi)微服務(wù) accountsever.go:
package mainimport ("github.com/opentracing/opentracing-go""golang.org/x/net/context""grpc-jaeger-server/account""time" )// 計(jì)費(fèi)服務(wù) type AccountServer struct{}// 檢測(cè)用戶余額微服務(wù),模擬子span任務(wù) func (s *AccountServer) CheckBalance(ctx context.Context, request *account.CheckBalanceRequest) (response *account.CheckBalanceResponse, err error) {response = &account.CheckBalanceResponse{Reply: "CheckBalance Reply", // 處理結(jié)果}// 創(chuàng)建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "CheckBalance")// 模擬系統(tǒng)進(jìn)行一系列的操作,耗時(shí)1/3秒time.Sleep(time.Second / 3)// 將需要追蹤的信息放入tagspan.SetTag("request", request)span.SetTag("reply", response)// 結(jié)束當(dāng)前spanspan.Finish()return response, err }// 從用戶賬戶扣款微服務(wù),模擬子span任務(wù) func (s *AccountServer) Reduction(ctx context.Context, request *account.ReductionRequest) (response *account.ReductionResponse, err error) {response = &account.ReductionResponse{Reply: "Reduction Reply", // 處理結(jié)果}// 創(chuàng)建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "Reduction")// 模擬系統(tǒng)進(jìn)行一系列的操作,耗時(shí)1/3秒time.Sleep(time.Second / 3)// 將需要追蹤的信息放入tagspan.SetTag("request", request)span.SetTag("reply", response)// 結(jié)束當(dāng)前spanspan.Finish()return response, err }5.2 gRPC Client端代碼main.go:
package mainimport ("context""fmt""github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing""github.com/opentracing/opentracing-go""github.com/uber/jaeger-client-go/config""google.golang.org/grpc""grpc-jaeger-client/account""io""log" )// 初始化jaeger func initJaeger() (tracer opentracing.Tracer, closer io.Closer, err error) {// 構(gòu)造配置信息cfg := &config.Configuration{// 設(shè)置服務(wù)名稱ServiceName: "ServiceAmount",// 設(shè)置采樣參數(shù)Sampler: &config.SamplerConfig{Type: "const", // 全采樣模式Param: 1, // 開(kāi)啟全采樣模式},}// 生成一條新tracertracer, closer, err = cfg.NewTracer()if err == nil {// 設(shè)置tracer為全局單例對(duì)象opentracing.SetGlobalTracer(tracer)}return }func main() {// 初始化jaeger,創(chuàng)建一條新tracertracer, closer, err := initJaeger()if err != nil {panic(fmt.Sprintf("ERROR: cannot init Jaeger: %vn", err))}defer closer.Close()log.Println("succeed to init jaeger")// 創(chuàng)建一個(gè)新span,作為父spanspan := tracer.StartSpan("CalculateFee")// 函數(shù)返回時(shí)關(guān)閉spandefer span.Finish()// 生成span的contextctx := opentracing.ContextWithSpan(context.Background(), span)// 連接gRPC serverconn, err := grpc.Dial("localhost:8080",grpc.WithInsecure(),grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(tracer),)))if err != nil {log.Println(err)return}// 創(chuàng)建gRPC計(jì)費(fèi)服務(wù)客戶端client := account.NewAccountClient(conn)// 將父span的context作為參數(shù),調(diào)用檢測(cè)用戶余額的gRPC微服務(wù)checkBalanceResponse, err := client.CheckBalance(ctx,&account.CheckBalanceRequest{Account: "user account",})if err != nil {log.Println(err)return}log.Println(checkBalanceResponse)// 將父span的context作為參數(shù),調(diào)用扣款的gRPC微服務(wù)reductionResponse, err := client.Reduction(ctx,&account.ReductionRequest{Account: "user account",Amount: 1,})if err != nil {log.Println(err)return}log.Println(reductionResponse) }注:本文全部源代碼位于:https://github.com/wangshizebin/micro-service
本文時(shí)候用的開(kāi)發(fā)工具為:goland
原文鏈接:https://www.cnblogs.com/wanghao72214/p/13932810.html
如果覺(jué)得本文對(duì)你有幫助,可以點(diǎn)贊關(guān)注支持一下,也可以點(diǎn)進(jìn)我主頁(yè)關(guān)注我公眾號(hào),上面有更多技術(shù)干貨文章以及相關(guān)資料共享,大家一起學(xué)習(xí)進(jìn)步!
總結(jié)
以上是生活随笔為你收集整理的ajax请求是宏任务还是微任务_微服务-如何解决链路追踪问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 阴阳师中武士之灵哪里多(《阴阳师》手游官
- 下一篇: vba移动文件_VBA代码解决方案的第6