ebay 支付账务系统架构解析之“读”一无二
导读
ebay 支付账务系统 FAS(Financial Accounting System)的“读模块”能将“写模块”[1]产生的业务数据源源不断地传给 FAS 下游。这是各类金融财务报表、风控、查询服务和数据分析的核心数据来源。“读模块”旨在为 FAS 用户提供一个针对所有读场景的一站式解决方案。该模块自去年 4 月上线以来,总计传输账务记录超两百亿条,数据规模过百 TB,并且随着业务量的不断上升,写读两端的传输延时依然能稳定维持在亚秒级别。本篇文章将讲述我们设计的“读”一无二的系统是如何构建的,以达到高数据质量、低延时、满足所有读场景和开发简单等要求。
一、背景介绍
FAS 用户对于“读”的需求可以归纳为以下四点:
必须具备极高的数据质量,即不能错、不能丢、不能多,并且严格有序
与其他互联网应用相比,作为一个核心金融系统,FAS 和钱息息相关。这就要求:①下游收到的结果必须绝对正确,错一条记录,少一条记录,甚至多一条记录,都会带来难以估量的后果;②数据必须严格按照写入数据源的顺序来消费,否则进账出账的顺序不一样,对应业务的含义也会完全不一样。
端到端的延时低
对于高实时性的下游服务(如查询账户余额),延时越低意味着可以更快地获取到最新余额。这样许多支付业务就能更准确和及时,用户体验也就会更好。
能支持各类读场景
使用 FAS 数据的部门有很多,每个部门都有各自的业务,业务场景不同,所需的数据也是各异的。这就需要“读模块”不仅能提供原始数据,还能支持产生对原始数据做多次加工计算出来的衍生数据。
学习成本低,能快速上手
业务开发者希望在保证项目质量的同时缩短开发周期,这就要求对外接口的设计尽可能简单。
在“读模块”之前,下游获取数据的方式如下图所示。FAS 先将数据写入数据库(Database,DB),DB 接着把数据同步到它的镜像数据库(Mirror DB),然后 ETL Job(将业务系统的数据经过抽取 Extract、转换 Transform 之后加载 Load 到数据仓库)每天将数据从镜像数据库导入到数据仓库(Data Warehouse,DW)。如此一来,下游就能通过访问 DB 或 DW 来获取 FAS 数据。
图 1
上述方式主要存在以下三个问题:
数据有丢失的风险:这是因为数据库供应商所提供的同步机制存在 bug,会导致镜像数据库和源数据库内容不一致。
数据实时性低:ETL Job 是一天跑一次,这意味着下游看到的最新的数据也是一天前的。
数据可用性依赖于 DB 和 DW:如果遇到 DB 或 DW 升级,所有“读服务”将不可用。
因此“读模块”在设计时充分考虑到了上述需求,通过自研架构,我们保证 FAS 数据能以一种准确、有序、稳定和高效的方式被下游使用,并且学习成本低、易上手。
二、系统设计
在之前的文章超越“双十一”|ebay支付核心账务系统架构演进之路中介绍了 FAS 系统的“写模块”被设计成了一个状态机(StateMachine),状态机对上游请求的处理结果称为事件(Event),事件为既定事实,一旦产生就无法更改(Immutable)。所有事件会以追加的方式(Append-only)写入到事件仓库(Event Store)。事件仓库即为 FAS 的 Golden Source,它被抽象成一个只能追加且能无限延伸的一维数组,数组的每一项即为事件。
而“读模块”的主要功能是把这个数组完整无误地传递给下游。整个“读模块”,按功能可以分成复制子模块和处理子模块。
1、复制子模块
图 2
如上图所示,复制子模块被设计成一个分层的树状结构,顶层 L0 的节点(Root Node)直接从 Golden Source 获取;第 N 层节点从上层(N-1 层)获取数据,并为下层(N+1 层)提供数据。这样设计既能满足各应用对延时的不同需求,也不会对 Golden Source 造成太大压力。因此复制子模块有如下优点:
1)数据的高质量保证
图 3
我们在 FAS Golden Source 的事件中采用了如上图所示的存储格式。它由两部分组成,分别是 Payload 和 Meta。
Payload 包含了该事件相关的所有业务信息,例如账号、交易金额等
Meta 是描述该事件的一系列元信息。其中,Index 表示该事件在事件仓库中的位置,即一维数组中的下标;SHA 是对 Payload 和前一个事件 SHA 值计算得到的哈希值,公式如下:
如此设计存储格式就可以包含以下两类验证,来保证数据在每一个复制环节中的正确性和有序性:
一致性验证:对拉取到的每个事件,节点会应用以上公式计算 SHA 值,并和 Meta 中的 SHA 值比较,相等表示数据本身内容完整,同时和前一项的依赖关系是一致的(这是由于其中包含了前一个事件的 SHA 值)。
有序性验证:节点将接收到的数据持久化到本地硬盘时,会确保当前事件的 Index 和上一次写入事件的 Index 满足以下等式:
2)稳定的低延时
我们设计了一套完备的重连和服务发现机制,即当发现父节点连接不上时,子节点会自动寻找新的父节点,整个过程不会超过一分钟。并且,由于数据分发过程没有 DB 和 DW,其可用性不受到它们升级的影响。这两点保证了整个服务的可用性和稳定性。
为了实现稳定的低延时,我们对近期数据和历史数据的延时分别做了优化,并且提高了节点数据的恢复速度。
(i)近期数据的延时优化
最近发生的事件都存储于本地磁盘,延时是指该事件被写入 Golden Source 的时刻(Twrite)和被下游读出时刻(Tread)的差值,如下公式所示。
该差值主要由两部分组成:该事件从顶层到第 N 层经过的网络传输时间和每一层节点写入本地磁盘到从磁盘读出的时间。
图 4
为了缩短网络传输时间,节点会按照同机房->同数据中心->同地区->异地机房的顺序寻找父节点;为了缩短本地读写时间,采用了基于 mmap 的零拷贝方案(zero-copy)[2],减少了用户空间和内核空间的数据拷贝次数。
(ii)历史数据的延时优化
由于本地磁盘空间有限,历史事件无法一直保存在磁盘上。这些历史事件会被存入远程存储系统(Remote Storage),每一个节点都能直接访问。为了降低远程存储系统访问延时过高的影响,我们设计了一个三层存储结构,如下图所示:
图 5
本地磁盘只会保存最近几天的数据,所有的历史数据被保存在远程存储系统中,而中间的网络存储作为缓存,则保留了近期从远程存储系统访问过的历史数据。
(iii)节点数据恢复
如果有新增的节点,或者由于机器故障等影响的已有节点,在重新启动后,需要从父节点恢复数据。为了缩短这一过程,采用了类似TCP Sliding Window[3]的机制做并行拉取,如下图所示:
图 6
子节点会将需要恢复的数据分成若干段,每一段由一个线程去父节点拉取。这样做能极大地提高恢复速度。同时,只有前面的数据被写入之后,当前数据才能被写入磁盘,所以有序性也能得到保证。
2、处理子模块
如果将“写模块”比做发电厂的话,那么“读模块”中的复制子模块就是电网,而处理子模块就是电插头,它将电力源源不断地输送给各类电器(即读应用)。
读应用的架构如下图所示。
图 7
数据访问层负责从复制子模块的节点中获取数据,对原始数据解码并构造出事件(Event)后,将它传递给应用层并执行 onEvent 回调函数。该函数是用户处理 FAS 数据的接口,由用户实现具体的处理逻辑。
这样设计有如下两大好处:
1)简单易用,开发效率高
首先,该模块对外只提供了一个简单的接口,让开发者只需聚焦在业务逻辑的实现上,而不用操心其他技术细节(如多线程获取数据、换主、重连、去重、保序等)。
其次,由于数据的存储格式是语言中立的,理论上可以支持各种编程语言来开发读应用。
2)支持所有的读场景
我们将读场景定义为用户能拿到的包括原始数据及基于此不断衍生出来的所有数据的全集,如图 8 所示。当中的 f,g 在集合论中被称为映射函数,可以将一个集合的元素通过计算,产生另一个集合中的元素。
图 8
映射函数对应了这里的 onEvent 函数。由于处理子模块本身还能作为数据节点,计算产生的数据可以作为另一个子模块的输入,进而产生新的衍生数据。
图 9
看到这里,读者可能会问,为什么不使用现有流处理架构?这个和现有的基于Kafka Steams[4]或Flink[5]的流处理模式有什么不一样?
这里最大的区别在于 Kafka 或 Flink 只能保留最近几天的数据,如果要访问历史数据,下游应用需要写代码去其他地方(如 HDFS)拿,而 FAS 的读模式对外只提供单一的 API 接口,省去了拿历史数据的额外开发工作。
三、总结
FAS 作为一个读写分离(Command Query Responsibility Segregation,CQRS)系统[6],“读”和“写”同等重要。而和“写”不同,“读”需要解决的挑战有:高数据质量、低延时、满足所有读场景和开发简单等。我们在充分考虑这些需求的基础上设计开发出了“读模块”,为 FAS 下游应用提供了一站式解决方案。同时,我们也希望能为对数据有同等需求的行业,例如金融、医疗等,提供一些参考。
四、展望
目前的读应用只能接受一组数据流作为输入,后续会加入对多组数据流的支持,从而丰富应用场景。未来,我们还将进一步提高读模块的通用性,将框架和数据完全解耦,使得稍作修改就可以作为其他系统读模块的解决方案。
参考文献
[1] ebay 支付核心账务系统架构演进之路:https://mp.weixin.qq.com/s/O5_Rde5uUXvmBS2B7w2hOQ
[2] 零拷贝提高 IO 性能:https://en.wikipedia.org/wiki/Zero-copy
[3] TCP Sliding Window Protocol: https://en.wikipedia.org/wiki/Sliding_window_protocol
[4] Kafka Streams Architecture: https://docs.confluent.io/platform/current/streams/architecture.html
[5] Flink Architecture: https://flink.apache.org/flink-architecture.html
[6] CQRS:https://martinfowler.com/bliki/CQRS.html
文章原载于“eBay 技术荟”微信公众号,原文链接:ebay支付账务系统架构解析之“读”一无二。欢迎前往关注。
版权声明: 本文为 InfoQ 作者【贾奇 (Jacky)】的原创文章。
原文链接:【http://xie.infoq.cn/article/d4ffcaa9d99dec6463d79143a】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论