美团可视化全链路日志追踪实践
背景
业务系统日益复杂
随着互联网产品的快速发展,不断变化的商业环境和用户诉求带来了纷繁复杂的业务需求。业务系统需要支撑的业务场景越来越广、涵盖的业务逻辑越来越多,系统的复杂度也跟着快速提升。与此同时,由于微服务架构的演进,业务逻辑的实现往往需要依赖多个服务间的共同协作。
业务追踪面临挑战
业务系统往往面临着多样的日常客诉和突发问题,"业务追踪"就成为了关键的应对手段。业务追踪可以看做一次业务执行的现场还原过程,通过执行中的各种记录还原出原始现场,可用于业务逻辑执行情况的分析和问题的定位。
目前在分布式场景下,业务追踪的主流实现方式包括两类,一类是基于日志的ELK方案,一类是基于单次请求调用的会话跟踪方案。然而随着业务逻辑的日益复杂,上述方案越来越不适用于当下的业务系统。
传统的ELK方案
在分布式系统中,ELK技术栈已经成为日志收集和分析的通用解决方案。传统的ELK方案需要开发者在编写代码时尽可能全地打印日志,再通过关键字段从ES中搜集筛选出与业务逻辑相关的日志数据,进而拼凑出业务执行的现场信息。然而该方案存在以下痛点:
- 日志搜集繁琐: 日志数据往往是缺乏结构性的文本段,很难快速完整地搜集到全部相关的日志
- 日志筛选困难: 不同业务场景之间存在重叠,重叠逻辑打印的业务日志可能相互干扰
- 日志分析耗时: 搜集到的日志只是一条条离散的数据,需要人工串联分析还原现场
分布式会话跟踪方案
分布式会话跟踪的理论知识由Google在2010年《Dapper》论文中发表,随后Twitter开发出了开源版本Zipkin。市面上的同类型框架都是通过一个分布式全局唯一的id(即traceId),将分布在各个服务节点上的同一次请求串联起来。
分布式会话跟踪的主要作用是分析分布式系统的调用行为,并不能很好地应用于业务逻辑的追踪,主要体现在:
- 无法同时追踪多条调用链路: 分布式会话跟踪仅支持单个请求的调用追踪,不同链路之间相互独立
- 无法准确描述业务逻辑的全景: 调用链路只包含单次请求的实际调用情况,部分未执行的调用以及本地逻辑无法体现
- 无法聚焦于当前业务系统的逻辑执行: 覆盖了单个请求流经的所有服务,包含众多下游服务的内部调用
总结
无论是传统的ELK方案还是分布式会话跟踪方案,都难以满足日益复杂的业务追踪需求。本文希望能够实现聚焦于业务逻辑追踪的高效解决方案,将业务执行的日志以业务链路为载体进行高效组织和串联,并支持业务执行现场的还原和可视化查看。
可视化全链路日志追踪
设计思路
可视化全链路日志追踪考虑在前置阶段,即业务执行的同时实现业务日志的高效组织和动态串联,此时离散的日志数据将会根据业务逻辑进行组织,绘制出执行现场。
问题1: 如何高效组织业务日志?
新方案对业务逻辑进行了抽象,定义出业务逻辑链路:
- 逻辑节点: 业务系统的众多逻辑可以按照业务功能进行拆分,形成一个个相互独立的业务逻辑单元,可以是本地方法也可以是RPC等远程调用方法
- 逻辑链路: 每个业务场景对应一个完整的业务流程,可以抽象为由逻辑节点组合而成的逻辑链路
问题2: 如何动态串联业务日志?
由于逻辑节点之间往往通过MQ或者RPC进行交互,新方案可以采用分布式会话跟踪提供的分布式参数透传能力实现业务日志的动态串联:
- 通过在执行线程和网络通信中持续地透传参数,实现在业务逻辑执行的同时,不中断地传递链路和节点的标识,实现离散日志的染色
- 基于标识,染色的离散日志会被动态串联至正在执行的节点,逐渐汇聚出完整的逻辑链路
与分布式会话跟踪方案不同的是,当同时串联多次分布式调用时,新方案需要结合业务逻辑选取一个公共id作为标识。
通用方案
链路定义
"链路定义"的含义为: 使用特定语言,静态描述完整的逻辑链路。本方案选择使用DSL描述逻辑链路:
json
[
{
"nodeName": "A",
"nodeType": "rpc"
},
{
"nodeName": "Fork",
"nodeType": "fork",
"forkNodes": [
[{"nodeName": "B", "nodeType": "rpc"}],
[{"nodeName": "C", "nodeType": "local"}]
]
},
{
"nodeName": "Join",
"nodeType": "join",
"joinOnList": ["B", "C"]
},
{
"nodeName": "D",
"nodeType": "decision",
"decisionCases": {
"true": [{"nodeName": "E", "nodeType": "rpc"}]
},
"defaultCase": [{"nodeName": "F", "nodeType": "rpc"}]
}
]
链路染色
"链路染色"的含义为: 在链路执行过程中,通过透传串联标识,明确具体是哪条链路在执行,执行到了哪个节点。
- 步骤一: 确定串联标识
- 链路唯一标识 = 业务标识 + 场景标识 + 执行标识
- 节点唯一标识 = 链路唯一标识 + 节点名称
- 步骤二: 传递串联标识,在分布式的完整链路中透传串联标识,动态串联链路中已执行的节点
链路上报
上报的日志数据包括: 节点日志(记录节点的开始、结束、输入、输出)和业务日志(记录对业务逻辑起到解释作用的数据,包括与上下游交互的入参出参、中间变量、异常)。
链路存储
上报日志可以拆分为三类:
- 链路日志: 链路类型、链路元信息、链路开始/结束时间等
- 节点日志: 节点名称、节点状态、节点开始/结束时间等
- 业务日志: 日志级别、日志时间、日志数据等
存储模型是树状结构,业务标识作为根节点,用于后续的链路查询。
大众点评内容平台实践
业务特点与挑战
点评内容平台作为内容流水线的"治理方",承上启下实现了内容的统一接入、统一处理和统一输出:
- 统一接入: 统一内容数据模型,将异构的内容转化为平台通用的数据模型
- 统一处理: 积累并完善通用的机器处理和人工运营能力,保证内容合法合规
- 统一输出: 为下游提供规范且满足其个性化需求的内容数据
业务追踪建设主要面临的困难:
- 业务场景多: 涉及实时接入、人工运营、分发重算等多个业务场景
- 逻辑节点多: 不同内容类型节点各异
- 触发执行多: 不同来源触发,逻辑存在差异
实践
日志上报架构: log_agent + Kafka + Flink + HBase
- 日志收集: 各应用服务通过log_agent收集异步上报的日志数据,统一传输至Kafka
- 日志解析: 通过Flink统一进行解析和处理,解析为链路日志、节点日志和业务日志
- 日志存储: 选择HBase作为存储选型,适合高流量的数据写入
TraceLogger工具包: 模仿slf4j-api对外提供相同的API,屏蔽内部细节,实现众多服务改造的成本最小化
业务日志上报:
java
// 替换前:原日志上报
LOGGER.error("update struct failed, param:{}", GsonUtils.toJson(structRequest), e);
// 替换后:全链路日志上报
TraceLogger.error("update struct failed, param:{}", GsonUtils.toJson(structRequest), e);
节点日志上报:
java
public Response realTimeInputLink(long contentId) {
// 链路开始:传递串联标识
TraceUtils.passLinkMark("contentId_type_uuid");
// 本地调用(API上报节点日志)
TraceUtils.reportNode("contentStore", contentId, StatusEnums.RUNNING);
contentStore(contentId);
TraceUtils.reportNode("contentStore", structResp, StatusEnums.COMPLETED);
// 远程调用
Response processResp = picProcess(contentId);
}
// AOP上报节点日志
@TraceNode(nodeName="picProcess")
public Response picProcess(long contentId) {
TraceLogger.warn("picProcess failed, contentId:{}", contentId);
}
成果
可视化全链路日志追踪系统已经成为点评内容平台的"问题排查工具",问题排查耗时从小时级降低到5分钟内;同时也是"测试辅助工具",明显提升了RD自测、QA测试的效率。
优点总结:
- 接入成本低: DSL配置配合简单的日志上报改造,即可快速接入
- 追踪范围广: 任意一条内容的所有逻辑链路,均可被追踪
- 使用效率高: 管理后台支持链路和日志的可视化查询展示
总结与展望
随着分布式业务系统的日益复杂,可观测性对于业务系统的稳定运行愈发重要。点评内容平台在日志(Logging)、指标(Metrics)和追踪(Tracing)三个方向上都进行了一定的探索和建设。
"可视化全链路日志追踪"结合日志与追踪,通过在业务执行阶段结合完整的业务逻辑动态完成日志的组织串联,替代了传统方案低效且滞后的人工日志串联,最终实现了业务全流程的高效追踪以及业务问题的高效定位。
未来,点评内容平台会持续深耕,实现覆盖告警、概况、排错和剖析等功能的可观测体系。