Uber如何实现互联网大规模金融交易的自动化审计?


假设乘客于 2022 年 1 月从家到机场,费用为 60 美元。6-7 个月后,乘客再次从家到机场,但现在需支付 50 美元。在这两次行程中,乘客都使用了具有相同出发地和目的地的 UberX。现在,用户担心收费不符,并提出纠纷。
现在可能有很多事情可能会有所不同: 

  • 由于燃油价格或其他一些因素,同一服务(例如 UberX)的定价可能会发生变化。
  • 可能会针对特定服务取消或征收新的州税。国家可能在一段时间内补贴/取消一些通行费。
  • 由于下雨或其他因素,可能会出现拥堵,导致行程需要更长的时间。此外,由于维修工作,一些道路可能会被堵塞。
  • 您的旅行可能会有全市范围或个人的促销活动。

除了提到的因素之外,还可能存在其他业务逻辑变化。现在考虑对数百万次旅行(在过去的季度、一年或几年)执行此操作。
从软件工程的角度来看,对于给定的输入(源事件)和输出(生成的内部事务),我们可以将其视为具有足够的日志来调试给定执行实例到底发生了什么(以及原因),包括但不包括仅限于源日志和执行逻辑日志。 

这里需要解决的两个主要挑战是金融交易的 可重复性和可追溯性。

再现性
我们遵循零证明复式记账系统(为了符合公认会计准则),因此每个变动都会有相应的分录;借贷。
考虑到当时的事实(例如,路线、定价、促销、会计规则和流量),我们应该能够生成完全相同的财务条目。这种将事实转化为目标条目或报告的能力称为再现性。

可追溯性
给定一份报告或日记账分录,审计员可能想知道我们为何预订它们,或者可能想知道哪些旅行或移动导致了这些日记账分录。从报告或报告中的条目将其映射回用于生成特定日记账条目的行程/订单、会计规则和事实的能力称为可追溯性。

跟踪哪些交易受到给定错误或错误会计规则影响的能力有助于进行影响分析以及纠正这些交易。如果没有可追溯性,在数百万笔交易中查找受影响的交易集就像大海捞针一样。

关键的设计因素
1、逻辑变化
有些人可能会问:为什么这么复杂?这并不像前面例子中提到的那样简单,即获取每次出差的事实,对所有出差重复相同的流程,然后执行相关规则。这是因为业务规则和流程也会随着时间的推移而变化,需要加以考虑。

Uber在70多个国家运营,每个国家都有自己的法规。修改和更新其实并不罕见。此外,随着我们业务模式的扩展和增强,我们需要适应业务变化并更新业务流。
因此,可能需要根据不同行程的发生时间(或者更准确地说,是我们系统的处理时间)使用不同的逻辑。
具体来说,我们应该能够将每个事务映射回事实、特定业务规则和使用的流程(规则引擎 上下文)。

上下文环境变化
现在,基于对可重复性的需求,我们可以得出这样的结论:对于逻辑或代码中的给定变化,需要考虑的规则可能很多,但它们仍然是有限的。

如果我们可以假设我们有一个服务二进制文件来处理这些变化,那么我们就可以通过适当的服务二进制文件来处理所有事件,并重现完全相同的条目。

不幸的是,这并不是全部。

服务在设计上与不同的系统交互,如数据存储、队列、缓存以及其他服务。

这种由外围系统和技术组成的不断变化的环境,使您几乎不可能始终如一地重现允许您运行相同二进制文件并实现相同结果的确切条件。

因此,除了为每次代码/规则变更维护二进制文件外,外围系统的任何变更都可能需要额外的手动工作来设置沙箱基础设施,以便再次执行相同的二进制文件。


方案
来自多个来源的事件通过消息队列(在我们的案例中为Apache Kafka )到达事件处理器。根据事件类型和涉及的实体,创建执行计划。

执行计划对需要执行的规则和执行顺序进行协调。该计划可在执行过程中根据附加参数进行扩展,因此具有动态性。

每一步都在规则执行器中执行,规则执行器执行规则库中的相应规则。每天都会捕获财务交易的审计事件。

规则数据模型
因此,有了初始输入(我们称之为规则参数,因为这些只是规则中使用的参数)和规则执行流程,我们实际上就可以建立一个自给自足的事件,用于审计目的。

该审计事件包含重现相同日记账条目所需的一切(规则参数是数据,DAG是逻辑)。此外,如果我们只是为每笔记账会计事项不断映射审计事件,我们实际上可以追溯到特定的交易或分录的业务流程和规则(存储为DAG的一部分),以及导致它的事实。因此,同一审计事件既解决了可重复性问题,也解决了可追溯性问题。

这里,如图所示,一个审计事件可能有多个项目,如促销、积分、税金、通行费;每个项目可能有单独的规则参数和规则执行流程。因此,审计事件并不总是单一的规则参数和规则执行流程。

除了多个项目之外,在某些情况下,我们还需要为一个特定的细列项目涉及多个相关方(例如,机场、过路费、乘客、司机、机场接机的Uber)。

例如,机场费需要向乘客收取,并支付给当局。此外,这也增加了预订毛额,然后抵消了我们的收入,因为我们不会保留这些费用。

如果我们在规则流协调器上添加一个装饰器,这将确保执行流被适当捕获;对于生成的每个事务,我们将有一个相应的审计事件。此外,我们仅将源事件中的相关数据属性作为规则参数存储在审计事件中。然后,这些事件被推送到安全队列(在我们的案例中为安全Kafka)。

该队列可被服务、Apache Flink 作业或任何其他类型的消费者使用,以持续监听审计事件。这个队列可以让我们做很多事情。

作为一个金融技术平台,我们希望持续消费审计事件,并验证每一笔生成的交易都有相应的审计事件,以证明审计事件的完整性。消费者可以利用通用逻辑(有向无环图的深度优先搜索遍历)来处理审计事件。审计事件的准确性可通过与实际记录的交易进行比较来验证。在这种情况下,该逻辑可被视为一个插件,使我们能够将财务审计人员开发的代码纳入其中,并在不向审计人员透露底层代码库的情况下证明我们的系统按预期运行。

审计事件存储在HDFS中,以便长期保存,方便审计查询和其他用例。

重新计算和调节
总体而言,我们有多个上游发出各种类型的不同资金流动(例如,行程完成、收费失败、促销和信用赠款或兑换)。我们的服务(金融计算服务)使用所有这些 Kafka 消息并按照上一流程中提到的方式处理它们。因此,每个上游事件都会转换为一个或多个交易,每个交易都包含一些日记行和一些与事件相关的元数据。 

我们的服务使用 Git 中提交的适当会计规则来生成这些交易。作为财务控制,所有这些承诺均由会计团队批准,因此遵守 SOX 合规性和控制。

交易被写入不可变的、仅附加的交易存储中。我们使用内部 NoSQL 数据存储 Schemaless 作为交易存储。

对于写入的每笔交易,我们都会将压缩的审计事件发布到安全且无损的 Kafka 主题。这里的 HDFS 用作审计事件的持久存储,因此我们可以针对我们的用例查询数据或对其运行重新计算。可以查询这些 Hive 表以支持临时审计请求并提供可跟踪性。

我们在这个批处理存储上有日常作业来读取每日数据并对其运行重新计算,以验证以 Schemaless 编写的事务是否与重新计算生成的数据同步。重新计算方法是一个可插入模块,我们在 Java 中将其实现为 Hive 用户定义函数。因此,重新计算可以像另一个 Hive 查询一样执行,无需任何工程支持。 

重新计算函数是使用不同的规则引擎库用不同的语言编写的。
因此,如果 Go 服务使用的实际库出现任何错误或不需要的更改,我们可以及早检测到潜在的回归。
此外,如果审计员愿意,他们可以提供自己的函数,并通过他们的库运行相同的事件,以确保规则执行按预期工作。

结果
借助 Tesseract 审计框架,我们可以确保 Uber 财务系统处理的每一个事件即使在互联网规模上也是可审计和可追溯的。

借助这个框架,我们能够将解决方案扩展到每天超过 10 亿笔会计交易,并且未来还能够横向扩展。

财务审计有时会要求我们重现与特定旅行相关的所有交易。每次旅行都由多个事件组成,因此并不总是发生在同一天或同一个月。借助 Hive 中通过 transactionUUID 查询的数据,数据分析师和审计员能够轻松地对数据进行切片和切块,以衡量影响并确保旅行样本的可重复性和可追溯性。我们将总体手动工作量减少了 60%。

我们能够使用标准 Hive 配置在几个小时的 SLA 内重新计算本季度发生的每个审计事件,但这可以通过添加更多计算来水平扩展。
此功能支持数百条规则以及代码更改,无需对任何审核组件、重新计算方法或引擎进行任何更改。

此外,持续审核的能力有助于我们每天监控系统的运行状况。这使我们能够每天对我们的财务系统进行审计,及时发现并纠正审计中的任何问题。