Lua是OpenResty软件包的一部分,已在我们内部的动态CDN(DACDN)模块中广泛使用。 CDN通常用于快速的内容交付,但是我们内部的CDN既可以作为内容交付,也可以作为数据采集服务,用于收集客户访客的大量数据。由于以高吞吐量收集数据需要复杂的队列机制,因此,我们DACDN的核心部分之一就是将数据包发布到Google Cloud Pub / Sub。
Pub / Sub允许任意数量的发布者发布数据,理想情况下是针对某个主题发布数据,该数据可以由任意数量的订阅者订阅。该消息队列对于我们来说是理想的选择,因为它可以接受高达200 MB / s的发布吞吐量和高达400 MB / s的订户吞吐量。它可以将未确认的数据保留7天,可以通过应用程序级别的确认提供可靠性,并且基于“至少一次”传递语义。 Pub / Sub还具有存储属性的功能,该属性用于以键值格式存储有效负载的元数据。我们在发布/订阅中享受的其他一些好处是:
全球可用性:发布/订阅是一项全球服务,可在所有Google Cloud Zones中使用;在我们的数据中心之间传输数据不会通过常规的互联网提供商进行,而是会使用底层的Google网络。
一个简单的REST API:由于Lua中没有发布/订阅库,因此我们可以使用其REST API快速编写自己的发布者/订阅者。
自我管理:无需创建容量模型或部署策略,也无需设置监视和警报。
当默认情况下lua-nginx模块不提供将消息推送到Pub / Sub的方法时,就会出现挑战。 Google Pub / Sub支持执行HTTPS请求以发布消息。尽管如此,开箱即用的Lua仍不提供发出外部HTTP请求的接口。因此,有一个名为lua-resty-http的维护良好的库,该库可以帮助我们执行相同的操作。使用该库仍无法完全解决我们的问题。我们需要提出一种解决方案,该解决方案可以处理30k请求/秒的吞吐量,同时保持非阻塞行为。为了处理如此高的吞吐量,我们不能只选择一个数据包并对同一数据包执行新的HTTP请求,这将是高度未优化的,这会导致响应时间增加。因此,我们需要设计一种可以满足我们要求的解决方案。
对于在DACDN端收到的每个请求,我们都可以在其中形成有效负载,然后可以分别对每个请求执行HTTP请求。尽管这种方法易于实现且高度直观,但在请求发送者端会遇到高度阻塞的行为,从而增加了响应时间。较高的机器配置和更多的工人可以克服这个问题,但这最终会导致更高的系统成本。此外,根据此Google文档:“无论邮件大小如何,每个发布,推送或拉取请求至少要评估1000个字节。”这意味着小于1000字节的消息仍要为整个1000字节计费,因此也是不经济的。
使用计时器在工作者级别进行数据批处理:与不发布消息的发布方法相比,这种方法将我们的响应时间减少了3倍。即使是此Google文档,也建议在发布请求中推送批量数据,以降低成本。这个想法很简单,但是非常有效。步骤如下:
将此数据包添加到一个缓冲区中,该缓冲区不过是一个Lua表,用于暂时保存数据包
初始化一个将在工作环境之外工作的计时器,使其具有非阻塞性。
使用HTTP连接重用的keep-alive属性从连接池中获取连接,如果不存在,则将自动创建一个新连接。
连接重用或HTTP保持活动:HTTP保持活动或HTTP连接重用是使用同一TCP连接发送和接收多个HTTP请求/响应的想法,而不是为每个单个请求/响应打开一个新的请求对。它通过减少TCP连接的建立和拆除而使用较少的网络流量来提高HTTP性能,并由于避免了初始TCP握手而减少了后续请求的延迟。
在某些情况下,由于吞吐量低,某个主题的批次可能永远不会满,或者填充速度可能很慢。因此,在那种情况下,我们还需要在后台运行一个递归的Nginx计时器,该计时器将在特定的时间间隔内反复触发,并负责检查缓冲区中存在的任何陈旧数据。如果是,则创建这些剩余数据的批处理并将其立即推送到Pub / Sub。
我们要做的第一件事是使用Cloud Pub / Sub测试优化的解决方案,以查看它是否可以处理预期的负载。主要目的是将我们的优化方法与简单的初始蛮力方法的性能进行比较。我们从该解决方案中获得的希望是,该系统将能够长时间处理来自生产者的流量,而不会降低服务质量。
有了新客户之后,我们就可以开始为发布/订阅增加一些负担了。我们将JMeter脚本和Taurus一起用作负载测试工具,以通过DACDN将模拟流量发送到Pub / Sub。在执行负载测试时,使用了以下机器配置:
这是性能测试报告和Stackdriver监视图的屏幕快照,它们的负载吞吐量相同,但其中一个没有优化,而另一个没有优化。
显然,我们首先注意到的是平均响应时间从7ms减少到3ms,这是非常重要的。而且,90%的响应时间从10ms减少到4ms,这是一个巨大的提升。
由于响应时间短,Taurus能够以相对较高的吞吐量在DACDN上发送请求,从而比蛮力解决方案提前12分钟完成了2000万个请求。
而且,经过优化的解决方案比“蛮力”解决方案具有更高的“发送消息操作数”,因此,相同DACDN的资源得到了更好的利用。
两种方法的成本差异明显是2.5倍。因此,使用优化的解决方案以更高的吞吐量发布消息的任何人都将便宜。
我们绝对希望其他lua-nginx开发人员可以从此解决方案中受益,因此我们已将此方法开源为GitHub上的库。您可以从此处获得带有详细文档的库,以帮助您在将该库合并到系统中时获得帮助。如果您觉得可以以任何方式为该库做出贡献,那么作为开源贡献者,我们将非常欢迎您。
它还维护本地缓存,以便可以在令牌到期之前重复使用相同的令牌 结合使用Pub / Sub Rest API和Nginx Timers进行批处理,它有一些明显的优点: