分佈式調用鏈跟蹤系統,屬於監控系統的一類。系統架構逐步演進時,後期形態往往是一個平臺由很多不同的服務、組件構成,用戶請求過來後,可能會經過其中多個服務。
不過,出問題時往往很難排查,如整個請求變慢、偶爾報錯、不可用等,我們很難得知具體是由哪一個或哪些服務引起的,通常開發同學都會互相甩鍋,最後不得不花大量時間人肉 tracing
Jaeger是Uber Technologies用GO語言開發的分佈式跟蹤系統,現已開源。它用於監視和排除基於微服務的分佈式系統,包括:
分佈式上下文傳播
分佈式事務監控
根本原因分析
服務依賴性分析
性能/延遲優化
架構圖
兩個核心概念
TraceId:
用於標識一次完整請求 trace,會從頭到尾貫穿在各個服務中,通常在請求入口時生成。
SpanId:
用於標識某個調用跨度 span,一個 span 可以有多個 子span, 通常一個完整的 trace 由很多個 span 組成。
PHP使用並傳遞uber-trace-id到下一層鏈路中!在http請求時調用jaege注入請求,會生成唯一的uber-trace-id 與spanid,獲取到這倆個id,組裝數據Metadata ,通過grpc調用服務方法時把Metadata傳遞過去,而在go-micro中通過context.Context就可以獲取到,如果需要服務之間繼續調用,則繼續傳遞context。這樣就把一次完整的鏈路請求記錄到Jaeger中。
基本流程
請求入口生成 trace
在方法(或服務)調用前,生成 span,記錄時間
調用時,攜帶 TraceId SpanId (如,在 http header 或 grpc meta data 裏)
調用完後關聯到 trace
統一上報到 Jaeger存儲。
代碼示例
public function Login(Request $request)
{
$jaegerClien = new jaegerClien();
$jaegerClien->setSvcName();
$metadata = $jaegerClien->Hprose('admin/login'); //請求鏈路的metadata
$phone = $request->input('phone');
$password = $request->input('password');
$consul = new Consul("xyd.vip.srv");
$host = $consul->getHost();
$client = new Vipuser($host,[
'credentials' => \Grpc\ChannelCredentials::createInsecure(),
]);
$req = new vipuserReq();
$req->setMobile($phone);
$req->setPwd($password);
list($rsp, $status) = $client->Login($req,$metadata)->wait();
$code = $rsp->getCode();
$msg = $rsp->getMsg();
$data = $rsp->getData();
$token = $rsp->getToken();
if ($code == 200){
$user = json_decode($data);
$request->session()->put('token',$token);
$request->session()->put('user', $user);
$jaegerClien->setDisabled(); //關閉jaeger
return $this->_returnJosn($code,$user,$msg,0,$token);
}else{
return $this->_returnJosn($code,array(),$msg);
}
}
public function Hprose($operationName){
$this->jaegercfg = JaegerConfig::getInstance();
$tracer = $this->jaegercfg->initTracer($this->svcName, $this->jaegerUrl);
$spanContext = $tracer->extract(Formats\TEXT_MAP, $_SERVER);
$serverSpan = $tracer->startSpan($operationName, ['child_of' => $spanContext]);
$tracer->inject($serverSpan->getContext(), Formats\TEXT_MAP, $_SERVER);
//init server span end
$serverSpan->finish();
$this->jaegercfgFlush();
$spanContext = (array)$serverSpan->getContext();
$meta = new Metadata();
$meta->set('uber-trace-id', $_SERVER['UBER-TRACE-ID']);
$meta->set('X-B3-ParentSpanId', $spanContext['parentId']);
$meta->set('X-B3-SpanId', $spanContext['spanId']);
$meta->set('X-B3-Sampled', true);
return $this->getZipKinMetadata($meta);
}
func (a *User) Login(ctx context.Context, req *user.Request, rsp *user.Response) error {
mobile := req.Mobile
pwd := req.Pwd
code, msg, user := UserService.Login(mobile, pwd)
jsons := "{}"
token := ""
if user != nil {
jsonsby, _ := json.Marshal(user)
jsons = string(jsonsby)
token = UserService.MakeAccessToken(ctx, user.Id, user.Mobile)
}
response.UserReturnRes(code, msg, jsons, token, rsp)
return nil
}
//使用上下文傳遞 span
func (a *AuthClient) MakeAccessToken(ctx context.Context, userid int64, phone string) string {
info, err := a.client.MakeAccessToken(ctx, &authSvc.Request{
Userid: userid,
Phone: phone,
})
fmt.Println("info:", info)
fmt.Println("err:", err)
if err != nil {
return ""
}
//span.Finish()
return info.Token
}
實際效果圖
可以看到整個鏈路的耗時,點擊進去可以看到每個span的詳細信息
實踐準備工作
安裝Jaeger
docker安裝測試版 所有數據都是存儲到內存中 只能作爲測試
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 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:latest
Docker部署jaeger並使用elasticsearch作爲存儲引擎
docker + collector安裝
docker run -d --name jaeger-collector --restart=always --link elasticsearch -e SPAN_STORAGE_TYPE=elasticsearch -e ES_SERVER_URLS=http://172.26.155.215:9200 -e ES_USERNAME=elastic -p 14267:14267 -p 14268:14268 -p 9411:9411 jaegertracing/jaeger-collector
注意:
--link elasticsearch,代表docker 關聯,該名字必須和你安裝elasticsearch —name的名字相同
--SPAN_STORAGE_TYPE=elasticsearch 代表安裝jaeger選擇elasticsearch作爲存儲
-e ES_SERVER_URLS=http://elasticsearch:9200次條目代表你選擇容器安裝的elasticsearch的9200端口
-e ES_USERNAME elasticsearch的用戶名:默認elastic,下同
-e ES_PASSWORD elasticsearch的密碼 沒有設置密碼不用填
docker + query安裝
docker run -d --name jaeger-query --restart=always --link elasticsearch -e SPAN_STORAGE_TYPE=elasticsearch -e ES_SERVER_URLS=http://172.26.155.215:9200 -p 16686:16686/tcp jaegertracing/jaeger-query
注意,ES_USERNAME、ES_PASSWORD這兩個環境變量,當你的elasticsearch未設置賬號密碼時,你可以不填,也可以填上默認值,elasticsearch的默認ES_USERNAME=elastic,ES_PASSWORD=changeme
部署完成query之後,根據你暴露的端口號(-p 16686:16686/tcp),瀏覽器輸入以下地址(將localhost換成你部署query的地址):
http://localhost:16686
docker + agent安裝
docker run -d --name jaeger-agent --restart=always -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778/tcp jaegertracing/jaeger-agent --collector.host-port=172.26.155.215:14267