分布式调用链跟踪系统,属于监控系统的一类。系统架构逐步演进时,后期形态往往是一个平台由很多不同的服务、组件构成,用户请求过来后,可能会经过其中多个服务。
不过,出问题时往往很难排查,如整个请求变慢、偶尔报错、不可用等,我们很难得知具体是由哪一个或哪些服务引起的,通常开发同学都会互相甩锅,最后不得不花大量时间人肉 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