我们如何将 Moove 的性能提升 59%
大多数 Moove 用户离我们的服务器很远。我们没有迁移任何一台服务器,就将首次响应时间缩短了 59%,以及这件事为何比看上去更难。距离是我们最慢的依赖
打开 Moove app 的人,大多使用的是尼日利亚和印度尼西亚的移动网络,距离响应他们的服务器有数千公里。本月,我们改变了他们的请求抵达我们的方式,我们的首字节时间 (TTFB),即手机发出请求到收到答复的第一部分之间的时间,缩短了 59%,从 0.74 秒降到 0.31 秒。为此,我们没有迁移任何一台服务器。
本文将解释为什么距离给我们造成的代价超出预期、我们做了哪些改变、比改变本身更难的那一部分,以及我们会对即将做同样事情的其他团队说些什么。
为什么一次长途旅程会变成三次
工程师把发送某物与它到达之间的延迟称为延迟 (latency),把一条消息到达服务器并收到回复所需的时间称为往返时间 (RTT)。从拉各斯到我们的服务器,RTT 在 200 到 300 毫秒之间。单看这个数字尚可接受。问题在于,一个请求很少是单独出行的。
手机要通过安全连接请求任何内容之前,必须先完成两次握手 (handshake),即用来建立连接的简短交换。TCP 握手打开连接,随后TLS 握手商定保护连接隐私的加密密钥。每一次都要花费一个完整的往返。之后请求本身才发出,这是第三次往返。按每次 250 毫秒计算,在我们的服务器开始做任何工作之前,用户就已经等了大约四分之三秒。
在移动网络上,这笔代价的支付频率远超你的预期。手机会在基站之间切换,无线模块会为省电而休眠,应用会被切到后台。这些事件中的每一个都可能让连接中断,下一个请求就必须从冷连接 (cold connection),即尚未完成任何握手的连接,重新开始,并再次支付完整的建立成本。而热连接 (warm connection),即已经打开并加密的连接,则可以直接发送请求。
我们无法让拉各斯离我们的服务器更近,但可以让连接的建立离拉各斯更近。
挪动握手,而不是服务器
内容分发网络 (CDN) 通常被描述为一种缓存:遍布全球的一组站点,保存文件的副本,以便就近提供。我们使用它的目的不同。对于我们的 API,也就是应用做任何事情时与之通信的接口,缓存被完全关闭。每个请求仍然要到达我们的源站 (origin),也就是 CDN 对其背后服务器的称呼。
改变的是连接开始的位置。手机现在会向最近的边缘节点 (edge location),也称为接入点 (PoP),建立连接:它是 CDN 在全球各地的站点之一,往往只有几十毫秒而不是几百毫秒的距离。边缘节点执行 TLS 终止 (TLS termination),也就是说,来自手机的安全连接在这里结束。随后它复用一池通往我们源站的热连接,这些连接通过 keep-alive 保持打开,这个设置会让连接在两次请求之间保持打开,而不是关闭。两次昂贵的建立往返发生在这一小段短跳上,只有请求本身才会跨越大洋,而且走的是一条已经预热的连接。
三次长途往返变成了一次长途加两次短途。整个思路就是这样。
其余部分我们有意保持平淡:
- 客户端毫无察觉。 应用和网站沿用相同的地址,因此无需发布新版应用,也没有人需要更新任何东西。
- 失败行为保持不变。 超时设置与之前一致,因此以前会失败的情况,现在仍以同样的方式失败。我们不想引入任何新型错误。
- 边缘节点先关闭空闲连接。 每个连接都有一个空闲超时 (idle timeout),即连接闲置多久之后会被关闭。如果我们的服务器关闭了一条安静的连接,而边缘节点仍以为它是开着的,下一个请求就会失败。把边缘节点的超时设得更短,就意味着它总是先放弃,并改为新建一条连接。
- 面向手机的新协议。 手机可以通过 HTTP/3,即网络请求协议的最新版本,进行连接。它运行在 QUIC 之上,QUIC 将两次握手合并为一个往返,并且更能应对移动网络上常见的丢包。
- 不遗漏任何地区。 边缘网络的低价档位往往恰好跳过我们用户所在的那些地区,因此我们选择了完整覆盖。
我们测量了什么
我们在悉尼进行测量,它与我们服务器的距离大致和拉各斯相当,并对比了旧路径和新路径。每条路径,我们都建立了五条全新的冷连接,这是移动网络上最重要的情形,并取中位数 (median),也写作 p50:即居中的结果,一半的请求比它快,一半比它慢。同一个项目还为我们网站的脚本开启了压缩 (compression),此前这些脚本是以原始大小发送的。压缩会在发送之前缩小文件,浏览器收到后再将其还原;我们使用的是 Brotli,一种为网络而设计的压缩格式。
| 测量项目 | 之前 | 之后 | 改善 |
|---|---|---|---|
| TCP 和 TLS 握手完成 (p50) | 490 ms | 59 ms | 快 88% |
| 首字节时间 (p50) | 743 ms | 307 ms | 快 59% |
| 首页脚本体积 | 1.76 MB | 560 KB | 缩小 68% |
59% 就来自首字节时间。过去需要半秒钟才能建立的连接,现在不到十分之一秒就能就绪;而在许多人按每一兆字节付费的网络上,首次访问我们的网站,现在下载的数据减少了三分之二。
这些数据来自一座城市,并不是全球基准测试。不过这笔账同样适用于拉各斯和雅加达,而那里正是我们的用户所在。
难点在于知道是谁在调用
在服务器前面放一个东西很容易。让它背后的各项防护继续正常工作,才是花费大部分精力的地方。
与大多数支付平台一样,Moove 使用速率限制 (rate limiting),即限制任何一个调用方在给定时间内能发出的请求数量,而这些限制是以调用方的 IP 地址,即在互联网上标识一台设备的数字,为键的。在边缘网络背后,每个请求都来自某个边缘节点的地址,而不是用户的地址。如果其他什么都不改变,拉各斯的所有人就会共用同一份额度,一个繁忙的下午就会开始拒绝那些什么都没做错的人。
解决办法基于两个思路。
恰当地信任你拥有的那几层,不多不少。 CDN 是一种反向代理 (reverse proxy),即位于你的服务器之前、并将请求转发过去的服务器。每个代理都会把它收到请求时的来源地址添加到一个名为 X-Forwarded-For 的标准请求头中,因此真正的调用方位于该列表中向前固定数量的步数,即**跳数 (hops)**之处。这个数字弄错,两个方向都是缺陷。数少了,每个用户看起来都像他们的边缘节点。数多了,你就会开始信任由调用方自己写入的条目。因此,我们让这个数字成为基础设施的属性,而不是代码的属性。它在网络改变的同一时刻随之改变;如果这个值不合理,我们的服务会拒绝启动,而不是去猜测。
让边缘节点为调用方作证,并且只相信边缘节点。 边缘节点现在会把真实调用方的地址写到每个请求上,并覆盖调用方自己发送的任何内容。只有当我们的防护机制能够验证请求确实经过了我们的边缘节点时,才会相信这个值。过去以网络地址为键的每一项限制,现在对来自边缘节点的流量都改为以真实调用方为键。我们是在旧规则旁边添加这些新规则,而不是修改旧规则,因此上线过程中的每一项改动都是纯粹的新增,任何人读一遍就能审阅完。
我们在上线前发现的一个缺陷
Moove Profiles 可以放在商家自己的域名上。针对这些域名的请求,会带着商家的域名经由我们的网站转发,这样网站就知道该显示哪个资料页。
网站前面新增的边缘节点会从传入的请求中剥离这类信息,这完全正确,因为访客永远不应该能设置它。但这也意味着,一旦网站移到边缘节点之后,每个商家域名都会悄悄地显示 Moove 首页,而不是商家的资料页。
我们在评审中发现了这一点,先把商家域名的路径移到它自己的路由上,并让我们的基础设施拒绝在尚未完成这一迁移的任何地方应用网站的改动。过去只存在于评审者脑中的错误,现在变成了构建会直接拦下的事情。
总的教训是:当你添加一个会清理输入的层时,要找出所有依赖于你即将清理的那些输入的东西。
我们如何上线
支付基础设施不能“先部署再看”,所以我们先改预发布环境,再改生产环境,并且有几个选择让切换过程平安无事。我们降低了 DNS 记录的 TTL (time to live),DNS 是互联网的目录,将 moove.xyz 这样的名称转换为服务器地址。TTL 是互联网其余部分可以缓存该答案的时长,我们把它从一小时降到五分钟,并且在任何东西移动之前整整一小时就完成了,这样旧的答案已在各处过期,回滚也能在几分钟内生效。我们让地址记录保持在可以原地切换的形式,因为删除再重新创建它们会留下空档,而空档就是故障。每一个有风险的步骤都是一项独立的小改动,并且按顺序写下了撤销它的步骤。
我们会对其他团队说什么
如果你的用户离你的服务器很远,即使不缓存任何内容,边缘网络也值得拥有。在切换之前:
- 列出所有读取调用方地址的东西,包括速率限制、允许列表、欺诈信号、审计日志和安全规则。除非你做出改变,否则它们每一个看到的都是边缘节点的地址。
- 让受信任的层数成为基础设施的一部分,并在它不正确时拒绝启动。
- 让边缘节点记录真实的调用方,并且只在你能验证来自你的边缘节点的流量上信任该记录。
- 确保你的服务器只接受经由边缘节点的流量,并且在强制执行之前先观察。
- 如果你缓存页面,请根据响应实际依赖的所有内容来构建缓存键 (cache key),即缓存用来判断两个请求能否共享同一份已存储响应的那组请求细节。服务器会在 Vary 头中列出这些细节,所以请去读它,而不要凭记忆。
- 在前后都进行测量,并分别查看握手时间和首字节时间,这样你就能区分连接建立的时间和服务器工作所花的时间。
展望未来
我们的大多数页面都是个性化的,所以网站的边缘缓存目前大多处于闲置状态。连接方面的改进对每个页面都适用,而且各个页面现在可以在安全共享时选择开启缓存。缓存 API 响应则需要单独的设计和评审,这是我们接下来想要研究的事情。
资金的流动应该以网络的速度,而不是跨越大洋的光速。
如果你在 Moove 之上进行开发,请阅读我们对 Web3 支付 API 和 SDK 的介绍以及 Moove Developer Program。
由 moove.xyz 工程团队撰写。
关于moove.xyz
moove.xyz是一个全球Web3金融科技平台,专为无需许可、轻松流转的价值移动而建。我们赋能全球各地的企业和消费者,在任意区块链上发送、接收、staking和交换任意加密货币——一切尽在一个平台。
我们是全球首批创新并构建全栈加密支付和去中心化金融基础设施的Web3金融科技公司之一,实现了跨多链钱包访问、个性化钱包标识、跨链代币互换、嵌入式跨链交易和去中心化社交金融网络的综合全面覆盖。我们的核心产品包括Moove Profile、Moove Send、Moove Receive、Moove Stake、Moove Swap、Moove Rewards、Moove Discover等。
我们的使命很简单——为下一个10亿Web3用户创造和分发无需许可的轻松金融技术。我们从根本上相信,货币和价值流动的未来将是无成本、无边界、无需许可、毫不费力并为所有人而建的——我们正在构建终极Web3金融科技平台,将这一未来变为现实。
你的财富。你的选择。






