Airbnb 的核心日志系统架构及主要系统模块的设计之道

来源:互联网 发布:淘宝客平台源码 编辑:程序博客网 时间:2024/06/03 21:30

随着业务的快速增长,传统的基于批处理模式和无格式的日志处理已经逐渐不能满足应用需求。因此,Airbnb 数据基础构架组开发了新一代的日志数据存储和查询平台,着重于保证日志数据的质量,解决数据的实时性,提高查询的灵活性,方便多维度统计分析,和提供异常检测。演讲主要分享 Airbnb 核心日志平台的系统构架,以及主要系统模块的设计和实现。

背景介绍

什么是日志平台系统?首先我们知道大数据对于互联网公司有非常重要的价值,对 Airbnb 也不例外。我们通过数据可以为客户提供最佳的旅行体验,同时我们也可以通过数据发掘用户的需求、市场需求,对我们的产品做出正确的决策。

这里是一些很简单、很具体的应用。首先通过数据可以进行反欺诈,让我们的平台变得更加可靠、值得信赖,同时通过数据可以找到我们所需要的房源,通过分析用户的相关行为,我们可以做到用户的拉新、促或、匹配,我们也可以做 AB 测试,可以通过数据决定新的产品或者功能是不是对业务起到一定的帮助,同时数据可以用来做监测。这是产品和数据仓库的关系,这么一个简单的从产品到数据仓库的纽带,就是日志的收集系统。

说到 Airbnb 的平台系统,在开发和完善过程中也踩到了很多坑,取得了很多进步。这是大概两年前的状态,使用的是无格式的 JSON 日志,我们有超过 800 种不同的日志记录类型,由于产品迭代和代码改动,没有约束所以非常容易出错,缺乏监控手段,保证整个日志平台的可靠性。

这是一些例子,在左边我们通过数据产生一些图表,这些图表由于整个数据平台系统的不可靠,经常会不显示或者数据比较陈旧。另外,右边有一个白板,我们项目经理会负责一个白板,写着有多少天没有产生数据,数据平台运行了,自从建立这个白板之后数字没有超过 10。

这也是真实的邮件,是某个组的项目经理发给他们全组的员工,说 dashboard 图表大家不要去相信,因为数据更新地不够快,或者数据是不准确的。

我们可以看到,大数据领域,数据的收集处理的平台对大数据和整个公司的数据驱动是非常重要的。

日志平台的需求

对于这么一个日志系统的平台有哪些需求呢?我们发现,第一个是数据的时效性,数据必须按照一定的时间落盘,有一定的可预测性。第二个,数据的完整性,就是说我们数据不能丢失,不能损坏,也不能够重复。第三个,保证数据的质量,就是说数据不能是无效的数据,如果数据没有办法反序列化,或者数据丢失了必须的字段,我们后续的处理就没有办法进行。

Airbnb 日志平台构架

我们看一下整个 Airbnb 日志平台大体构架。首先我们有一些前端的应用,比如网页和手机的 APP,这是我们的客户端。我们整个服务器是构建在 Ruby 上的,所以有 Ruby 的服务集群,同时还有很多内部产品的集群。所有的集群能够产生数据,数据或者直接发送到 Kafka,或者通过一些代理网关发生了 Kafka,Kafka 是整个数据消息的中间件。会有一些作业定期地去消费这些消息、这些日志,我们使用 Camus,是以离线或者批处理的方式把 Kafka 中间的数据放在我们的数据仓库,上面我们用 Hive 或 Presto 进行查询。更重要的一环是,收集完这些数据之后,把数据通过一个派生数据库,得到相关的数据产品,用于提高和改善我们前端的服务。这就是数据收集并回馈到产品的过程。

在这么一个比较复杂的框架下有很多组件会发生错误。首先,进程死锁,会丢失数据,或者 Kafka 代理会产生故障造成数据丢失。因为网页和手机都是运行在客户端的,会产生一些无效的数据,或者恶意的不能够反序列化的数据。我们发也现很多软件 Bug,这些软件 Bug 也会造成数据的丢失。同时服务器节点故障,比如硬盘、内存和网络的故障会导致数据不能够按时的或者不能进入数据系统。还有做一些代理会缓存配置,缓存配置如果没有正常的数值,也会造成缓存溢出,数据丢失。

考虑到整个系统之后,我们希望去改善或者去提高日志收集的平台,我们发现有五个方面可以改进:

  • 首先,我们需要模块监测,作为整个数据平台中间,每个模块需要保证它的正确性和可靠性;

  • 第二点,尽管系统中间的每个模块都正确运行了,但是也不能保证系统端到端的可靠性,所以还需要一个端到端的审核机制;

  • 第三点,发现如果日志的格式没有一定约束的话,会很容易产生非常多的无效日志,不能保证数据质量,因此我们对日志的格式也应该有一定的约束性和一定的强制性;

  • 第四点,整个系统出现异常的情况下,怎么样能够快速地或者自动地去检测到这个异常,所以需要一个异常检测的模块;

  • 最后一点,现在越来越多的系统已经不能满足批处理和离线的方式,在我们的日志平台中间也需要引入实时流处理的模块,使得日志能够更快地让用户查询或者进行一些数据聚合操作。

下面分别介绍一下每个方向的工作。

模块检测

首先,模块故障的监控,这个是比较直接的,对于日志系统中间的每个模块需要保证正确性和可靠性。比如进程的监测,进程是否活着,对于 CPU 和内存的使用量。因为日志系统有数据的输出和输入,对比数据的输出和输入帮助我们发现系统的故障。很多时候,比如对 Airbnb 网站来说,数据是有季节性的,比如说每周一、每周二我们平台的数据量会上升,到了周末数据量会下降,因此对数据会有一个季节性的对比,把这周一的数据和上周一的数据对比看有没有差别,如果差别很大看系统是不是有一定的错误。有了这些基本的模块级别指标之后就可以建立预警机制,发生错误的时候可以告知,检查系统错误。

端到端审核

除了模块级别的监测之外我们还要考虑端到端的审核制度。首先,我们监测每个模块不足以保证整个系统的可靠性,比如有些错误是未知的,没有看到过所以目前的监测手段会失效,因为我们不知道监测怎么样的指标去发现错误。第二个,需要量化整个系统的可靠性。第三个,在发生错误的时候要尽快定位到错误发生的模块。第四个,需要除了应用系统本身的指标去监测系统之外,还希望有一些第三方的数据或者带外的形式,和日志系统的指标进行对比,发现系统的错误。

基于这几点我们主要提供了心跳日志和数据库更新日志。心跳日志就是一种虚假的或者人工的日志,能够定期向不同的服务发送,按照一定的速率,通过在后端数据库里面聚合这些心跳日志,就知道整个平台是不是产生了数据丢失,这个方法非常简单、有效、可靠。另外,我们可以通过一些数据库的更新日志,比如订单日志,每次订单产生都会对数据库产生一次更新,更新的次数会被统计出来,根据更新的次数对比在后台数据操作中订单日志的差别,我们可以发现整个系统日志平台是不是可靠,数据库的更新日志提供一种带外的方式检查整个平台的可靠性。

对于整个平台端到端的日志审核,主要是在日志中添加一些辅助的信息,比如日志 ID(唯一识别的 ID)、主机名称、进程号、序列号、时间戳等等,有了这些辅助信息之后就可以量化数据平台的可靠性。同时可以计算,日志在整个系统平台中间每一跳的可靠性。

这是一个简单的例子,在每个日志中间需要添加这些信息,比如数据的类型、数据唯一的标识,数据在整个流程中会经过节点,每个节点会有一些类型、主机号、IP 地址、时间戳、序列号等等。

日志格式

有了端到端的保证和模块级别监控之后,依然不能保证整个系统的可靠性,是因为日志的格式还没有得到保证,会产生一些错误的日志格式。

回顾一下两年前使用的无效日志,我们有超过 800 种的日志类型,容易出错,缺乏很多监测,导致数据事故和数据丢失,也对整个公司和数据平台造成不信任。

这是真实的邮件,每次产生数据事故之后都会发报告,告诉大家我们在代码重构中不小心丢失了日志中间的某个域,导致数据错误或不能被使用,甚至完全把某一种日志丢掉了。

针对这种情况我们就想到了需要对日志的格式进行约束,我们选择了 Apache Thrift,是与语言无关的格式定义语言,有丰富的客户端,支持 Java、Ruby、JS、Mobile 的开发端。这个语言本身比较简单、容易理解,有助于让数据科学家和产品工程师共同去定义这种日志格式,因为日志不光是要发送,还要被数据科学家所理解去处理,所以我们觉得 Apache Thrift 能够满足这样的协议。同时我们定义了标准的开发流程,让大家遵照这个开发流程。

为什么选择 Thrift 呢?第一是语法比较直观,非工程师也容易理解。第二点,它在 Ruby 环境中有良好的性能。同时日志格式信息和开发文档统一存取。第四,我们也有严格的版本管理信息,比如日志是前项还是后项的兼容性,在日志的进化当中,可以保证日志不会破坏后台的处理。同时我们也给出了相应的统一发布流程,可以发布 Jar、Gem 文件,使得后端服务和客户端可以使用这样的日志模式。

这是简单的例子,我们定义了这么一个日志格式,在 Java 和 Ruby 语言当中,有一些 field 必须在日志中间,还有一些 field 是可选的。

这是在 JS 当中有这样的格式之后,开发过程中如果忽略了一些必须的 field,开发时候发现错误及时更正,避免错误。

这是有了日志格式之后的开发流程。首先要定义格式,数据科学家和工程师共同定义这个格式,然后通过统一的发布流程,把数据日志发送到服务器或者客户端,客户端或服务器就会用这个日志格式产生日志,通过相应的流处理应用,将日志发送到数据仓库,最后在数据仓库中间对日志进行丢失检测、异常查询。

我们也开发一些前端的应用,使得用户能够非常方便地通过关键字查找日志,并且日志格式提供了一些元数据,如日志的作者、版本等等。同时我们也做一些简单代码的模板生成或者查询语言生成,比如给定日志之后可以对不同语言类型产生代码样板,在不同语言环境下简单地使用日志。

实时流处理

上面提到的三点就是模块级别的监测、端到端的审核和日志的格式。现在越来越多的应用需要实时的处理,因此在日志系统中间加入了实时流处理。

回顾一下之前整个日志的框架。有前端的移动端和服务,日志通过代理服务器或者直接进入 Kafka 总线。引入了流处理之后发生了大变化,通过 AirStream 这个流处理平台,数据实时地被 AirStream 处理,放入 Hbase,Hbase 是现在使用的存储方案,Druid 是多维度统计的存储时间序列的平台。我们有定时的作业可以把 Hbase 或实时的作业批量放到 Hbase 数据仓库中间,同时我们开发了 Presto 在 Hbase 上面的接口,使得用户可以用 Presto 对这些实时的数据进行查询。在反馈流程上面,除了原来的批处理反馈同时也引入了实时的反馈,可以将实时的信号反馈给产品服务器,使它能够做出更好的改进。

这是实时处理的一些重要的部件。第一基于 Spark Streaming 对实时数据进行了实时注入。第二我们使用了 HBase 对数据进行去重,并且按照数据类型进行分片,同时开发了 Hive/Presto 接口访问 Hbase,提供日志的实时查询,同时我们对数据进行实时多维度聚合的操作。

这是我们的一个界面,当数据注入之后,可以对数据进行一些维度上的分析。这是简单查询的例子,图的右边是一些维度的定义,比如查询来自于哪个平台,它的查询语言是什么,它来自于哪个页面,在这个例子当中看到对于这个查询日志数据来说来自于 iOS 平台、英语国家数量是最多的。

整个界面也是我们自主开发的数据可视化的模块,叫做 Superset,在 GitHub 上已经开源了,可以支持很多数据源,比如我们这里用的数据源,可以把实际数据发掘出来显示在图形上,同时有许多其他的数据源支持,还支持各种不同的可视化模块,比如说支持这样持续的线状图,基于地图的可视化,或者一些热点图、饼状图等等,这是一个非常好用的图形可视化服务。

异常检测

最后一点,我们还需要对数据进行一些异常的检测。

这是一个非常实际的例子,比如在某年 9 月 22 日上线了日期的选择组件,右边是我们 Airbnb 的主页,主页的第一行给大家显示了需要搜索哪里的房源,入住日期和退房日期是怎样的,让用户快捷地定义这个组件。我们打开了 AB 测试,有 50% 的用户可以看到这个新的组件,50% 的用户还是使用旧的日期组件。一个星期之后,9 月 29 日实验结果表明新组件导致搜索下降 14%,这是个糟糕的结果,我们就下线了新组件,搜索指标恢复。

这个例子的目的在于,一旦发生了错误,如何分析原因?这是一个繁琐的过程,基于很多因素,依赖于经验、试错和运气,经常开发中遇到上线新的产品、新的功能之后,发现指标比如订单量、搜索量下降了,需要找到根源所在。对于具体的例子来说原因是,如果把搜索的下降量按照国家、地区分类、搜索来自的平台,比如是网页版或者手机版,并且把搜索的下降按页面来分类发现,原来在手机平台上、意大利语页面搜索下降是最多的,而其他平台或其他维度没有受到影响。我们最后发现原来是新的 JS 组件,在特定的环境下、特定的语言平台下有 Bug,导致整个数据搜索量的下降。

我们开发环境中经常碰到这样的问题,怎样不这么繁琐、费人工的查找问题?开发了一个 Curiosity 系统对于顶层聚合指标进行自动异常检测。如何判定异常检测的组合,我们有策略地尽可能去尝试各种不同的组合,比如对于任何一个顶层聚合指标来说,有很多维度,如搜索的国家、操作系统、渠道,每一个日志数据会有不同的维度,这些维度会产生成百上千的维度组合,如何在不同的维度组合下发现聚合指标产生异常呢?这就是 Curiosity 要解决的问题。分几个步骤:

  • 第一个,获取实际的数据源;

  • 第二个,可以想象成一个广度搜索的过程,对不同的维度组合进行广度搜索,每次给定一个维度,对这个维度进行两个方向的异常检测,一个是水平方向,一个是垂直方向。所谓水平方向,时间序列方向,比如搜索来自的语言,在这个时间轴方向是不是有大的波峰或者波谷,我们就认为产生了异常。垂直方向就是给定一个时间段,对维度不同的取值是否异常,比如在这个时间段内来自于一种语言的搜索比其他语言的搜索行为更加可疑,就是个异常;

  • 第三个,把水平异常检测结果和垂直异常检测结果结合起来进行裁剪,比如发现在水平维度上,只有英语国家产生了异常,就可以裁剪这个维度其他值,使得广度搜索不会指数级的增长。当我们对一个维度分析完毕之后,会把这个维度异常的值和下一个维度合并,进行下一个广度的搜索。

具体到实现,我们抽象了具体的设计接口,是因为虽然对于异常的检测,不同的系统需要不同的算法、不同的数据源,比如数据具有季节性,因此一个简单的检测波峰和波谷按照阈值的检测不一定能够有效,我们就会用其他的一些算法。所以在实现 Curiosity 的时候,每个模块都进行了接口化的设计,数据源的获取,水平异常检测,垂直异常检测,然后维度裁剪,广度搜索。对于每个接口我们有基本的实现,比如对于数据源实现了 Druid 数据源,对于检测我们实现了基本的算法,比如基于阈值或者 Kalman Filter,算法都是可定制化的。最后把广度搜索分片,基于 Spark 支持分布式运行,加快异常检测的过程。

异常检测的结果我们会把它以一个图形化的方式展示出来,展示的过程基于一篇论文。最上面是日志数据的维度信息,这是一个搜索的日志,它的维度有搜索来自的国家、搜索目标的国家、搜索来自的渠道和搜索使用的操作系统或者平台的信息。对于这些不同的维度来说,颜色的深浅表示这个维度可能发生异常的概率高低。

上面这个表格,垂直方向是搜索来自国家的维度,横向是一个时间轴,每一个单元格代表这个时间段内国家异常的状况,颜色越深代表异常越严重,比如法国人的搜索在 5 月 15 日产生了异常,所以网格是红色的。如果网格的边缘很宽,表示如果把这个维度定义为法国人搜索的维度之后,那么法国人在搜索的目标国家也会产生很大的异常的可能性,所以可以点击法国人的维度继续检测下一个异常发生的状况。

写在最后

最后总结一下,我们这个日志系统有几个特点:第一个,提供了系统级的检测和报警。第二个,可以量化整个平台的可靠性。第三个,对日志的格式进行规范,能够尽量减少无效性的数据。第四个,引入实时流处理日志能够实时查询。第五个,开发了日志异常检测服务,可以很快地检测出异常发生的状况。

推荐一个活动

【百度 AI 开发者大会】7 月 5 日,Baidu Create 2017 百度 AI 开发者大会将在北京国家会议中心举办。百度创始人、董事长兼首席执行官李彦宏,百度集团总裁兼首席运营官陆奇,将发布面向开发者和生态合作伙伴的重要计划。DuerOS 开放平台、Apollo 开放平台等百度 AI 生态重要战略、技术、业务进展、解决方案,也将首次面向开发者及各行业合作伙伴集中展现,释放生态势能。详情请戳 「 阅读原文 」

原创粉丝点击