此文系转载,原作者为 MigratoryData 公司 CTO Mihai Rotaru 。
对于需要实时通信的网站,使用 RESTful HTTP 请求响应方法可能显得极不高效。我们提出了一种新方法,并通过一种需要实时通信的功能对其进行了验证,这种功能已经众所周知,并在很多网站中有所运用:搜索框自动补全。
作为最繁忙的搜索平台,根据 Internet Live Stats估算,谷歌每秒钟大约要处理40,000 次用户搜索。假设在每次搜索中自动补全功能产生6 个请求,我们的实验表明MigratoryData 只需一台1U 服务器即可应对该负载。
准确来说,我们证明了通过1U 服务器运行的一台MigratoryData 服务器可以处理 1 百万并发用户产生的每秒 240,000 个自动补全请求,并实现平均 11.82 毫秒的往返延迟。
当前采用的方法及其局限
自动补全功能可以在用户通过搜索框输入查询的过程中提供搜索建议。目前所用的方法主要基于 HTTP 请求 - 响应模式,用户每输入一个字符,均需要向 Web 服务器发送一次 HTTP 请求,并通过 HTTP 响应获得搜索建议。这种方法有两个局限:带宽和延迟。
一方面,由于每个自动补全请求只包含少量字节(例如用户在搜索框中输入的字符),但浏览器会自动添加数百字节内容作为 HTTP 标头。对于用户搜索活动频繁的网站,大量额外的数据意味着带宽的巨大浪费,同时还需要耗费额外的 CPU 周期以处理这些不必要的 HTTP 标头。
另一方面,对于每个 HTTP 请求,都需要在用户和 Web 服务器之间新建一个 TCP 连接,甚至可能需要进行 TLS/SSL 握手。随着用户输入每个字符都进行这样的操作会对延迟产生极高影响(例如用户输入一个字符到看到搜索结果之间的等待时间)。为了消除这种局限可以使用 HTTP 保活(Keep-Alive)连接,借此在一次超时期间通过同一个 TCP 连接发送多个 HTTP 请求。然而尽管如此,当超时值到期后依然需要新建连接。
另一种新方法
为了解决上文提到的 RESTful HTTP 方法所面临的局限,很多人选择使用 WebSocket 协议代替 HTTP。WebSocket 协议在开销方面只增加几字节数据,因此相比 HTTP 协议数百字节的数据增量,可大幅降低开销。更重要的是,WebSocket 协议按照设计可使用持久连接,无需定期重连,可实现更低延迟的通信。
目前有很多 WebSocket 协议的服务器实现,然而相比 RESTful HTTP 方法,这些实现在带宽优化方面做的都略显不够,并非所有 WebSocket 服务器实现能提供同等程度的低延迟和可缩放性。
注意 – 相比 Web 服务器,WebSocket 协议本身无法保证服务器能获得更好缩放性或更低延迟,该协议只能提供实现这些特性的前提。缩放性和延迟的程度取决于具体的 WebSocket 服务器实现。
MigratoryData Server 就是一种此类 WebSocket 服务器实现。该产品是成功解决 C10M 问题(单一服务器上千万并发用户)的首个服务器实现。
MigratoryData 提供了一套通用 API,并为大部分主流编程环境,包括 Web 应用程序提供了所需的库。该产品可暴露一种基于主题(Subject)的发布 / 订阅通信范式,根据所采用的发布 / 订阅模式,还可暴露下列异步请求 / 响应模型:
- 一个生成方订阅至主题 X
- 消耗方发送包含主题 X 的请求消息并附加回复主题 Y(如果尚未订阅,消耗方可自动订阅主题 Y)
- 生成方收到该消息后,从请求消息中提取出回复的主题 Y,并使用包含主题 Y 的消息作为回复
下文我们将展示这种通过 WebSocket 实现的请求 / 响应交互如何作为可缩放的方法取代 RESTful HTTP。
性能评测环境配置
我们使用了四台完全相同的计算机,每台装备 2 颗 2.60GHz 主频 Intel Xeon E5-2670 CPU,以及 64GB 内存:
- 计算机 A 运行一个 MigratoryData Server 5.0.20 实例
- 计算机 B 和计算机 C 运行两个 Requestor 工具实例,分别用于打开 500,000 个并发 WebSocket 连接,自动补全请求将通过这些连接发送
- 计算机 D 运行 16 个 Provider 工具实例,用于为每个自动补全请求提供搜索建议
四台计算机均运行 CentOS Linux 7.2,使用默认的 3.10.0-327.28.3.el7.x86_64 内核,未进行任何内核调优。
为了模拟一百万用户中的一位用户 N 发送一条自动补全请求,Requestor 工具会随机选择十六个 Provider 所订阅的某一主题,例如 /s/M。此外 Requestor 工具会将用户 N 订阅至主题 /c/N(如果尚未订阅),并将具备下列属性的请求消息发布至 MigratoryData Server:
- 主题:/c/N
- 主题:/s/M
- 载荷:一个代表搜索查询的 32 字节随机字符串
订阅至主题 /s/M 的 Provider M 将收到上述消息,通过向 MigratoryData Server 发布具备下列属性的回复消息即可作出回应:
- 主题:/c/N
- 载荷:一个代表搜索建议的 256 字节随机字符串
由于用户 N 已订阅至主题 /c/N,便可收到上述回复信息。往返延迟将按照请求消息的创建完成到用户最终收到回复信息之间的时间差来计算。
注意 – 请求 - 回复通信的往返延迟包含请求消息从 Requestor 传递至 MigratoryData Server,随后传递至 Provider 所需的时间,外加回复消息从 Provider 传递至 MigratoryData Server,并最终传递至 Requestor 所需的时间。
最后需要注意,在上述环境中,有多个代表搜索服务的 Provider 实例对请求进行均衡。该架构使得搜索服务(包括其搜索缓存)能够横向缩放并模拟 RESTful HTTP 方法,此外还可通过多个搜索服务对请求进行均衡。
结果总结
每秒钟,两个 Requestor 实例为从一百万并发用户中随机选择出的 240,000 个用户处理 240,000 个自动补全请求,获得搜索建议所需的平均往返延迟为 11.8 毫秒,其中第 95 百分位(95th percentile)延迟为 20 毫秒,第 99 百分位(99th percentile)延迟为 130 毫秒(通过超过 40 亿个请求的结果计算而来)。
指标
数据
并发 WebSocket 连接数
1,000,016
订阅主题数
1,000,016
每秒请求数
每秒请求 240,000 条消息
消息总吞吐率(Requestors 与 Providers 收和发)
每秒 960,000 条消息
平均延迟
11.82 毫秒
标准偏差延迟
26.28 毫秒
第 95 百分位延迟
20 毫秒
第 99 百分位延迟
130 毫秒
最大延迟
1783 毫秒
请求总数
4,084,890,291
硬件
一台 1U 服务器,装备 2 颗 2.60GHz 主频 Intel Xeon E5-2670 CPU 与 64GB 内存,Intel X520-DA1 10 GbE 网络适配器
操作系统
CentOS Linux 7.2,默认内核 3.10.0-327.28.3.el7.x86_64(未进行内核调优)
Java 运行时环境
Oracle 1.8.0_40-b25
入站网络利用率(Providers 与 Requestors 总和)
每秒 1.06Giga 字节
出站网络利用率(Providers 与 Requestors 总和)
每秒 1.17Giga 字节
CPU 利用率
65%
结果
MigratoryData Server 可通过 JMX 和其他协议进行监视。我们使用 jconsole 工具(包含在 Java Development Kit 中)进行 JMX 监视。下列屏幕截图截取自 JMX 监视过程。
连接和消息
正如评测环境介绍中所述,我们通过两个 Requestor 实例创建了 1,000,000 个到 MigratoryData 服务器的并发 WebSocket 连接,借此模拟一百万用户。以百万用户中的每个均订阅至不同的主题,随后通过这些主题获得搜索建议。此外我们使用 16 个 Provider 实例打开 16 个到 MigratoryData 服务器的连接,借此模拟搜索建议服务。这 16 个服务中的每个均订阅至不同主题,借此响应自动补全请求。如下图所示,JMX 的 ConnectedSessions 属性也显示出共有 1,000,016 个并发连接。
在评测环境中,用户每秒发出 240,000 条请求消息。因此每秒传入 MigratoryData 服务器的消息总数包含来自 Requestors 的每秒 240,000 条请求消息,外加来自 Providers 的每秒 240,000 条回复消息。
另外每秒传出 MigratoryData 服务器的消息总数为每秒发送给 Requestors 的 240,000 条回复消息,外加每秒发送给 Provides 的 240,000 条请求消息。
这些总数(每秒 480,000 条传出消息外加每秒 480,000 条传入消息)对应了下列截图中 JMX 的 OutPublishedMessagesPerSecond 和 InPublishedMessagesPerSecond 属性。
因此 MigratoyData Server 处理传入和传出消息的总吞吐量约为每秒 1 百万条消息。
最后需要注意,该性能评测是在大致 5 小时内进行完毕的。按照每秒 240,000 个请求的速度,MigratoryData 共处理了超过 40 亿个请求!
CPU 和内存利用率
从截图中可以看到,评测过程中 CPU 用量始终低于 70%。分配给 JVM 的内存最大值为 30GB。最后,由于整个评测是在大约 5 小时内进行的,因此内存和 CPU 用量均呈现出规律性变化。
延迟
如上文评测环境介绍中所述,往返延迟是请求从 Requestor 传递至 MigratoryData 服务器,后传递至 Provider 所用时间,外加回复消息从 Provider 传递至 MigratoryData 服务器,并最终传递至 Requestor 所用时间总和。
从该评测中我们计算了每个请求 / 回复交互的往返延迟,共得出超过 40 亿个延迟值。此外我们还计算了延迟的平均值、标准偏差,以及最大延迟值。这些有关延迟的统计信息会随着每次新产生的请求 / 回复交互递增,结合超过 40 亿个延迟值汇总计算而来。简单总结延迟情况如下:
- 平均延迟:11.82 毫秒
- 标准偏差延迟:26.28 毫秒
- 最大延迟:1783 毫秒
另外我们还使用 HdrHistogram 库计算了延迟的百分位数。在下图中可以看到请求数(百万计)和往返延迟(毫秒计)在不同百分位下的分布。
例如在上图中可以看到,第 95 百分位的延迟为 20 毫秒,第 99 百分位延迟为 130 毫秒。因此对于共 40 亿请求中的 38 亿个请求,往返延迟均低于 20 毫秒;而对于共 40 亿个请求中的 39.6 亿个请求,往返延迟均不超过 130 毫秒。
注意 – 通过进一步优化还可降低第 99 以及更高百分位的延迟。这些值通常会受到 JVM 垃圾回收机制的影响。在之前针对另一个场景进行的性能评测中我们发现,使用 Azul Systems 的 Zing JVM 对垃圾回收进行优化后,可以将第 99 百分位的延迟从 585 毫秒降低至 25 毫秒,最大延迟值则从 1700 毫秒降低至 126 毫秒。
结论
本文中,我们为有大量用户、高频请求,以及 / 或需要低延迟通信的网站提出了一种新的通信架构,并通过搜索框自动补全功能这个用例进行了证实。
我们发现可缩放的 WebSocket 服务器提供了更易用的编程模型,例如发布 - 订阅,可以很好地取代目前所采用的 RESTfull HTTP 架构,在延迟和带宽使用方面均有更出色的表现,同时在编程的复杂度方面也基本持平。
作者: Mihai Rotaru ,阅读英文原文: A Scalable Alternative To RESTful Communication: Mimicking Google’s Search Autocomplete With A Single MigratoryData Server
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论