电影《我们的秘密宇宙:细胞的隐秘生活》剧照
投资业务是银行业最复杂的领域之一,因为不仅有贷款、借款和存款,还有证券、货币、商品、衍生品以及结构性产品形式的各种复杂内容。
最近,我们看到人们的金融知识有所提高。 越来越多的人参与到证券市场的交易中。 个人投资账户不久前出现。 它们允许您在证券市场进行交易并获得减税或避税。 所有来到我们这里的客户都希望管理他们的投资组合并实时查看报告。 此外,这种投资组合通常是多产品的,也就是说,人们是不同业务线的客户。
此外,俄罗斯和外国监管机构的需求也在不断增长。
为了满足当前的需求,并为未来的升级打下基础,我们开发了基于Tarantool的投资业务核心。
一些统计数据。 Alfa-Bank的投资业务为个人和法人实体提供经纪服务,以提供在各种证券市场进行交易的机会、存储证券的存管服务、为拥有私人和大型资本的个人提供信托管理服务、为其他公司发行证券的服务。 Alfa-Bank的投资业务包括每秒超过3条报价,这些报价是从各个交易平台下载的。 工作日期间,代表银行或其客户在市场上完成了超过 300 万笔交易。 外部和内部平台每秒执行多达 5 个订单。 与此同时,所有客户,无论是内部还是外部,都希望实时查看他们的头寸。
史前
从2000年代初开始,我们的投资业务领域开始独立发展:交易所交易、经纪服务、货币交易、证券和各种衍生品的场外交易。 结果,我们陷入了功能井的陷阱。 这是什么? 每个业务线都有自己的系统,这些系统彼此重复功能。 每个系统都有自己的数据模型,尽管它们使用相同的概念进行操作:交易、工具、交易对手、报价等等。 随着每个系统的独立发展,各种各样的技术出现了。
此外,系统的代码库已经相当过时,因为有些产品起源于1990世纪XNUMX年代中期。 在某些领域,这减慢了开发过程,并且出现了性能问题。
新解决方案的要求
企业已经意识到技术改造对于进一步发展至关重要。 我们被赋予了任务:
- 将所有业务数据收集在单个快速存储和单个数据模型中。
- 我们不得丢失或更改此信息。
- 有必要对数据进行版本控制,因为监管机构随时可能要求提供前几年的统计数据。
- 我们不能仅仅带来一些新的、时尚的DBMS,而是要创建一个解决业务问题的平台。
此外,我们的建筑师还设定了自己的条件:
- 新的解决方案必须是企业级的,也就是说,它必须已经在一些大公司进行了测试。
- 解决方案的操作模式应该是任务关键型的。 这意味着我们必须同时存在于多个数据中心,并在一个数据中心发生故障时从容地度过难关。
- 系统必须可水平扩展。 事实是,我们当前所有的系统都只是垂直可扩展的,并且由于硬件能力的低增长,我们已经达到了天花板。 因此,我们需要一个水平可扩展的系统才能生存的时刻已经到来。
- 除此之外,我们被告知解决方案必须便宜。
我们遵循标准路线:制定要求并联系采购部门。 从那里我们收到了一份总体上准备好为我们做这件事的公司名单。 我们向每个人讲述了这个问题,并收到了其中六人对解决方案的评估。
在银行,我们不相信任何人的说法;我们喜欢自己测试一切。 因此,我们招标竞争的一个强制条件就是通过负载测试。 我们制定了负载测试任务,六分之三的公司已经同意自费实施基于内存技术的原型解决方案来进行测试。
我不会告诉您我们如何测试所有内容以及花了多长时间,我只是总结一下:负载测试中的最佳性能是由 Mail.ru Group 开发团队基于 Tarantool 的原型解决方案展示的。 我们签署了协议并开始开发。 Mail.ru Group 有四名人员,Alfa-Bank 有三名开发人员、三名系统分析师、一名解决方案架构师、一名产品负责人和一名 Scrum Master。
接下来,我将告诉您我们的系统是如何发展、如何演变、我们做了什么以及为什么这样做。
进入菜单
我们问自己的第一个问题是如何从当前系统获取数据。 我们认为 HTTP 非常适合我们,因为当前所有系统都通过 HTTP 发送 XML 或 JSON 来相互通信。
我们使用 Tarantool 内置的 HTTP 服务器,因为我们不需要终止 SSL 会话,而且它的性能对我们来说已经足够了。
正如我已经说过的,我们所有的系统都存在于不同的数据模型中,并且在输入时我们需要将对象带到我们自己描述的模型中。 需要一种允许数据转换的语言。 我们选择命令式Lua。 我们在沙箱中运行所有数据转换代码 - 这是一个安全的地方,运行的代码不会超出这个地方。 为此,我们只需加载所需的代码,创建一个具有无法阻止或删除任何内容的功能的环境。
转换后,必须检查数据是否符合我们正在创建的模型。 我们讨论了很长时间的模型应该是什么以及用什么语言来描述它。 我们选择 Apache Avro 是因为该语言简单并且有 Tarantool 的支持。 新版本的模型和自定义代码可以每天多次投入运行,无论是否有负载,都可以在一天中的任何时间运行,并且可以非常快速地适应变化。
验证后,必须保存数据。 我们使用 vshard 来做到这一点(我们有地理分散的分片副本)。
此外,由于特殊性,大多数向我们发送数据的系统并不关心我们是否收到数据。 这就是我们从一开始就实施修复队列的原因。 这是什么? 如果由于某种原因某个对象没有进行数据转换或验证,我们仍然确认接收,但同时将该对象保存在修复队列中。 它是一致的,位于主业务数据仓库中。 我们立即为其编写了一个管理员界面、各种指标和警报。 因此,我们不会丢失数据。 即使源发生了变化,如果数据模型发生了变化,我们也会立即检测到它并进行调整。
现在您需要学习如何检索保存的数据。 我们仔细分析了我们的系统,发现 Java 和 Oracle 的经典堆栈必然包含某种将数据从关系数据转换为对象数据的 ORM。 那么为什么不立即以图表的形式将对象提供给系统呢? 所以我们很高兴采用了 GraphQL,它满足了我们所有的需求。 它允许您以图表的形式接收数据,并仅提取您现在需要的数据。 您甚至可以非常灵活地对 API 进行版本控制。
我们几乎立即意识到我们提取的数据还不够。 我们创建了可以链接到模型中的对象的函数 - 本质上是计算字段。 也就是说,我们将某个函数附加到该字段,例如计算平均报价。 而请求数据的外部消费者甚至不知道这是一个计算字段。
实施了身份验证系统。
然后我们注意到我们的决定中明确了几个角色。 角色是一种功能聚合器。 通常,角色具有不同的设备使用情况:
- T-Connect:处理传入连接,CPU 受限,内存消耗低,无状态。
- IB-Core:通过Tarantool协议转换接收到的数据,即对表进行操作。 它也不存储状态并且是可扩展的。
- 存储:只存储数据,不使用任何逻辑。 该角色实现最简单的接口。 得益于 vshard 的可扩展性。
也就是说,使用角色,我们将集群的不同部分相互解耦,从而可以相互独立地进行扩展。
因此,我们创建了一个异步事务数据流记录和一个带有管理界面的修复队列。 从业务角度来看,记录是异步的:如果我们保证将数据写入自己,无论在哪里,那么我们都会确认它。 如果未确认,则说明出现问题,需要发送数据。 这就是异步录制。
测试
从项目一开始,我们就决定尝试实施测试驱动开发。 我们使用 tarantool/tap 框架在 Lua 中编写单元测试,并使用 pytest 框架在 Python 中编写集成测试。 同时,我们让开发人员和分析师都参与编写集成测试。
我们如何使用测试驱动开发?
如果我们想要一些新功能,我们会尝试先为其编写一个测试。 当我们发现错误时,我们确保先编写测试,然后才修复它。 一开始这样工作很难,员工会产生误解,甚至破坏:“我们现在赶紧修复它,做一些新的事情,然后用测试覆盖它。” 只是这个“以后”几乎永远不会到来。
因此,你需要强迫自己先写测试,然后要求别人来做。 相信我,测试驱动开发即使在短期内也会带来好处。 你会感觉你的生活变得更加轻松。 我们认为 99% 的代码现在已经被测试覆盖了。 这看起来很多,但我们没有任何问题:测试在每次提交上运行。
然而,我们最喜欢的是负载测试;我们认为它是最重要的并定期进行。
我将告诉您一个关于我们如何对第一个版本之一进行第一阶段负载测试的小故事。 我们将系统安装在开发人员的笔记本电脑上,打开负载,每秒收到 4 笔交易。 对于笔记本电脑来说效果不错。 我们将其安装在由四台服务器组成的虚拟负载台上,比生产中的性能要弱。 部署到最低限度。 我们运行它,得到的结果比在笔记本电脑上的一个线程的结果还要差。 内容震撼。
我们非常难过。 我们查看服务器负载,但结果发现它们是空闲的。
我们给开发人员打电话,他们向我们这些来自 Java 世界的人解释说 Tarantool 是单线程的。 它只能被负载下的一个处理器核心有效使用。 然后,我们在每台服务器上部署了尽可能数量的 Tarantool 实例,打开负载,每秒已收到 14,5 个事务。
让我再解释一下。 由于角色划分使用资源的方式不同,我们负责处理连接和数据转换的角色仅加载处理器,并且与负载严格成比例。
在这种情况下,内存仅用于处理传入连接和临时对象。
相反,在存储服务器上,处理器负载增加,但比处理连接的服务器慢得多。
内存消耗与加载的数据量成正比增长。
服务
为了将我们的新产品专门开发为应用程序平台,我们创建了一个用于在其上部署服务和库的组件。
服务不仅仅是在某些领域运行的小段代码。 它们可以是相当大且复杂的结构,是集群的一部分,检查参考数据,运行业务逻辑并返回响应。 我们还将服务模式导出到 GraphQL,消费者收到数据的通用访问点,并对整个模型进行内省。 非常舒服。
由于服务包含更多功能,我们决定应该有一些库来移动常用代码。 我们将它们添加到安全环境中,之前已检查过它不会对我们造成任何破坏。 现在我们可以以库的形式为函数分配额外的环境。
我们希望拥有一个不仅用于存储而且还用于计算的平台。 由于我们已经有了一堆副本和分片,因此我们实现了一种分布式计算,并将其称为 MapReduce,因为它的结果与原始 MapReduce 类似。
旧系统
尽管我们的旧系统支持该协议,但并非所有旧系统都可以通过 HTTP 调用我们并使用 GraphQL。 因此,我们创建了一种允许将数据复制到这些系统中的机制。
如果我们发生某些变化,存储角色中会触发唯一的触发器,并且包含更改的消息最终会进入处理队列。 它使用单独的复制器角色发送到外部系统。 该角色不存储状态。
新的改进
您还记得,从业务角度来看,我们进行了异步录制。 但后来他们意识到这还不够,因为有一类系统需要立即接收有关操作状态的响应。 因此我们扩展了 GraphQL 并添加了突变。 它们有机地融入了现有的数据处理范式。 对于我们来说,这是另一类系统的读写的单点。
我们还意识到,仅服务对我们来说还不够,因为每天、每周、每月需要构建一次相当繁重的报告。 这可能需要很长时间,并且报告甚至可能阻塞 Tarantool 的事件循环。 因此,我们创建了单独的角色:调度程序和运行程序。 跑步者不存储状态。 它们执行我们无法即时计算的繁重任务。 调度程序角色监视这些任务的启动时间表,这在配置中进行了描述。 任务本身与业务数据存储在同一位置。 当正确的时间到来时,调度程序会接受任务,将其交给某个运行者,由运行者对其进行计数并保存结果。
并非所有任务都需要按计划运行。 有些报告需要按需阅读。 一旦该需求到达,就会在沙箱中创建一个任务并将其发送给运行器执行。 一段时间后,用户会收到异步响应,表明所有内容均已计算完毕并且报告已准备就绪。
最初,我们坚持存储所有数据、对其进行版本控制而不删除它的范例。 但在生活中,时不时你还是要删除一些东西,大部分是一些原始或中间信息。 基于expiration,我们创建了一种清除存储中过时数据的机制。
我们也知道,迟早会出现内存中没有足够空间来存储数据的情况,但无论如何,数据都必须存储。 为了这些目的,我们很快就会制作磁盘存储。
结论
我们从将数据加载到单个模型的任务开始,并花了三个月的时间来开发它。 我们有六个数据供应系统。 Lua 中整个转换为单个模型的代码大约有 30 万行。 大部分工作仍在进行中。 有时相邻团队缺乏动力,并且有很多情况使工作变得复杂。 如果您曾经面临类似的任务,请将您认为正常的实施时间乘以三甚至四。
还要记住,业务流程中的现有问题无法使用新的 DBMS 来解决,即使是一个非常高效的 DBMS。 我的意思是说? 在我们的项目开始时,我们给客户留下了这样的印象:现在我们将带来一个新的快速数据库,我们将生存! 过程会更快,一切都会好起来的。 事实上,技术并不能解决业务流程存在的问题,因为业务流程就是人。 而且您需要与人合作,而不是与技术合作。
测试驱动开发在早期阶段可能是痛苦且耗时的。 但即使在短期内,当您不需要做任何事情来进行回归测试时,它的积极效果也会很明显。
在开发的各个阶段进行负载测试极其重要。 你越早发现架构中的缺陷,就越容易修复它,这将为你节省大量的时间。
卢阿没有什么问题。 任何人都可以在其中学习编写:Java 开发人员、JavaScript 开发人员、Python 开发人员、前端或后端。 甚至我们的分析师也写了它。
当我们谈论我们没有 SQL 的事实时,人们会感到害怕。 “如何在没有 SQL 的情况下获取数据? 那可能吗? 当然。 在 OLTP 类系统中,不需要 SQL。 有某种语言形式的替代方案可以立即返回到面向文档的视图。 例如,GraphQL。 还有一种分布式计算形式的替代方案。
如果您了解需要扩展,请在 Tarantool 上设计您的解决方案,使其可以在数十个 Tarantool 实例上并行运行。 如果你不这样做,以后将会很困难和痛苦,因为 Tarantool 只能有效地使用一个处理器核心。
来源: habr.com