快狗打车CTO沈剑:低成本搞定分布式调用链追踪系统( 六 )


前面说的分布式调用链的一个核心难点 , 如何跨进程的串联请求 , 如何跨进程的标识调用时序 , 如何跨进程的标识调用深度 , 只要你做到了前面我们说的统一组件 , 统一框架 , 其实这个事情是相对比较容易做的 , 因为如果你统一框架的话 , 你的内部RPC的协议你是自己可以控制的 。 你在自有的协议里要增加三个字段:
第一个字段是请求的ID , 来做全链路的请求标识;第二个字段是时序的ID , 来做全链路的时序的标识;第三个字段是深度的ID , 来做调用链的深度调用数是广度还是深度的一个标识 。但是前提是上文所述协议是可控了 , 如果用的是Dubbo , 而Dubbo不只是这样的功能 , 则可能要去做二开 , 我们的整个站点和服务都是自研的 , 所以加相关的字段相对比较容易 。

快狗打车CTO沈剑:低成本搞定分布式调用链追踪系统
文章图片
1)请求ID
比如说你们统一用了Dubbo框架 , 那么Dubbo的定长包头变长包体的二进制协议 , 我可以在包头里面加一些字段 , 比如说加请求ID字段 , 我再请求第一次进入到我们的系统 , 比如说进入到我们的外部层的时候 , 我生成一个全局的、唯一的分布式的ID来标识 , 这一个请求在系统中的总串联 。
然后在站点层通过RPCClient调RPCServer的时候 , 把这个请求ID放到协议的头部里去 , 就可以由上游传到下游 。 如果还递归调别的服务 , 我还可以放在包头里传给下游 。 这样的话只要我们的协议是可控可扩展的 , 这一全局的请求ID , 我就可以在系统里跨机器跨进程的到处传 , 在数据上报的时候我就统一的传全局请求ID , 就能够将众多收集数据的一些信息 , 一些日志里 , 去将整个信息从请求ID给拿出来 。
2)时序ID
同理 , 我们不能够用本地的时间来做调用链追踪的时序偏序 。 因为每个服务器它的本地时间会不一样 , 但是你可以用一个时序ID , 比如说我在请求进入到系统的时候 , 我生成一个时序ID11 , 我调第1个的时候 , 时序ID2 , 时序ID3 , 时序ID4 , 都不用传到这边来 , 在这边就可以进入时序ID4、5、6、7、8 。 因为我们的协议是可控的 , 我们可以在协议的包头里加一个字段 , 叫做时序ID , 来标识数据上报的整体的时间偏序 , 本地的执行时间只做参考 。 即我们不能够用这一台机器的本地时间和这一台机器的本地时间来标识哪一个请求执行的前 , 哪一个数据上报在后 , 这个时间只能参考 。 绝对的偏序时间 , 是用你的时序ID , 对于同一个请求01234来做时间 , 同一个请求的时间偏序 。
3)深度ID
同理 , 深度ID也是完全相同的 。 比如说这一层它有一个深度ID是1 , 一旦往下调用 , 深度ID变成2 , 成第二层 , 往下调 , 深度变成3 , 但一旦请求调回来 , 深度ID则变成2 , 再调回 , 深度ID变成1 , 这个就是持续ID的特点 , 它只增不减 。 持续ID是1、2、3 , 深度ID4往下一层调用会涨 , 返回则会减 , 所以通过三个自有协议的字段增加 , 可以解决刚才说的难点 。
5、分布式调用链追踪系统的数据收集
如何收集数据和展现数据?收集数据常见的方式有两种 , 实时的和相对异步的 , 根据数据量大小来用 。 我们公司比较常见有这两种方式:

快狗打车CTO沈剑:低成本搞定分布式调用链追踪系统
文章图片
第一种UDP上报 , 每一个要收集数据的点 , 签一个UDP的SDK , 然后往UDP的Server去发数据 。 在机房内网UDP的可靠性还是不错的 , 而且性能是比较高的 。 然后UDP收集到日志数据之后 , 再往日志中心或者叫搜索中心去做二次同步 , 然后后台要做检索 , 可以把它存到ES里 , 然后用这个调用链追踪后台 , 调相关的服务去做数据可视化;收集数据的第二种方式 , 对原有的架构体系的冲击会比较小 , 也是有一个SDK , 嵌入到我们的框架 , 我们的组件的某些需要收日志的地方 , 先打本地日志 , 先落盘 , 然后通过一个异步的 , 将日志再同步到我们的调用链追踪系统的后台系统里面 。 如果要支持检索的话 , 落到到ES里 , 后台来查询 。 这个数据的收集无非是这两种方式 , 我们用的是这种方式 , 先打本地日志 , 对性能的影响也会比较小 。
快狗打车CTO沈剑:低成本搞定分布式调用链追踪系统
文章图片
最后是要修改哪些地方?其实上文也基本上说了 , 如果我们的框架统一 , 组件统一 , 只要修改这10个地方 , 每个地方加1到2行代码就可以解决了 。 刚刚说不管是站点框架还是RPC的框架 , 在请求进来和请求出去的时候 , 会记时间 , 做时间差 , 传递参数 , 上报日志 。 在缓存组件开启Client的组件 , 发请求和回请求的两个地方 , 要记做时间差 , 以及要上报缓存访问的Key 。 数据库调用的发出请求、收到请求的地方要做时间差 , 并且上报所有相关的信息 。 RPCClient和RPCServer在发出请求 , 收到请求时 , 要上报时间以及上报占用函数以及函数传递的参数 。 为什么Client和Server也要收集?因为对于RPC调用来说有客户端视角和服务端视角 , 有时候有些请求的性能在网络上 , 我们客户端视角看的是100毫秒 , 服务端的视角会同一个请求是80毫秒 , 很有可能网络上爆了20毫秒 , 可能网络是主要矛盾 。