Shopify如何动态地引导店面流量

129 阅读10分钟

Shopify如何动态地引导店面流量

在2019年,我们着手重写Shopify店面的实施。我们的目标是让它更快。我们在之前关于优化服务器端渲染和实施高效缓存的文章中谈到了我们用来实现这一目标的策略。在这篇文章的基础上,我将详细介绍Storefront Renderer团队如何调整我们的负载均衡器,在传统店面和新店面之间转移流量。

首先,让我们来看看我们使用的技术。对于我们的负载平衡器,我们正在运行带有OpenResty的nginx。我们以前讨论过,可编写脚本的负载平衡器是我们在高流量高峰期生存的秘密武器。我们用Lua模块建立了我们的店面验证系统,并使用该系统来确保我们的新店面与我们的传统店面达到同等水平。一旦实现平价,将流量永久转移到新店面的系统也是用Lua建立的。我们的聊天机器人,spy,是我们通过控制平面与负载均衡器互动的前端。

在项目开始时,我们预测需要不断地更新哪些请求被新的店面支持,因为我们继续迁移功能。我们决定建立一个规则系统,使我们能够轻松地添加新的路由规则。

一开始,我们把规则保存在nginx仓库的Lua文件中,并在控制平面中保持启用/禁用状态。这使我们能够快速禁用一个规则,而不需要在出错时等待部署。这被证明是成功的,在项目的这个阶段,启用和禁用规则是很容易的。然而,我们改变规则的工作流程很繁琐,我们希望这个过程能更快。我们决定将整个规则作为JSON有效载荷存储在我们的控制平面中。我们使用spy来创建、更新和删除规则,此外还有之前的启用和禁用规则的功能。我们只需要部署nginx来增加新的功能。

动态规则的力量

快速的持续集成(CI)时间和部署是提高变化进入生产的速度的好方法。然而,对于像我们这样时间紧迫的用例,完全取消CI时间和部署甚至更好。将规则系统移入控制平面,并使用spy来操作规则,这就取消了整个CI和部署过程。我们仍然需要在启用spy命令或启用新命令之前进行 "代码审查",但与之前使用的完整部署过程相比,这只是一个微不足道的时间。

在深入了解配置的不同选项之前,让我们看看用spy创建规则是什么样子的。下面是三张图片,显示了创建规则、检查规则、然后删除规则。该规则从未被启用,因为它是一个例子,但这个过程需要得到团队中另一个成员的批准。我们在运行命令时影响了Shopify平台上很大一部分的真实流量spy storefront_renderer enable example-rule ,所以对好的代码审查的规则仍然适用:

用spy添加一个规则

用spy显示一个规则

用spy删除一个规则

配置新规则

现在让我们回顾一下创建新规则时可用的不同选项:

选项名称
说明默认值例子
规则名称
规则的标识符。
产品-json
筛选器
一个用逗号分隔的过滤器列表。
is_product_list_json_read
商店名称
一个用逗号分隔的商店ID列表,该规则适用于这些商店。
所有

rule_name是我们使用的标识符。它可以是任何字符串,但它通常是描述它所匹配的请求的类型。

shop_ids 选项让我们选择让规则针对所有商店或针对特定商店进行测试。例如,测试商店允许我们在不影响真实生产数据的情况下测试变化。这对于测试用新店面渲染实时请求很有用,因为验证请求是在后台发生的,不会直接影响客户请求。

接下来,filters 选项决定了哪些请求会符合该规则。这使我们能够将流量划分为较小的子集,并针对我们传统的Ruby on Rails实现中的单个控制器动作。对过滤器列表的改变确实需要我们经历完整的部署过程。它们被保存在一个Lua文件中,过滤器选项是一个以逗号分隔的函数名称列表,以函数风格应用于请求。如果所有的过滤器都返回 "true",那么该规则将与该请求匹配。

上面是一个过滤器的例子,is_product_list_path ,它让我们针对HTTP GET请求到用Lua实现的店面产品JSON API。

选项名称
说明
默认值
例子
渲染率
我们渲染允许的请求的速度。
0
1
验证率
我们验证请求的速度。
0
0
反向验证率(reverse_verify_rate
当从新店面渲染时,请求被反向验证的比率。
0
0.001

Both render_rate 和 ,允许我们以符合规则的流量的百分比为目标。这对于逐步推广渲染一个新的端点或验证生产流量的小样本很有用。verify_rate

reverse_verify_rate 是当一个请求已经被新店面渲染时使用的比率。它让我们首先用新的店面渲染请求,然后将请求异步地发送到传统的实现中进行验证。我们称这种情况为反向验证,因为它与原来的流程相反或相反,即请求由传统店面渲染,然后被发送到新店面进行验证。我们把这种相反的流程称为正向验证。当我们实施新的端点时,我们使用正向验证来发现问题,而反向验证则帮助检测和跟踪回归。

选项名称

说明

默认值

例子

自我验证率

我们在最近的地区验证请求的速度。

0

0.001

现在是介绍自我验证和相关self_verify_rate 的好时机。传统店面实施的一个限制是由于我们对Shopify pod的架构意味着在任何时候只有一个区域可以访问MySQL写入器。这意味着,所有的请求都必须到一个pod的活动区域去。有了新的店面,我们将店面渲染服务与数据库写入器解耦,现在可以从任何有MySQL副本的区域提供店面请求。

然而,当我们开始解耦对活动区域的依赖时,我们发现自己不仅想根据传统的店面来验证请求,而且还想根据新店面的活动和被动区域来验证。这导致我们添加了self_verify_rate ,允许我们对活动区域的请求进行抽样,以便对本地区域的店面部署进行验证。

我们发现路由规则很灵活,它使我们很容易添加新的功能或原型,而这些功能或原型通常是相当难以推出的。你可能很熟悉我们如何产生负载来测试系统的极限。然而,如果系统不堪重负,这些负载测试往往会成为我们的负载削减器的受害者。在这种情况下,我们会放弃任何来自我们的负载发生器的请求,以避免对真正的客户体验产生负面影响。在BFCM 2020之前,我们想知道如果与我们的依赖关系(主要是Redis)的连接发生故障,应用程序会有什么表现。我们希望尽可能地对这些类型的故障有弹性。这与使用负载生成工具的测试不太一样,因为这些测试可能会影响真实的流量。团队决定建立一个全新的店面部署,我们使用验证器机制向它发送重复的请求,而不是将任何流量路由到它。然后我们将新的部署与Redis断开,并将我们的负载发生器打开到最大。现在我们有了关于应用程序在部分中断情况下的表现的数据,并且能够在BFCM之前挖掘和改善系统的弹性。这些只是我们利用我们灵活的路由系统来快速和透明地改变底层店面实施的一些方法。

实施

我想通过店面Lua模块的主要入口来告诉你更多的技术实现。首先,这里有一张图,显示了请求处理过程中每个nginx指令的执行位置。

nginx指令的运行顺序 - 来源:github.com/openresty/l…

在重写阶段,在请求被代理到渲染服务之前,我们检查路由规则以确定请求应该被路由到哪个店面。在头过滤阶段的检查之后,我们检查请求是否应该被正向验证(如果请求去了传统的店面)或反向验证(如果它去了新的店面)。最后,如果我们在日志阶段对请求进行验证(不管是正向还是反向),我们会在请求周期结束后,排队向相反的上游发送一份原始请求的副本。

在上面的代码片段中,在重写阶段和头过滤阶段引用的渲染器模块以及在头过滤阶段和日志阶段引用的验证器模块,使用下面店面规则模块的相同函数find_matching_rule ,从控制平面获得匹配规则。routing_method 参数被传入,以确定我们是在为渲染寻找匹配的规则,还是为验证当前请求寻找匹配的规则。

最后,验证器模块使用nginx的定时器来发送验证请求,这样我们就不会让客户端同时等待两个上游。下面所示的send_verification_request_in_background 函数负责排队发送验证请求。为了复制请求并验证它,我们需要跟踪原始请求参数和来自传统店面(在正向验证请求的情况下)或新店面(在反向验证请求的情况下)的响应状态。然后,这将把它们作为参数传递给定时器,因为在定时器的上下文中,我们将无法访问这些信息。

路由规则的未来

在这一点上,我们开始简化这个系统,因为新的店面实现几乎为所有的店面流量服务。一旦迁移完成,我们将不再需要用传统的店面实现来验证或渲染流量,所以我们将撤销已经完成的工作,回到项目早期的硬编码规则方法。虽然这并不意味着路由规则会完全消失,但路由规则所提供的灵活性使我们能够建立验证系统,并为负载测试建立一个单独的部署。这些功能在以前的传统店面实施中是不可能实现的。虽然我们不会改变店面实施之间的路由,但规则系统将不断发展以支持新的功能。