Shopify如何在浏览器之外使用WebAssembly?
Shopify 致力于让大多数商家都需要的功能变得简单易用,并通过接口在 Shopify 平台上执行查询、扩展和更改,进而为商家提供更多可能。借助这些接口,我们丰富的合作伙伴生态系统可以解决诸多问题。这一生态系统主要借助“App”(一个独立托管的 Web 服务)来运作。该 App 通过网络与 Shopify 进行通信。尽管这种模式很强大,但会带来一系列技术问题。我们的合作伙伴需要打造能够随 Shopify 规模扩展的 Web 服务,这让一些本就资源有限的合作伙伴越发捉襟见肘。即便合作伙伴有无限的资源,在与 Shopify 通信时产生的网络延迟也足以让我们的 App 在对时效性要求很高的用例中败下阵来。
我们希望我们的合作伙伴能够专注于利用他们的专长来解决问题,而不用花费时间管理可扩展的 Web 服务。为实现这一目标,我们保留了不受信任的合作伙伴代码的灵活性,并将其在我们的基础设施上运行。为了确保这些代码的性能、安全性与灵活性,我们选择了 WebAssembly 这种通用格式。
什么是 WebAssembly?WebAssembly.org 给出了如下定义:
”WebAssembly(缩写为 Wasm)是一种基于堆栈虚拟机的二进制指令格式。Wasm 是为编程语言设计的可移植编译目标,使客户端和服务器应用程序能够在 Web 上部署。“
如需详细了解 WebAssembly 及其历史,可以浏览由 Mozilla 的 Lin Clark 撰写的这篇图文并茂的文章。本文在此不做详述。
https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/
Wasm 通常都是与 JavaScript 一起在浏览器内运行,但 Shopify 却另辟蹊径,在浏览器之外运行 Wasm,并且不用到 JavaScirpt。作为一款高性能语言,Wasm 绝非 JavaScript 的单纯替代品:它面向 Web 和非 Web 的嵌入而设计,解决了广泛存在于浏览器和代码执行引擎中的一个难题,即如何在不受信任的环境中高效执行程序。Wasm 满足了我们的三大主要技术需求:安全性、性能和灵活性。
运行不受信任的代码具有极大的风险。从本质上来讲,这些代码不仅难以预测,并且还很有可能对整个 Shopify 平台造成损害。尽管市面上并没有百分之百安全可靠的应用,但我们还是要防范安全漏洞,并且在出现问题后采取措施来减轻其影响。
Wasm 将代码执行放到了一个基于堆栈的沙箱环境中,依靠显式导入来与主机进行通信。因此,我们无法在 Wasm 中写入任何恶意代码,只能使用提供的输入端口操作虚拟环境。在这一点上 Wasm 与字节码有所不同,字节码在语法中直接引用了它们希望在其中运行的计算机或操作系统。
Wasm 还有很多不同的功能,可让用户免受错误代码的影响,包括受保护地调用堆栈和运行时类型检查。WebAssembly.org 上提供了更多关于 Wasm 安全模型的详细资料。
https://webassembly.org/docs/security/
在电商领域,较快的运行速度才是商家推动业绩增长时必备的利器。如果 Shopify 提供的功能无法兼顾加载时间和定制价值,那么这种功能压根就没有任何价值可言。
Wasm 本身的设计充分利用了常见的硬件功能,并在各种平台上发挥出最接近原生的性能。它面向追求最高性能、优化浏览器执行的开发者社区。因此,无论是现在还是未来,Wasm 和它的周边工具在设计上都会以性能优化为中心。
能帮助开发者提高开发效率的代码执行服务才是真正有用的服务。Wasm 作为一款字节码格式,与多种编译器相兼容,为代码开发者提供了支持多种编程语言的一流开发体验。这也让我们能够在不改变底层执行模型的情况下,提供多语言支持。
Shopyify 在发展目标和设计方面基本保持一致,这为我们选择 Wasm 提供了技术上的理由,但事实并不仅限于此:我们对 Wasm 的选择不仅关乎于技术,更关乎于人。如果 Wasm 生态系统无人问津,或者它仅在生死线上垂死挣扎,那么我们不会选择它。WebAssembly 的社区是个充满活力的社区,不断创新,它的潜力是无穷的。自从加入这个充满热情的社区,Shopify 就获益匪浅。
同样,我们也在为社区贡献出我们的力量。通过收集用户反馈,探讨功能缺陷,以及为我们使用的开源工具提交代码贡献。我们认为,这为我们与 WebAssembly 社区之间建立良好的互惠合作关系打下了坚实基础,我们也期望着在未来能够继续为这个鲜活的社区献出我们的力量。
在简单介绍过 WebAssembly 以及我们选择它的原因后,下一步就来深入探讨我们的运行方案。
我们使用的是最初由 Fastly 开发的开源工具 Lucet。Fastly 这家公司为大批量寿命不长且不受信任的模块提供了一个可编程的边缘云平台,让它们可以在尽可能接近发起请求的地方执行请求。这与我们的合作方提供的代码所面临的问题相同,因此,我们自然而然就选择了 Lucet。
Lucet 是 Wasm 的运行时和编译器。Wasm 中的模块确保了系统的安全性,由于我们无法在 Wasm 中写入恶意代码,因此 Lucet 利用 Wasm 模块的验证进行安全检查。在验证之后,模块会被编译为一个可执行的文件,其性能可以达到原生状态。另外,Wasm 还支持提前编译,可避免执行运行时编译带来的延迟。Lucet 容器在启动时无需执行任何操作,这让它拥有了令人惊叹的 35μs 启动时间。如果您对 Lucet 及其工作原理感兴趣,可以去看看 Fastly 的 CTO Tyler McMullan 的演讲视频。
https://www.youtube.com/watch?v=QdWaQOgvd-g
Shopify 中 Wasm 引擎的工作原理流程图
我们将 Lucet 包装在一个管理 I/O 和模块存储的 Rust Web 服务里,并将其称作是 Wasm 引擎。在运行时,Shopify 通过 Web 请求调用 Wasm 引擎以处理部分功能。引擎之后再调用站点的上下文中应用输出,这里的上下文可能会涉及到创建折扣、执行约束,或者是任何商家想要在平台中私人定制的同步服务。
下图中是我们在最近一次的性能测试中提取到的一些指标。我们选择了一个很小的功能及逆行测试:让模块对用户购物车中添加的物品数量进行限制。在测试期间,每分钟执行十万个模块,持续时间约 5 分钟。
模块执行所需时间
该图表展示了执行一个模块所需时间的详细情况,其中包括容器的 I/O 和模块的执行。y 轴代表时间(单位:ms),x 轴代表测试运行的具体时间。
图中的浅紫色图例代表 Lucet 中执行模块需要的时间,其宽度大约在 100μs 左右徘徊。其余图标则是 I/O 的处理和引擎的具体情况,可以看出执行的全部时间大约在 4ms 左右。所有的时间显示都是第 99 位百分比(99p)。为了能更好地理解图中时间的含义,下面让我们将用 Shopify 中性能卓越的在线商店渲染服务:Storefront Renderer 的测试请求时间做比较。
Storefront Renderer 响应时间
这张图表中展示了 Storefront Renderer 在一段时间内的请求时间。y 轴代表请求时间(单位:秒),x 轴代表返回数值时的具体时间。浅蓝色线条代表在 700 毫秒左右的第 99 百分比。
如果将模块处理时间大致估算在 5 毫秒内,那么可以说 Lucet 执行时间带来的性能影响几乎可以忽略不计。
为了让我们性能卓越的执行引擎发挥作用,我们还需要授权开发者创建兼容的 Wasm 模块。Wasm 的作用并不是让用户亲自编写(想写当然是可以写的)代码,而是作为一个编译目标存在。这就会让我们思考以下问题:我们支持哪些编程语言,具体又要支持到什么程度。
理论上来说,任何有 Wasm 支持的开发语言都是可以的。但是,我们更希望开发者可以将精力集中在为商家解决问题上,而不是研究要如何符合我们的 API。这也是我们选择单一语言 Ruby 支持,并为开发者提供快速启动工具的原因。然而,由于 Ruby 动态语言的特性,我们并不能将其直接编译为 Wasm,而涉及编译解释器的解决方案会有严苛的性能惩罚。正因如此,我们最终决定采用静态编译的语言,并将动态语言编译的可能性留待未来。
通过我们的调研发现,Shopify 生态系统中的开发者大多能对 JavaScript 熟练应用。可惜的是,由于 JavaScript 与 Ruby 一样是动态语言,只得被排除在外。最终,我们选择了一种语法类似于 TypeScript 的开发语言:AssemblyScript。
虽然 WebAssembly 支持大量开发语言,但其中有两大类编译器是我们无法使用的:
生成环境或开发语言特定产物的编译器,即节点或浏览器。(例如 Asterius、Blazor)
只适用于特定运行时的编译器。这些编译器生成的模块依赖于特定语言的特定导入,通常是为了支持某些特定语言的标准库,让他们能够在系统调用或运行时功能可用而存在的。因为我们并不想被锁死在某一特定语言上,所以这类编译器就不在我们的考虑范围内了。(例如 Lumen)
这些功能强大的编译器在其他情况下或许能够发挥奇效,但可惜无法为我们所用。我们需要能够生成 WebAssembly 的工具,而不是由 WebAssembly 支持的工具。AssemblyScript 便是被我们选中的工具。
与 WebAssembly 中的其他工具一样,AssemblyScript 还在开发过程中。它缺乏一些诸如闭包支持等关键功能,在边缘情况下仍会报错。这时候就显现出了社区的重要性。
开发语言 AssemblyScript 和它的周边工具拥有一个用户活跃的爱好者和维护者社区,自从 2019 年 Shopify 首次使用 AssemblyScript 以来,他们就一直在支持着我们。而我们也通过 OpenCollective 长期贡献代码以支持社区。我们编写完成了一个语言服务器,在实现闭包方面也取得里一些进展,也为编译器和周边工具提供了错误修复。
我们还将 AssemblyScript 融入了我们早期的工具之中。在 Shopify CLI 中,我们通过集成 AssemblyScript,允许开发者通过命令行创建、测试和部署模块。为了提高开发效率,我们提供了可以处理 Shopify 定义对象(例如“Money”)底层实现的 SDK。除了这些工具,我们还搭建了一个允许合作伙伴监控模块的系统,方便他们在模块出现故障时收到警报。我们的最终目标是让合作伙伴们能够在不失去代码在原生平台上灵活性和可观察性的前提下,将他们的代码迁移到我们的平台之上。
通过连接商家和合作伙伴,Shopify 做到了商家与企业的对接,解决了双方各自面对的问题。如果你对我们的代码执行服务感兴趣,觉得这些对您或者您的 App 很有用,欢迎在推特上 @ShopifyEng。如需了解更多关于 Shopify 及我们 App 的信息,请访问我们的开发者页面。
https://shopify.engineering/shopify-Webassembly
很多想成为架构师的程序员,苦于无法经历大规模的案例,我们拜托李智慧老师,甄选一些顶级技术大会上的优秀架构案例资料,其中有他自己的分享内容,也包括其他优秀讲师的资料,一同打包提供给你,帮你增长架构案例经验,锻炼自己的架构思维。
现在免费分享给你价值¥999 的包括淘宝架构演进、饿了么异地多活、小米海外电商架构演进等等大厂架构案例,还有 24 本架构师电子书、 原创架构师技能图谱等你领取。