
序言
时间回到 2008年,还在上海交通大学上学的张旭豪、康嘉等人在上海创办了饿了么,从校园外卖场景出发,饿了么一步一步发展壮大,成为外卖行业的领头羊。2017年8月饿了么并购百度外卖,强强合并,继续开疆扩土。2018年饿了么加入阿里巴巴大家庭,与口碑融合成立阿里巴巴本地生活公司。“爱什么,来什么”,是饿了么对用户不变的承诺。
饿了么的技术也伴随着业务的飞速增长也不断突飞猛进。据公开报道,2014年5月的日订单量只有 10 万,但短短几个月之后就冲到了日订单百万,到当今日订单上千万单。在短短几年的技术发展历程上,饿了么的技术体系、稳定性建设、技术文化建设等都有长足的发展。各位可查看往期文章一探其中发展历程,在此不再赘述:
而可观测性作为技术体系的核心环节之一,也跟随饿了么技术的飞速发展,不断自我革新,从“全链路可观测性 ETrace”扩展到“多活下的可观测性体系 ETrace”,发展成目前“一站式可观测性平台 EMonitor”。

EMonitor 经过 5 年的多次迭代,现在已经建成了集指标数据、链路追踪、可视化面板、报警与分析等多个可观测性领域的平台化产品。EMonitor 每日处理约 1200T 的原始可观测性数据,覆盖饿了么绝大多数中间件,可观测超 5 万台机器实例,可观测性数据时延在 10 秒左右。面向饿了么上千研发人员,EMonitor 提供精准的报警服务和多样化的触达手段,同时运行约 2 万的报警规则。本文就细数饿了么可观测性的建设历程,回顾下“饿了么可观测性建设的那些年”。

1.0:混沌初开,万物兴起
翻看代码提交记录,ETrace 项目的第一次提交在 2015年10月24日。而 2015年,正是饿了么发展的第七个年头,也是饿了么业务、技术、人员开始蓬勃发展的年头。彼时,饿了么的可观测性系统依赖 Zabbix、Statsd、Grafana 等传统的“轻量级”系统。而“全链路可观测性”正是当时的微服务化技术改造、后端服务 Java 化等技术发展趋势下的必行之势。
我们可观测性团队,在调研业界主流的全链路可观测性产品——包括著名的开源全链路可观测性产品“CAT”(https://github.com/dianping/cat)后,吸取众家之所长,在两个多月的爆肝开发后,推出了初代 ETrace。我们提供的 Java 版本 ETrace-Agent 随着新版的饿了么 SOA 框架“Pylon”在饿了么研发团队中的推广和普及开来。ETrace-Agent 能自动收集应用的 SOA 调用信息、API 调用信息、慢请求、慢 SQL、异常信息、机器信息、依赖信息等。下图为 1.0 版本的 ETrace 页面截图。

在经历了半年的爆肝开发和各中间件兄弟团队的鼎力支持,我们又开发了 Python 版本的 Agent,更能适应饿了么当时各语言百花齐放的技术体系。并且,通过和饿了么 DAL 组件、缓存组件、消息组件的密切配合与埋点,用户的应用增加了多层次的访问信息,链路更加完整,故障排查过程更加清晰。
整体架构体系
ETrace 整体架构如下图。通过 SDK 集成在用户应用中的 Agent 定期将 Trace 数据经 Thrift 协议发送到 Collector(Agent 本地不落日志),Collector 经初步过滤后将数据打包压缩发往 Kafka。Kafka 下游的 Consumer 消费这些 Trace数据,一方面将数据写入 HBase+HDFS,一方面根据与各中间件约定好的埋点规则,将链路数据计算成指标存储到时间序列数据库-- LinDB 中。在用户端,Console 服务提供 UI 及查询指标与链路数据的 API,供用户使用。

全链路可观测性的实现
所谓全链路可观测性,即每次业务请求中都有唯一的能够标记这次业务完整的调用链路,我们称这个 ID 为 RequestId。而每次链路上的调用关系,类似于树形结构,我们将每个树节点上用唯一的 RpcId 标记。

如图,在入口应用 App1 上会新建一个随机 RequestId(一个类似 UUID 的 32 位字符串,再加上生成时的时间戳)。因它为根节点,故 RpcId 为“1”。在后续的 RPC 调用中,RequestId 通过 SOA 框架的 Context 传递到下一节点中,且下一节点的层级加 1,变为形如“1.1”、“1.2”。如此反复,同一个 RequestId 的调用链就通过 RpcId 还原成一个调用树。
也可以看到,“全链路可观测性的实现”不仅依赖与 ETrace 系统自身的实现,更依托与公司整体中间件层面的支持。如在请求入口的 Gateway 层,能对每个请求生成“自动”新的 RequestId(或根据请求中特定的 Header 信息,复用 RequestId 与 RpcId);RPC 框架、Http 框架、Dal 层、Queue 层等都要支持在 Context 中传递 RequestId 与 RpcId。
ETrace API 示例
在 Java 或 Python 中提供链路埋点的 API:
/*
记录一个调用链路
/
Transaction trasaction = Trace.newTransaction(String type, String name);
// business codes
transaction.complete();
/*
记录调用中的一个事件
/
Trace.logEvent(String type, String name, Map<String,String> tags, String status, String data)
/*
记录调用中的一个异常
/
Trace.logError(String msg, Exception e)
Consumer 的设计细节
Consumer 组件的核心任务就是将链路数据写入存储。主要思路是以 RequestId+RpcId 作为主键,对应的 Data 数据写入存储的 Payload。再考虑到可观测性场景是写多读少,并且多为文本类型的 Data 数据可批量压缩打包存储,因此我们设计了基于 HDFS+HBase 的两层索引机制。

如图,Consumer 将 Collector 已压缩好的 Trace 数据先写入 HDFS,并记录写入的文件 Path 与写入的 Offset,第二步将这些“索引信息”再写入 HBase。特别的,构建 HBase 的 Rowkey 时,基于 ReqeustId 的 Hashcode 和 HBase Table 的 Region 数量配置,来生成两个 Byte 长度的 ShardId 字段作为 Rowkey 前缀,避免了某些固定 RequestId 格式可能造成的写入热点问题。( 因RequestId 在各调用源头生成,如应用自身、Nginx、饿了么网关层等。可能某应用错误设置成以其 AppId 为前缀 RequestId,若没有 ShardId 来打散,则它所有 RequestId 都将落到同一个 HBase Region Server 上。)
在查询时,根据 RequestId + RpcId 作为查询条件,依次去 HBase、HDFS 查询原始数据,便能找到某次具体的调用链路数据。但有的需求场景是,只知道源头的 RequestId 需要查看整条链路的信息,希望只排查链路中状态异常的或某些指定 RPC 调用的数据。因此,我们在 HBbase 的 Column Value 上还额外写了 RPCInfo 的信息,来记录单次调用的简要信息。如:调用状态、耗时、上下游应用名等。
此外,饿了么的场景下,研发团队多以订单号、运单号作为排障的输入,因此我们和业务相关团队约定特殊的埋点规则——在 Transaction 上记录一个特殊的"orderId={实际订单号}"的 Tag——便会在 HBase 中新写一条“订单表”的记录。该表的设计也不复杂,Rowkey 由 ShardId 与订单号组成,Columne Value 部分由对应的 RequestId+RpcId 及订单基本信息(类似上文的 RPCInfo)三部分组成。
如此,从业务链路到全链路信息到详细单个链路,形成了一个完整的全链路排查体系。

Consumer 组件的另一个任务则是将链路数据计算成指标。实现方式是在写入链路数据的同时,在内存中将 Transaction、Event 等数据按照既定的计算逻辑,计算成 SOA、DAL、Queue 等中间件的指标,内存稍加聚合后再写入时序数据库 LinDB。

从2015年至今,饿了么的可观测性系统从ETrace发展到EMonitor,历经多活架构、实时计算引擎、报警系统等关键演进,最终成为一站式可观测性平台。EMonitor整合了业务指标、应用链路、基础设施指标等多层次数据,提供了统一的可观测性体验。
最低0.47元/天 解锁文章
410

被折叠的 条评论
为什么被折叠?



