从Monzo银行系统看开源系统的应用

集群管理 Kubernetes

http://kubernetes.io/

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

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

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

RPC框架  finagle

https://twitter.github.io/finagle/guide/

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

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

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

负载均衡:大部分HTTP库都实现了基于DNS的轮询负载均衡,但是这个方式比较生硬。理想的负载均衡应该能够选择最合适的目标服务器,以达到较低的失败率,较小的延时。这样即使集群中出现因为故障而进行复制的副本,也不会影响整个系统的性能。
自动重试:对于分布式系统来说,故障是难以避免的。如果一个幂等调用失败,RPC系统应该要能够自动请求集群中的其他副本,以确保集群中存在少量故障节点时,系统整体仍然可用。
连接池:如果每个请求都需要重新创建连接,远程调用的延迟会大大增加。理想情况下每个远程调用请求都应该尽可能复用之前已经创建的连接。
路由:对于一个RPC系统来说,能够运行时修改目标机器是非常有必要的。例如一个新版本服务上线,可能需要一定的灰度过程。从新上限到100%使用,期间需要通过路由功能逐步将流量引到新版本服务上。

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

异步消息Kafka

http://kafka.apache.org/

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

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

高可用:消息发送者在发出消息之后,无需再关注消息的消费情况。即使消费者节点或者消息系统本身出现故障,该消息也必须确保被最终消费者处理。
可扩展性:由于整个系统中会有大量消息流转,消息系统本身必须能够水平扩展。当业务压力过大时,可以像其他微服务一样简单的扩展消息系统的容量。
持久化:在任何情况下,消息系统必须保证消息不会丢失。即使出现消息系统服务器故障,或者消费者出现故障时,消息最终也能够被消费者消费。
可回放:消息系统支持回放,可以方便的重现每一个消息的处理流程,对于排查问题、故障恢复等场景非常有用。
至少一次投递:消息系统首要保证消息必须被正确消费,但是要确保投递且仅投递一次基本上是不可能的,因此消息系统需要在通常情况下尽可能确保不会重复投递。
基于上述特性,在比较了多重消息中间件之后,Monzo最终选择了Kafka。

三大基础核心组件,很值得推荐学习学习。

最后更新于 三月 19, 2021

心有多大,路就有多远