饿了么EMonitor演进史

今日分享开始啦,请大家多多指教~

序言
时间回到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”后,吸取众家之所长,在两个多月的爆肝开发后,推出了初代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。

指标存储:LinDB 1.0

应用指标的存储是一个典型的时间序列数据库的使用场景。根据我们以前的经验,市面上主流的时间序列数据库-- OpenTSDB、InfluxDB、Graphite–在扩展能力、集群化、读写效率等方面各有缺憾,所以我们选型使用RocksDB作为底层存储引擎,借鉴Kafka的集群模式,开发了饿了么的时间序列数据库–LinDB。

指标采用类似Prometheus的“指标名+键值对的Tags”的数据模型,每个指标只有一个支持Long或Double的Field。某个典型的指标如:

COUNTER: eleme_makeorder{city="shanghai",channel="app",status="success"} 45

我们主要做了一些设计实现:

• 指

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值