2017年,全世界都经历了加密电子货币的爆发式增长,整个生态圈的市值从200亿美元激增至6,000亿美元。
在此期间,Coinbase几乎每一个技术组件都经历了考验。事实表明,我们团队应像重视安全性一样,重点关注这一平台的可靠性和可扩展性。在2018 MongoDB全球用户大会上,我和Coinbase 工程师Michael de Hoog以及Jordan Sitkin一起发表了主题演讲,向参会者介绍了Coinbase在2017年所获得的经验教训以及我们当前是如何进行平台扩展工作的。以下为演讲概述。
一Luke Demi
1
2017年的经验教训
Coinbase 2016年的流量模式显然较为平稳,这是加密电子货币取得爆发式增长的前一年。在繁荣期之前,如果画一条红线,用来表示对于平台的期望水平,我们会将这条红线画在比平日最大流量线高大概四到五倍的位置上。在这个位置上,后台API请求量大约是每分钟100,000次。
(通过上图我们可以快速了解2016年每分钟后台请求量,这是以太币价格飙升之前的请求量。)
然而,到了2017年的5月和6月,以太币价格和流量均出现爆发式增长,已经越过了红线。在这段时期,有几天时间我们的流量持续保持在红线区域,而正是在此期间,我们遇到了故障停机的情况。
(上图显示了在2017年高流量期的早期时段,每分钟后台请求量的情况。)
为迅速解决可扩展性方面的问题,Coinbase工程团队开始把精力集中在系统环境中易于实现的目标上。我们夜以继日地工作,执行诸如垂直扩展、升级数据库版本之类的作业,以提升性能、优化索引,并将热点区的各种采集作业拆分到各自的集群上处理。每次的性能提升都会带来一定的扩展空间。然而,这些容易实现的成果越来越少,而流量仍在不断增长。
每次服务中断的模式都是相同的:我们的主监控平台会显示出一个100倍的峰值延时,同时,在Ruby处理时间和MongoDB处理时间之间存在一个奇怪的50/50分割现象。作为主数据存储,在高流量情况下,MongoDB出现高延时是有可能的,而Ruby处理时间却不应增加。
我们形象地将这个问题称为“幽灵”,这是因为我们现有的监控工具无法对某些最关键的问题给出清晰的答案。这些查询究竟来自于哪里?到底是怎样的查询?为何在Ruby处理时间会出现相关的峰值?这个问题会是应用方引起的吗?
简言之,我们现有的监控服务无法利用系统环境中的所有可用信息。我们需要一个框架,以解答上述问题并实现系统环境中各组件之间关系的可视化。
我们开始更改MongoDB数据库驱动器,将超出特定响应时间阈值的所有查询操作都记录下来,同时记录重要的场景信息,如请求/响应数据量、响应时间、源代码行数和查询形状,从而进一步分析数据库查询操作。
更改后的MongoDB数据库驱动器提供了详细的数据,使我们得以快速缩小出现某些奇怪现象的范围,甚至在不中断运行的情况下也可以做到。我们观察到的第一个异常值来自一个目标对象,它有着超大的响应数据量,该对象源自一台发现查询请求的设备。在用户登录进行采购或查看仪表盘时,大量的查询会导致巨大的网络负载。
引起超大响应数据量的原因是我们在建模时将用户和设备类别之间的关系设定为多对多的关系。例如,有些用户可能有多台设备,有些设备可能有多个用户。某种拙劣的指纹识别算法可能会将大量用户不平衡地分配给同一台设备,导致单台设备对象被分配了大量的用户标识数组。
为解决这个问题,我们将关系进行了代码重构,将其变为简单的单对多关系,一台设备只被映射到一个单独用户。这种改进很大程度上影响了系统性能,它将Coinbase 2017年的单点性能提升到最高水平。
这一发现显示出良好的监控所能发挥的力量。在对我们的数据库查询操作进行精细化分析前,这是一个几乎无法排查出的问题。现在借助新的工具,这个问题搞清楚了。
我们着手解决的另一个问题是某些特定采集活动的读操作吞吐量过大。我们决定增加一个查询缓冲层,可以在分布式缓存中暂时存储查询结果。在特定的采集活动中,如果有对单个文档的查询,在查询数据库前首先会查询缓存,而且任何数据库写操作都会使缓存无效。
我们能够同时更改多个数据库集群。在关系对象映射(ORM)和驱动器层,通过对查询缓存执行写操作,我们可以一次性地对多个有问题的集群做出更改。
后来发现,我们在5月和6月经历的流量大规模激增的情况根本没办法与几个月后(即12月和1月)发生的流量激增情况相提并论。有了这些修复操作以及其他活动所积累的经验,我们已能够经受更大的流量激增所带来的考验。
(2017年较早期的流量激增情况与12月和1月相比,在屏幕上只是一个微不足道的光点)
2
时刻为将来做好准备
我们今天的努力工作是在为加密电子货币的下一次爆发提前做好准备。我们需要找到一条未来改善系统性能的途径,即使是在流量较低的时期。显而易见的做法就是:基于我们过去经历的流量水平,将数值增加若干倍,以此模拟流量模式对系统环境进行测试,从而发现下一个薄弱点。
我们选择的解决方案能够进行流量捕获和回放,特别是针对我们的数据库,以根据需要生成人工的“加密狂躁者”(crypto mania)。对我们而言,这种方法要优于合成化流量生成的方法,因为它无需保存最新的合成脚本。每次运行这个套件时,我们都要基于捕获到的数据来确定:查询活动所映射的正是我们的应用所产生的流量类型。
为做到这一点,我们搭建了 “捕获器”(Capture)工具,用以代替原有的被称为“mongoreplay”的工具。在系统环境中选择了一个特定集群后,“捕获器”工具就同时启动了磁盘快照,并开始从我们直连到该集群的应用服务器上捕获原始数据流量。稍后,它将这些捕获到的数据的加密版本保存到S3回放设备。当我们准备好进行回放时,另一台被称为“标准器”(Cannon)的工具(该工具基于mongoreplay) 将记录的流量回放到一个新部署的集群上,这个新部署的集群是基于先前的集群快照搭建的。
我们面临的挑战是如何从一个跨越多台应用服务器的集群同时捕获MongoDB数据库的全部流量。“标准器”为每个“捕获器”开放了10MB的缓冲区,以同时对这些“捕获器”的数据进行合并和过滤,从而解决了这个问题。
最终结果是生成了一个合并后的“捕获器”文件,该文件可被“标准器”定位到一个新部署的MongoDB集群。“标准器”允许你精确设定捕获数据器的回放速度,目的是模拟将某个特定日期的负载量增大数千倍后的情形。
我们刚刚从“捕获器”和“标准器”起步,在对所有MongoDB数据库集群进行不同类型的负载测试时,我们惊喜地看到了不同类型的发现。
我们在使用“捕获器”和“标准器”工作时,作为工作成果的一项重要发现即来自“标准器”的调试功能。“标准器”具备检查特定的“捕获器”文件的功能,并能看到其中的前100条信息。根据检查情况,我们发现了一些有意思的信息:
注意到与find指令相混杂的ping指令了吗?显示结果表明:MongoDB Ruby驱动器没有正确跟在MongoDB驱动器参数的后面,而是在每次对数据库进行查询的同时执行了一个ping指令(用于检查副本集的状态)。尽管与故障停机相关的问题不太可能是这个动作所导致的,但这个动作极有可能就是我们在监控器上观察到“幽灵”现象的原因。
在付出了所有努力应对这些挑战之后,我们对Coinbase目前的可靠性状态感到骄傲。2017年发生的几件事再度表明:客户访问和查看其资金情况的能力对于我们能否实现自身目标非常关键,我们的目标就是成为客户最可信赖的购买、销售和管理加密电子货币的平台。尽管安全性一直排在我们工作的最优先位置,我们仍将努力确保将平台的可靠性同样放在最优先的位置!
关于Coinbase公司
Coinbase是美国最大的加密货币交易平台,提供比特币、以太币以及莱特币的交易。公司成立于2012年,总部设在旧金山。