在以前单体或者集群架构,用户一个请求过来或者经过反向代理,直接到web服务器,操作数据库,返回用户请求结果。在这种情况,请求的调用链,我们可以通过函数之间的调用,打日志,系统如果出现异常,可以通过分析日志去找bug;如果系统遇到性能瓶颈,想知道从请求进来到返回,是业务哪一步出了问题,可以通过调用链路间请求时长分析出耗时的操作,针对具体的去优化。
但是随着系统架构的改进,微服务分层架构实施,上层会调用下层服务(如最上层的站点业务服务,会调用基础的通用业务服务,通用业务服务又会去调用数据访问层服务),此时你的日志只能打在自己的服务上,其他的服务日志由其他人去打印。这样就出现几个问题
一,定位故障的过程非常痛苦
如果一个用户请求你的A服务,可能timeout or error。此时你就要去分析自己的日志,发现是你调用的一个B服务出现了问题,你就要通知B业务线,去找问题,B检查后会发现,是他自己调用的C服务出问题了,然后就这样剥洋葱一层层的反馈,可能最终问题只需要五分钟解决,但是找问题就花费了两个小时。
二,定位系统瓶颈很困难
如果你的服务在用户调用时候耗时很慢,比如1s,你想要分析出具体是哪一步耗时严重,此时由于是分层的,你只能在自己系统打日志,比如查到了是调用B,但是B也是存在调用,所以一层层找,很难分析出瓶颈的原因所在。
三, 不合理的调用,和弹性扩缩容困难
不合理的调用:我们微服务架构,要做到调用远端的服务就像是调用本地函数一样,但是很多人有误解就会觉得和调用本地的方法一样消耗性能,就往往会不考虑后果在循环中调用,此时你分析你的日志,无法分析出这是一个请求调用了几十次服务。
弹性扩缩容:由于现在的分层调用B服务作为下游服务,只知道上层有人调用,此时B 根本不知道有多少在调用自己,当B系统瓶颈加一台机器要通知所有人的时候,可能要去各个的产品线去询问有没有调用自己,自己加了机器,你们要做一下同步,让流量打到新服务节点。
解决上面的痛点需要用到分布式调用链追踪系统,这也是这篇文章的重点
分布式调用链追踪系统有什么特点?
1 全链路
日志的记录是全链路的,从请求进来,到web层,业务service,基础service,db,cache 等,一整条链路每一步的调用都有记录。
2 跨进程
跨站点,跨服务,跨存储等都是不同的进程。
3 全流量
不仅仅要做到描述清楚服务与服务之间的依赖关系,还要做到精确,精确到每一步的调用,每个数据库语句的执行时间等。
实现分布式调用链追踪系统有什么难点?
1 跨进程串联标识问题,
刚刚说了跨站点,跨服务,跨存储就是跨进程,我们如何把这些请求到达各个节点给串联起来呢,也就是一个请求进来,如何知道A->B->C,他们是同一个请求造成的这些依赖请求调用;
2 跨进程时序问题
因为是跨节点,不同的机器,机器本地时间也不同,你如何做到时间的一个偏序性,如果是单机系统,所有的时间都是同一台机器,先后的请求时间肯定是有偏序性,但是现在可能记录的时间在A机器是10点,请求调用B后,B服务记录的是9:59分。
3 跨进程调用深度的问题
如上图,左边调用深度是2层,a先调用服务b在调用服务c,右边调用深度是3层,a->b->c;此时通过时序判断调用层级肯定不行,因为现在在三台机器的时序肯定是一样的,都是先a 再b 再c。
分析了痛点,我们要知道改哪些地方?
分析一下上面的图片,是一个http请求的全链路,从开始到结束。我们如何做才能做到一个请求在各个系统造成的记录日志能标识出来是来自于同一个请求,如何做到在各个系统记录的日志有偏序性,如何做到,每一个调用的层级关系。
1 改框架,要记录请求开始和结束的点。
2 改rpc ,rpc-client要记录调用service的开始和结束的点,service要记录接到请求和请求结束的点
3 改db-client, 要在数据访问层封装一层,记录请求的sql,请求db开始和结束的点。
4 改cache-client 原理同db-client.
知道了要改哪些地方,我们如何去改呢,也就是如何解决上面的三个难题?
我们可以通过定长header的tranceid+spanid 去解决上面的两个维度的问题。
tranceid:表示一个请求链路,跨进程跨服务调用,由请求进来时候最上层web-service生成,全局唯一,整个请求调用链路一直不变,表示是同一个请求。
spanid: 用来表示每一层的rpc调用。表示深度和时序性