本文整理自火山引擎开发者社区 Meetup 第四期演讲,主要介绍了字节跳动流量平台的埋点内容解决方案和埋点链路解决方案,揭秘了流量平台如何支撑起字节跳动万亿+的实时数据处理。
首先我们定义一下埋点是什么?埋点主要是描述用户在 APP 内触发的一系列行为,包括点击、侧滑等。基于这些行为,我们可以进行行为分析、个性化推荐、精准营销等很多事情。埋点主要描述的是哪些数据?
Who:谁操作的数据 When:什么时候操作的数据 Where:在哪些页面、模块的数据 How:用户如何操作的 What:有哪些附加信息
因为本文介绍的是埋点治理,所以这里再介绍一下什么是数据治理。数据治理是指在数据的生命周期内,对其进行管理的原则性方法,其目标是为了确保数据的安全、及时、准确、可用和易用。数据总是会变得无效甚至无用,因此就涉及到对存量数据的治理。但这里要强调一下,数据治理不只针对存量数据,更重要的是对增量数据的治理,通过一系列手段,能保证数据从源头开始就是正确的。此外,所有的治理都有具体的落地内容,一个稳定的治理链路是所有数据治理的基石。
下面就为大家介绍字节跳动是如何治理埋点数据的。
字节跳动流量平台
流量平台是字节跳动内部统一的埋点平台,覆盖埋点数据定义、采集、生产、应用、治理等埋点全生命周期。当前,流量平台已经覆盖了 2000 多个应用,管理埋点(事件)数 20 万,每天产生的埋点数据量超过万亿,每年能给公司节省的成本超亿元。
上图是字节跳动流量平台的产品概念图,可以看到流量平台主要分为几块:
埋点内容:这是用户接触最多的一块,包括埋点生命周期的设计、开发、验证、上线、使用、乃至下线。 埋点治理:指的是狭义的对存量数据的治理。涉及成本、SLA 等的治理。 链路侧:包含埋点的收集、处理和订阅的全链路,平台目前支持对 iOS、Android 等全端数据的收集。平台已经跟下游使用流量数据的应用进行打通,用户可以订阅数据。 链路根基:即自研的动态实时计算平台,也是整个平台的核心技术,它能够支撑起字节跳动万亿+的实时数据的处理。 埋点内容解决方案
埋点内容主要管理埋点生命周期,这里要着重强调一下上图中心位置的埋点模型其实非常重要,因为埋点模型设计的好坏直接影响到埋点的设计、开发、测试甚至使用。
埋点内容用户痛点
埋点内容的用户主要是有两大类:埋点消费者和埋点生产者。对于埋点消费者来说,存在如下痛点:
查找难度大:埋点数量非常多,找不到自己想要的埋点。 使用难度高:找到埋点之后,用户也很可能不清楚指标埋点口径。 埋点难信任:当数据不符合预期时,不确定埋点数据是否可用。
对于埋点生产者来说,也有一些痛点:
生产链路长:各方信息对齐、流程推动难度大; 模型落地难:不知如何设计、不知是否符合规范; 缺乏工具支持:设计、开发、测试纯白手起家。
那怎么去解决用户的这些痛点呢?首先我们要弄清楚埋点的第一站是什么。很多公司都有埋点系统,对于大部分公司而言,埋点的第一站是埋点录入。但是大家会发现,埋点录入并不是一切的源头,埋点设计才是。埋点设计是第一手的资料,根据埋点的设计文稿可以将用户的需求梳理得非常细致。而埋点录入是第二手甚至是第 N 手资料,录入的信息肯定会有丢失,并且只能进行一些基本的校验,满足基本的准确性。
其次,如果没有资产的辅助设计,每一个埋点录入都要从 0 到 1 去实现一遍。但是埋点设计通过资产辅助设计可以变得很简单。因此,我们认为埋点设计才是 the single source of truth,这是我们整体设计的核心。下面来看看用户如何在我们的系统设计埋点。
字节跳动流量平台的产品辅助设计基于灵活的模型支持、设计资产积累、设计辅助,可以方便每一个用户定义高质量数据,让用户愿意在系统进行设计和评审。
设计完埋点,我们在埋点开发上也有对应的工具来服务来发。下面是我们的一段演示:
当用户要设计埋点时,可以通过 ID 找到要开发的埋点,通过点击即可插入代码。同时,系统支持 VSCode 等主流编辑器,针对不同语言和代码风格自定义代码模版,还有类型校验、编辑切换等。
埋点测试
埋点测试比 QA 要难很多,看的是一串数字、类型的值等。在字节跳动流量平台系统中,可以依托埋点设计中的规则辅助测试,针对类型、取值、必填等自动验证,并且可以一键生成报告。
我们是怎么去做好测试这件事的呢?重点还是前面提到的做好埋点设计。只有设计周全,才能积攒足够的规则进行自动化测试,因此埋点设计方案非常重要。
埋点设计者会在方案设计时制定一系列的约束规则,我们会依托这些约束规则生成一系列相匹配的测试用例,并在测试过程中进行自动匹配、测试。
埋点测试时,测试者手机扫码即可将服务器和浏览器建立连接,在 App 上操作后,流量平台可以实时接收到对应的埋点数据。因为已经有测试用例,规则执行引擎便可以自动匹配执行并得到结果,再通过验证结果推送服务实时推送至浏览器。
埋点测试后,用户可以通过报告生成器可以一键生成报告,发送给 RD 进行修改或者 DA 进行验收。这样就完成了整个测试流程。
埋点存量治理
在生成了大量的埋点之后,我们需要进行存量埋点数据的治理,具体涉及 SLA、成本、合规以及数据质量等方面。为什么还要进行存量埋点数据的治理呢?我们有这样一些观点:
数据不一定都是重要的。因为业务都不一定总是重要的。 数据并不总是有用的。比如活动下线了,埋点就要下线,否则付出了成本却没有收益。 数据不一定总是合规的。随着数据隐私的重要性越来越高,合规要求也在不断更新变动。
对于存量埋点数据的治理,也有一些痛点。对于治理负责方来说,数据越来越多,而对数据的实时性要求却越来越高;随着数据量暴增,成本也急剧增加,SLA 等级越来越慢;用户隐私也越来越重要。对于治理实施方来说,他们可能不敢治理,不愿治理,也不懂治理。
我们梳理了用户的需求,发现是这样几层:
用户层:自动化识别埋点是否有用,流程化引导埋点的分级和下线。 统计层:做到数字化、货币化,治理大盘清晰可知,埋点成本准确无误。 甄别层:通过的血缘图谱,对实时统计、离线报表、行为分析、推荐系统等做实时决策。 执行层:从 APP 端到数仓全流程,强兜底。 链路层:需要高效、稳定、完整的链路方案解决治理难题。
针对这些需求,我们是怎么做的呢?
埋点分级/无用埋点甄别
埋点血缘和离线血缘抽取不太一样。离线血缘是点与点之间的血缘,但埋点血缘关注的是内容与点的血缘,它需要知道一张表的哪些行的信息有用。这是完全不同的一个领域,没有任何前人的经验可以借鉴,我们在埋点血缘做了几个方面的事情:
离线处理:通过 SQL 解析,计算埋点与离线表的血缘; 实时处理:利用埋点链路优势,掌握实时分流血缘关系; 即时分析:与行为分析、SQL 临时查询系统打通; 推荐系统:利用数据清洗链路优势,解耦推荐血缘。
在埋点分级上,我们以性能埋点为突破口,给予匹配的 SLA 和 TTL 配置。
埋点链路解决方案
下面着重介绍一下我们的埋点链路解决方案。首先我们还是来看下埋点链路用户的需求是什么。对于非技术的运营、分析师同学,他们需要清楚自己:
需要什么样的数据:是埋点数据还是展现数据? 需要哪些数据:最好能对不同来源不同时效性的数据进行可视化过滤、清洗。 需要在哪里用这些数据:是实时报表,还是行为分析,还是推荐? 埋点链路的挑战
在埋点链路设计上我们也遇到了一些挑战:
大数据量下的稳定性:埋点数据在字节跳动是核心数据,其稳定性非常重要。 低延迟实时处理:尤其对于推荐,实时性要求非常高。
分级构建+下线。这里分几块内容:
数据接入:我们提供全栈 SDK 接入,还在 SDK 内置了管控的机制,利用各个 APP 内终端计算的能力,大大节省成本;而且根据合规要求不能上报的埋点就可以直接在 SDK 端丢弃掉; 数据收集:数据收集一般是提供 HTTP 接口,将上报的数据存到消息队列。而埋点数据量特别大,于是我们进行了埋点聚合,将埋点的 Event 数据聚合成 Applog 数据一起上报。数据进入到 Applog 后通过自研的实时数据处理平台来解析。 实时动态处理引擎
上图是我们自研的实时数据平台架构,该平台主要解决两方面的问题:
实时处理:快速处理大量数据; 动态化:字节跳动服务规模巨大,重启时间过长会导致下游断流,因此我们的实时领域里不接受重启。此外,逻辑有任何修改都要重启,在大量的业务和逻辑下这是不可能实现的。所以该平台一定要做到动态化。
动态实时处理引擎在收到实时数据的 Applog 后将其解析成真正的埋点数据。再通过数据加工,可以转换为其他的(甚至自定义的)数据格式,最后通过数据订阅推送到各应用。
第二种模式是分级。在埋点数据被解析以后,我们会打上标记,然后 dump 到 hdfs 不同的路径下。后续 Hive 进行构建的时候可以区分优先级,优先级高的进入高优队列。Hive 的数据也是分区的,分区的数据可以制定不同的 TTL。这样,数据的 TTL 和 SLA 就都能分级了。
第三是强保证。在埋点数据下线前,先将要下线的数据分流到 pre-discard Hive 表中暂存 30 天。如果在这段时间里没有问题,30 天之后就可以直接下线。
现在,该引擎的处理逻辑、拓扑、函数以及 RPC 都可以做到动态化。用户对于上游而言,一般是写 SQL 或者进行界面化操作。因为用户不懂如何处理,我们就需要特定的模型让用户进行适配。于是我们用声明式表达建立统一的逻辑模型让用户直接适配。在引擎上我们还能以插件化的形式支持 Flink、Pyjstorm、TCE 等多种运行时平台,业务方基于视图表达可以定制化支持业务场景。
Map 计算模型
下面介绍下该引擎的逻辑动态性。我们使用的是简单的 map 模型。
数据进来后判断是否是需要的,过滤清洗之后需要的数据进入下游,不需要的数据就丢弃掉。基于 Groovy 语言的热加载,将语言转换成可执行的逻辑。
拓扑重构
字节跳动的业务新增速度很快,我们希望新增业务下游后也不需要重启。对于业务方来说,用户只关心业务逻辑,运维关心底层稳定性和 Job 执行效率。但是在实际处理中,一个大的困境在数据源。我们以 Kafka 为例,每多一个消费者就多一份网络消耗和数据反序列化的计算成本,对 Kafka 的压力就越大。我们应对的方法原理其实很简单,即基于源数据集来进行重构。
相同 SLA 下的业务线,只要用了相同的 source,就可以把拓扑重构为新的模型。拓扑重构之后,用户侧无感知,SLA 也没有打破,但是效率确实成倍提升,而且对于上游 Kafka 的压力小了许多。
实时动态处理引擎整体架构
我们希望这个引擎是一个会变形的引擎。上游用户可以通过 SQL、图形化/界面化配置,我们可以根据 schema 产生的 catalog 生成一个通用的自定义逻辑规则。之后用户还会对逻辑规则进行修改,比如进行校验或函数重构,我们再会转换成用户的物理规则(physical rule),我们现在是使用 Groovy 进行转换。转换成物理规则之后,还有其他一些问题要处理。
首先是动态化,包括:
UDF 动态化:我们期望 UDF 改变也不用重启,所以 UDF 需要进行动态化编译。 拓扑重构动态化:重构之后拓扑改变,需要新的拓扑结构。 RPC 动态化:可以加载动态的函数。
这些配置更新以后,经过 Planbuilder 生成 JobGraph,引擎再拉取配置。
这时也有一个问题:我们的规则非常多,不能因为一条规则的更改就更新所有规则。所以我们做的是增量更新,只对有需要的规则进行更新。
引擎拉取之后,会加载新的资源(RPC、Schema),并进行拓扑重构以及编译。因为之前给到的是一些 Groovy 的代码片段,用户可以将其热编译为物理规则。
此外我们还做了很多细致的工作,例如 Object catch。举个例子:大部分埋点上报的是 String 格式的 Json 数据,用户在进行数据清洗时就需要将 String 反序列化为 Json object,如果用户在规则中多次用到该 Json object 就会导致多次反序列化计算。因此,我们将反序列化后的 object 进行缓存,这样再次使用时就可以直接使用,避免重复反序列化成本。
Q&A
Q:新增埋点之后不用重启,那多久会生效,如何保证生效?
A:我们对用户承诺的 SLA 是 2 分钟生效。因为实时动态引擎是动态获取数据的过程,可以更高频地感知变化。在变化完成之后,我们是增量修改,修改的频率也更低。
如何能保证新增埋点生效?前面提到了重视 SLA。SLA 不一样,资源的利用率也就不一样。此外埋点也不可能无限加,当资源利用率达到一定的阈值之后,就需要扩容。资源不足的问题没有办法解决,只能重启。
Q:资源是先申请到位再重启吗?
A:在我们这边是的,资源申请到位后才会进行对应的重启。同时我们的开发套件会进行增量重启,也就是不会一次把所有服务节点全部重启,一次只会对有问题的部分(比如 10% 的服务节点)进行重启,把限度降到最低。但是在大数据量下,重启执行的运维成本依然很高。
Q:平台上的埋点开发代码模版可以复用吗?
A:一般不会复用。不同语言的模版肯定不同,不同产品的工程团队风格要求也不一样,也需要定制,所以模版几乎不会被复用。
Q:埋点的丢失和重复上报的问题是怎么处理的?
A:对于丢失数据的处理分两个方面:
端上日志:上报数据失败后会进行重试,并且端上有监控,可以了解当前客户端上报的情况。
服务端的丢失,我们也有对应的监控,这时候丢失有几种情况:
脏数据:没有通过判断校验逻辑的数据不会直接丢弃,而是分到一个 dirty 流,可以重新找回。 特殊逻辑:比如风控逻辑。
重复上报的问题很少遇到,更多的是重启之后重新消费 Kafka 的 offset 不是那么精准。但我们的引擎是动态的,不需要重启,就避免了这个问题。