bilibili 毛剑 - B站微服务链路监控实践

郝乐生

2017/12/31 发布于 技术 分类

B站链路监控参考的是Google Dapper论文实现,在复杂的微服务进程中,如何和各个系统联动完成完整的监控支撑,甚至是更复杂的应用的呢,尽情期待

文字内容
1. bilibili分布式链路监控 毛剑 bilibili研发总监 2017.thegiac.com www.top100summit.com
2. 设计目标 • 无处不在的部署 • 持续的监控 • 低消耗 • 应用级的透明 • 延展性 • 低延迟 2017.thegiac.com
3. 分布式跟踪 参考Google Dapper 论文实现,为每 个请求都生成一个全局唯一的traceid, 端到端透传到上下游所有节点,每 一层生成一个spanid,通过traceid将不 同系统孤立的调用日志和异常信息 串联一起,通过spanid和level 表达节 点的父子关系; 核心概念: • Tree • Span • Annotation 2017.thegiac.com
4. 跟踪树 在跟踪树结构中,树节点是整个架 构的基本单元,而每一个节点又是 对span的引用。虽然span在日志文件 中只是简单的代表span的开始和结束 时间,他们在整个树形结构中却是 相对独立的; 核心概念: • TraceID • SpanID • ParentID • Family & Title 2017.thegiac.com
5. 跟踪Span • 追踪信息包含时间戳、事件、方法名(Family+Title)、注释(TAG/Comment) • 客户端和服务器上的时间戳来自不同的主机,我们必须考虑到时间偏差, RPC客户端发送一个请求之后,服务器端才能接收到,对于响应也是一样的 (服务器先响应,然后客户端才能接收到这个响应)。这样一来,服务器端 的RPC就有一个时间戳的一个上限和下限 2017.thegiac.com
6. 接口抽象 2017.thegiac.com
7. 跟踪植入点 Dapper可以以对应用开发者近乎零 浸入的成本对分布式控制路径进行 跟踪,几乎完全依赖于基于少量通 用组件库的改造。如下: • 当一个线程在处理跟踪控制路径 的过程中,Dapper把这次跟踪的 上下文的在ThreadLocal中进行存 储,在Go语言中,约定每个方法 首参数为context(上下文) • 覆盖通用的中间件&通讯框架、不 限于:redis、memcache、rpc、 http、database、queue 2017.thegiac.com
8. 跟踪的收集 2017.thegiac.com
9. 收集优化&存储 • tarce对象使用对象池分配,减轻GC压力; • 使用异步队列方式发送采样信息,在consumer中聚合后定时或者定量发送, 提升吞吐,减少系统调用; • HBase存储所有采样的全量数据traceid作为rowkey、每一个span存储为 column; • ES为主要字段建立索引,索引文件按天存储,family和title单独索引、用户 依赖关系存储; 2017.thegiac.com
10. 跟踪采集Agent 客户端通过unixsockt的进行异步发送 trace信息给agent,宿主机上面部署 一个collect 日志收集进程,监听本地 sock文件,共享给容器或者进程,聚 合收集日志,流程: • collect 将收集到的日志批量写入 磁盘,以时间戳作为文件名,存 储为固定大小的小文件; • agent 与service建立tcp连接,读取 相应路径下面的所有日志文件, 按照先后顺序发送到服务器端, 每10秒将读取位置写入索引文件。 读取完成并删除日志文件; 2017.thegiac.com
11. 跟踪损耗 • 处理跟踪消耗:1. 正在被监控的系统在生成追踪和收集追踪数据的消耗导 致系统性能下降,2. 需要使用一部分资源来存储和分析跟踪数据: 1. 是Dapper性能影响中最关键的部分,因为收集和分析可以更 容易在紧急情况下被关闭,ID生成耗时、创建Span等; 1. 修改agent nice值,以防在一台高负载的服务器上发生cpu竞争; • 采样:如果一个显着的操作在系统中出现一次,他就会出现上千次,基于 这个事情我们不全量收集数据,通过模型来预估真实情况,Reference: Uncertainty in Aggregate Estimates from Sampled Distributed Traces 2017.thegiac.com
12. 跟踪采样 • 固定采样,1/1024: 这个简单的方案是对我们的高吞吐量的线上服务来说是非常有用,因为那些 感兴趣的事件(在大吞吐量的情况下)仍然很有可能经常出现,并且通常足以 被捕捉到。然而,在较低的采样率和较低的传输负载下可能会导致错过重要 事件,而想用较高的采样率就需要能接受的性能损耗。对于这样的系统的解 决方案就是覆盖默认的采样率,这需要手动干预的,这种情况是我们试图避 免在dapper中出现的; • 应对积极采样: 我们理解为单位时间期望采集样本的条目,在高QPS下,采样率自然下降, 在低QPS下,采样率自然增加;比如1s内某个接口采集1条; 2017.thegiac.com
13. 跟踪采样 • 二级采样: 容器节点数量多,即使使用积极采样仍然会导致采样样本非常多,所以需要 控制写入中央仓库的数据的总规模,利用所有span都来自一个特定的跟踪并 分享同一个跟踪ID这个事实,虽然这些span有可能横跨了数千个主机。对于 在收集系统中的每一个span,我们用hash算法把跟踪ID转成一个标量Z,这里 0<=Z<=1,我们选择了运行期采样率,这样就可以优雅的去掉我们无法写入 到仓库中的多余数据,我们还可以通过调节收集系统中的二级采样率系数来 调整这个运行期采样率,最终我们通过后端存储压力把策略下发给Agent采集 系统,实现精准的二级采样; • 下游采样: 越被依赖多的服务,网关层使用积极采样以后,对于Downstream的服务采样 率仍然很高,我们会结合第二篇论文来解决,目前TODO中; 2017.thegiac.com
14. API • 搜索: 按照Family(服务名)、Title(接口)、时间、调用者等维度进行搜索,依 据Cost Metric(如:时间成本)进行排序,列出被采集的样本的基本信息、耗 时占比等; 2017.thegiac.com
15. API • 详情: 根据单个traceid,查看整体链路信息,包含span、level统计,span详情,依 赖的服务、组件信息等; 2017.thegiac.com
16. API 2017.thegiac.com
17. API 2017.thegiac.com
18. API • 依赖: 由于服务之间的依赖是动态改变的,所以不可能仅从配置信息上推断出所有 这些服务之间的依赖关系,能够推算出任务各自之间的依赖,以及任务和其 他软件组件之间的依赖。 2017.thegiac.com
19. API • 依赖搜索: 搜索单个服务的依赖情况,方便我们做“异地多活”时候来全局考虑资源的 部署情况,以及区分服务是否属于多活范畴,也可以方便我们经常性的梳理 依赖服务和层级来优化我们的整体架构可用性; 2017.thegiac.com
20. API • 推断环依赖: 一个复杂的业务架构,很难避免全部是层级关系的调用,但是我们要尽可能 保证一点:调用栈永远向下,即:不产生环依赖; 2017.thegiac.com
21. API • 服务元数据: 我们发现,服务内部的HTTP、RPC、Comment信息全部可以自动根据Dapper API生成,因此我们不再开发针对框架层的服务树元数据注册,依赖这些信息, 很容易把监控系统串联的很好、以及运维内部的服务树系统; 2017.thegiac.com
22. 链路系统经验 • 性能优化: 1、不必要的串行调用;2、缓存读放大;3、数据库写放大;4、服务接口聚 合调用; • 异常日志系统集成: 如果这些异常发生在Dapper跟踪采样的上下文中,那么相应的traceid和 spanid也会作为元数据记录在异常日志中。异常监测服务的前端会提供一个 链接,从特定的异常信息的报告直接导向到他们各自的分布式跟踪; • 用户日志集成: 在请求的头中返回traceid,当用户遇到故障或者上报客服我们可以根据 traceid作为整个请求链路的关键字,再根据接口级的服务依赖接口所涉及的 服务并行搜索ES Index,聚合排序数据,就比较直观的诊断问题了; 2017.thegiac.com
23. TODO • 容量预估: 根据入口网关服务,推断整体下游服务的调用扇出来精确预估流量再各个系 统的占比 • 网络热点&易故障点: 我们内部RPC框架还不够统一,以及基础库的组件部分还没解决拿到应用层 协议大小,如果我们收集起来,可以很简单的实现流量热点、机房热点、异 常流量等情况。同理容易失败的span,很容易统计出来,方便我们辨识服务 的易故障点; • opentraceing: 标准化的推广,上面几个特性,都依赖span TAG来进行计算,因此我们会逐 步完成标准化协议,也更方便我们开源,而不是一个内部“特殊系统”; 2017.thegiac.com
24. 扫码关注GIAC公众号 2017.thegiac.com www.top100summit.com