[关闭]
@babydragon 2016-10-10T16:53:35.000000Z 字数 3837 阅读 2782

Monzo是如何从0开始构建7*24小时不停歇的银行后台系统的?

架构

英国移动银行Monzo正在从0开始构建他们的后台系统,对于银行来说,它们的系统必须7*24小时可用,并且能够同时向全球数以亿计的客户提供服务。本文介绍了Monzo如何利用现代、开源的技术来构建它们的系统。


对于银行后台系统来说,稳定性是非常重要的;而对于移动银行来说,还需要面对数以亿计的客户。Monzo遇到的正是这种挑战,客户一方面需要7*24小时能够管理他们的钱,因此数据延时、单点故障等是不可接受的;另一方面又希望他们的需求能够很快实现。

正因为如此,Monzo的后台系统从一开始就设计成分布式微服务架构。一些大型互联网公司(例如亚马逊、Netflix、Twitter等)的实践已经证明,独立大型应用在用户数量快速增长或者开发人员数量增长之后,会导致扩容、需求响应等一系列问题。同时,Monzo的业务发展方向,需要针对不同地区的客户进行定制化,为了提高开发效率,必须减少开发团队之间的耦合。

Monzo后台系统开发人员最初只有3个人,必须从务实开始。最初的团队选择了他们相对熟悉的技术,确定了方向就开始构建产品,但是技术时刻在改变。

此处输入图片的描述
Monzo开发工作室

当Monzo beta版本发布的时候,他们的后台服务已经有将近100个,同时开发团队也在快速增长。此时,他们开始考虑现有系统的架构问题,梳理出以下几个着重关心的领域:

  1. 集群管理:随着服务数量的增加,需要有个自动化的方式管理大量服务器、分布式任务和服务器故障;
  2. 多语种服务:在Monzo主要使用Go语言进行开发,但是没有一种语言的生态环境可以满足整个银行后台系统的需要,因此整个后台需要能够承载多种语言环境的服务;
  3. 远程调用框架:由于有大量离散的服务,分布在不同的主机、数据中心、甚至不同大陆,后台系统必须有一个强大的远程服务调用层,以处理模块故障,降低延迟,理清调用链路。
  4. 异步消息:为了提高后台系统的性能和可伸缩性,Monzo通过异步消息队列将业务逻辑放入“后台”运行,消息队列必须保证极强的可靠性,以避免消息丢失的发生。

下面我们就来逐一了解这些系统的详细设计和选型。

集群管理

Monzo系统包含大量微服务,如果一台主机上只部署一个服务,会造成对服务器的大量浪费。按照传统方式将服务器归类,会增加服务扩容的难度。因此,一个支持快速伸缩的集群调度系统,应该抽象出应用程序的运行环境,将运行环境和底层硬件进行隔离。调度算法根据应用程序负载和可用资源,对其进行扩容和缩容。另外,Monzo在设计集群管理系统时,希望能够让所有的应用程序共享一个调度器,因此它不仅需要能够调度无状态服务,还需要能够调度有状态服务。

容器化的提出,特别是Docker的兴起,将前面提到的抽象层进行了标准化。首先,通过将应用程序及其运行时依赖打包成一个镜像,使得应用程序运行环境和主机解耦;然后,通过整合Linux内核的隔离特性(cgroupnamespace),使得主机上运行的多个服务之间能够相互隔离,减少干扰。这样,集群管理服务对应用程序可以做到黑盒,将重心放到服务编排上去。

Monzo最初使用Mesos+Marathon的架构,随后切换到了运行在CoreOS上的Kubernetes。下图展示了在切换到Kubernetes之后,整体开销的变化:

此处输入图片的描述

从上图可以看见,使用Kubernetes之后,整体开销开始下降。这是因为所有系统都共用资源池。例如,之前Monzo的构建服务搭建在独立的Jenkins服务器上,使用了Kubernetes之后,构建服务由Kubernetes根据机器可用资源分配到现有机器上。Kubernetes的资源分配和限制模块可以尽可能的利用机器空闲资源,并且保证低优先级的任务不会影响高优先级任务。

另外,通过在AWS上将Kubernetes以HA模式部署之后,系统稳定性有了明显的增强,Monzo开始在AWS上部署类似Netflix的Simian Army,对生产环境服务进行破坏性操作,以验证生产环境对服务失败的容错能力。

多语种服务

Monzo后台系统主要使用Go语言编写,因为Go语言在构建低延时高并发应用程序上有先天的优势。但是一种语言很难完成整个银行后台系统的搭建,因为系统中可能会用到大量不同语言编写的开源软件,同时技术人员也会有不同的语言偏重。

Docker的出现解决了多语种应用部署的问题,它将应用程序及其运行环境一并打包,调度系统无需关注其内部使用的具体语言。但是多种编程语言的混用,会降低代码的复用程度,因为无法再抽取公共代码成为类库相互引用。

为了解决这个问题,Monzo将大量基础服务采用RPC的方式暴露出来,将“代码共享”变成了“基础服务共享”。例如,如果要获取一个分布式锁,无需通过客户端访问etcd,而只需要通过封装好的RPC接口即可。通过将基础服务(例如数据库、消息队列等)RPC化,每种新的语言只需要发起RPC调用,即可使用现有的基础服务。通过这种访问,Monzo完成了对Java、Python和Scala等语言的兼容。

RPC框架

上一节提到了,Monzo通过RPC的方式将基础服务暴露出去,因此它们的基础架构需要一个强大的RPC框架,以支撑其微服务架构。

首先是传输协议,为了能够让多种语言构建的服务之间能够方面交互,HTTP协议是首选的传输协议。几乎每种语言都有实现HTTP协议的标准库,这能够降低使用门槛。

另外,要能够支撑整个微服务架构,RPC框架还应该有以下这些特性:

基于上述这些特性,Monzo最终选择了Finagle。它拥有上述所有特性,并且自身的模块化设计也降低了学习成本。另外,Twitter已经使用该框架多年,说明它经受了实战的考验。刚好,在今年linkerd发布了,它是基于Finagle的进程外代理,这意味着那些不运行在JVM上的语言也能够使用Finagle的这些特性。

Monzo将linkerd在Kubernetes上以守护集(daemon set)方式部署,这样每台服务器上都会运行linkerd服务。应用服务首先和本机上的linkerd服务进行交互,然后linkerd再通过Power of Two Choices + Peak EWMA(exponentially-weighted moving average)方式做负载均衡。该算法通过请求往返时间和请求次数加权计算均线,然后从其中选择最优的n个结果,再从结果集中随机挑选。这种负载均衡方式兼顾了选取最优后端,又避免了因为后端失效导致的抖动。

异步消息

Monzo的大部分业务逻辑在后台都是通过异步消息完成的。虽然有些操作本身耗时很短,但是通过异步消息,可以更快的向用户反馈任务状态。

由于大量核心逻辑都采取了异步化,每个消息都非常重要,一个完整业务操作每个步骤的消息都不能跳过,即使发生了无法恢复的异常,整个流程也应该能够在故障修复之后继续执行下去。因此,对于异步消息架构来说,必须满足以下特性:

基于上述特性,在比较了多重消息中间件之后,Monzo最终选择了Kafka。虽然从架构上来看,Kafka和一般的消息中间件有着很大的不同,它更像是一个可复制的提交日志(commit log),但是它的特性刚好可以满足Monzo对于异步消息架构的要求。首先Kafka的复制和分区特性,可以满足对高可用和可扩展性的要求。其次,Kafka的消费者仅仅维护了一个消息日志中的游标,这样可以降低发布/订阅模型模式的成本,另外还可以通过修改游标来重新处理过去的消息。

总结

Kubernetes、Docker、linkerd、Kafka,Monzo通过一系列开源软件,构建了他们的7*24小时银行后台系统,其中的选型、运用对于其他系统也有一定的借鉴意义。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注