Tobias Lutke仍在为Shopify编写代码

2020-11-06 09:28:02

马克西姆,几周前我们邀请你的同事西蒙·埃斯基尔德森上节目,第412集…时,我们曾调侃过这段对话。但他是来这里谈论餐巾纸数学的,他是Shopify店面重写的一部分,但我们不想和他谈这件事,因为我们知道你会来,我们想和你谈谈,…。所以首先,感谢你加入我们的变化日志来谈论你的这个项目。

因此,重写任何东西都是一件大事,尤其是像Shopify这样大、强大、成功的东西。我认为人们关注的主要新闻是巨石与微服务的对比,因为Shopify一直是大规模Rails巨石的典范,对吧?

是的。我认为,这是我们每天都在学习的东西,我们越多地弄清楚什么应该放在这个整体中,什么不应该放在这个整体中。这在某种程度上促使我们决定将这个特定的领域分割成一个单独的应用程序。

嗯。所以,也许当我们为这次对话奠定基础时,我们将经历重写的决策过程,我们将经历真正完成它的过程,因为这是几年的努力,在规模上你必须缓慢而谨慎地前进。你们用一些Nginx Lua重写了一些非常酷的东西,以确保你们在做的过程中是正确的-我们将深入研究这些细节。但首先,可能在您开始之前,先为巨石的外观打好基础,这一切都牵涉到什么。…。人们知道Shopify是一个电子商务平台,在那里人们可以经营自己的在线商店,所以它是多租户的…。我想我们的很多听众都知道Shopify是什么,但它看起来像什么,它都在做什么?

[]对。Shopify大约在15年前就开始了,这一切都是从一个简单的Rails应用程序开始的,宏伟的整体方法,其中所有的东西都会集成到一个应用程序…中。15年后的今天,我们仍然在使用它,这是大多数Shopify开发人员倾向于将代码发送到的主要位置。

当然,在进行扩展时,您会遇到一些挑战:如何处理数百名在同一平台上工作的开发人员,大规模交付代码库,但同时也要处理这些问题。当我们最终遇到无法使用Rails的情况时,因为它已经进入了基本形式,所以我们达到了使用基本Rails所能做的极限。

正如你所提到的,Shopify是一个多租户应用程序,所以只需处理它的影响,并确保数据不会跨租户访问-这类事情需要对整体进行一些补丁。因此,整体是一个应用程序,其中大部分代码都在那里。我们现在将其拆分成单独的组件,以便在整体中具有特定于业务领域的组件,这使得例如,店面和在线商店专门是这个自己的组件,其中所有内容都将放在该目录中。所以它给了我们一种很好的方式来在不同的组件之间创建这些边界,这样就不会有跨组件的访问,希望最终所有的东西都必须在每个组件之间进行接口,并且没有冲突。

我们刚刚在Shopify Engineering博客上发表了一篇文章,解释了我们如何开始加强这些组件之间的界限,以确保我们不会遇到不同类名的问题,这些问题没有意义,或者诸如此类的事情。

因此,这些是通过…实现的代码边界。您仍然处于进程或代码库的内部;这些不是通过网络对话的服务,对吗?

正确的。同样的过程,正确。比方说,这实际上更像是一种开发者体验,而不是网络拓扑。

好吧。因此,如果我们谈到巨石的各个部分,我们提到的是店面;你可以准确地解释什么是店面。然后是管理员。如果有人经营一家Shopify商店,他们至少在一定程度上知道管理是什么;当然,还有更多的事情要做。这里有支付处理,结账部分…。所以这里有一些合乎逻辑的部分。我错过了什么吗?我肯定我错过了一些。我敢肯定后端…上有很多。

当然,帐单就在那里,有支付处理,就像你提到的;当然还有店面,所有关于定价和退货的东西--这类东西都被分成了自己的组件,通常会与拥有该组件的特定团队合作来构建它。因此,这些组件的工作方式本质上就是每个组件中的一个小型Rails应用程序,它有自己的应用程序目录、测试以及类似于包装在这一个组件…中的所有东西。而网上商店就是其中之一,提供店面专用的东西。

好吧。所以值得注意的是,店面,这是这里讨论的话题,已经被重写了,我们将在这里经历一个艰难的过程。它仍然是一款红宝石应用程序…。

它仍然是一款Rails吗--是它自己的Rails应用程序,还是与之有什么不同?

[]所以我们从一个基本的Ruby应用程序开始。我们使用的是Rails的某些部分,但不是直接使用Rails本身。原因是Shopify店面的实现方式并不真正符合Rails应用程序通常使用的CRUD模式。例如,如果你进入Shopify店面,你就会进入索引页。您将拥有索引页、产品页、集合页以及在店面上看到的所有不同端点。

现在,所有这些都可以实现为要呈现的Rails操作,但是从头开始我们意识到我们并不需要Rails提供的所有东西,我们可以首先使用Ruby应用程序来简化这一点。

所以店面在某种程度上是整个堆栈的简化部分,就像-我现在假设的,所以请纠正我-它接受一个请求,然后基本上一旦它确定它是哪个店面,然后它就会说“好的,得到我所有可以由店面所有者写入的模板(它们是由拥有该主题的任何人或者它碰巧是什么的流动模板),抓取那些模板,抓取所有的数据,将它们合并在一起,并吐出一些HTML”,以最简单的形式,这就是它想要做的。

这就是事实。正因为如此,该特定应用程序的目标是做得非常好,非常迅速,而且是规模化的。因此,因为我们不一定想要与管理员相同的性能标准,例如,分离该应用程序给了我们一种Unix哲学,即“做一件事,把它做好”,让特定的服务去做。

那么重写的目的是什么呢?你提到有三个方面是你想通过这个过程来实现的。

正确的。当然,成功的标准--第一个标准是确保我们在新的应用程序中拥有与旧应用程序相同的功能和行为。通过这种方式,我们说对于给定的请求,对于相同的输入,如果两者都被视为黑盒,则得到相同的输出,并且无论您提供给它们的是什么输入,它们的行为方式都是相同的。这就是我们使用验证器机制的地方,以确保对于给定的输入,我们得到相同的输出,并确保我们永远不会向买家提供不等同的东西,这是不正确或无效的。

当然,第二个目标是提高绩效。通过新应用程序提高性能,我们能够真正关注并深入了解我们为此应用程序设置的性能标准,而不仅仅是应用程序本身的基础架构;我们已经考虑了在未来10年、20年或30年内,我们希望如何设置Shopify店面,以随Time…进行扩展。例如,在主动-主动复制设置上运行允许我们从不同位置读取数据,而无需走遍世界各地(如果我们不必使用…。考虑到我们如何在Ruby应用程序中编写代码,我们使用了通常不会使用的不同Ruby模式;所以它不是真正惯用的Ruby,而不是像往常一样编写漂亮的Ruby。我们正在考虑最终确实会对业绩产生影响的某些事情。像思考下面的内存分配这样的事情,我知道我在使用Ruby之前肯定没有做过,但现在我们考虑这些事情,以确保我们没有做任何事情-基本上,确保我们在做正确的事情来提高性能。

最后一个是提高抗震力和能力。所以Shopify有一个主要的,有点像超级碗的一年中的一部分,那就是黑色星期五和网络星期一…。

[]是的,大概一个月后,现在是…。甚至两个月,我想。11月底。这通常是我们发现一年中所做的一切是否足够好的时间和地点。到目前为止,一切都很顺利。今年将是我们用这个新的实现为平台上的大部分店面提供动力的第一年。所以这是我们的第一个真正的比赛日,在野外的真正的白天活动。希望一切顺利;这可能是著名的最后一句话,但希望一切都很顺利,我们做得很好。

但这是这个应用程序的第三个目标,基本上是利用我们在整体上拥有的东西,并使它变得更好,因为我们只针对这一个用例进行了优化。

在这一点上,我会有一个很大的问题,当你决定“这是我们的目标,它们围绕着性能,它们围绕着可扩展性和弹性”,而你提取的这一部分-让我们称之为有限的范围;我相信它非常复杂,但从逻辑上讲是有限的范围(…。您是否考虑过其他语言、其他运行时?

是的,我们做到了。有一件事让我们决定继续使用Ruby:1)所有的Shopify都在Ruby上。因此,就开发人员知识转移而言,这是作为一个组织最容易做到的事情。另一件事是,我们用来呈现店面的Liquid库是基于Ruby的,所以在项目一开始就应该保留运行时。

我们现在开始看到的另一件事是,我们刚刚开始探索Ruby的替代运行时。TruffleRuby是我们正在尝试探索的东西,看看这是否有助于提高店面渲染的性能。

因此,这并不是我们真正想要摆脱的事情。我们致力于Ruby,致力于Rails,这个决定在今天仍然有意义。也许最终我们会开始考虑一个不同的运行时,但到目前为止,它一直在为我们工作。

嗯,你必须重写的东西的数量--你的重写已经足够危险了,我们都知道这是一次大重写的背信弃义。这是一个相当大的重写,你花了两年时间,从头到尾。你不是百分之百确定,但你已经很接近了,对吧?

想象一下,如果你不得不用一种新的语言重写Liquid,那还需要多长时间。我相信你在整个Shopify中都会将大量的共享代码汇集到新的店面中,你只需要在上面搭建就可以了。这就像是从一堆乐高积木开始;如果你必须完全切换语言,你必须构建每个乐高积木,而且你可能永远也完成不了。

一点儿没错。我认为在这个范例中,将所有东西提取到这个不同的应用程序中是第一步。一旦做到这一点,我们就能够利用它,最终做一些不同的事情。当然,第一步是把所有东西都拿出来,把这个孤立的东西拿出来,然后我们就可以玩弄和试验…了。一旦你把它作为一项单独的服务,你就可以真正拥有更小的范围,而不是试图将不同的语言整合在一起,这肯定会更难做到,这样做就容易得多了。

我很好奇为什么要选店面。或者这只是你的团队的项目。这块巨石的每一部分都会得到类似的处理吗?或者他们会将这一努力并行不悖;或者只是先从店面入手,看看会发生什么?

到目前为止,它只是一个店面,我认为没有任何其他计划从那里提取任何其他主要街区。

需要记住的是,Shopify的平台接收了大量流量,其中大部分流量是针对店面请求的。当然,管理员要承担很大一部分费用,但主要是店面流量,所以我们优化代码的特定位置是有意义的,因为那里是流量最大的地方,这是我们可以利用的影响。

[]另一件事是,店面的要求不一定与管理员的要求相同。管理员是一种你想要在任何时候都拥有有效信息的东西。例如,支付处理和账单就是你想要从你的账户中提取适当金额的美元的东西。因此,性能不再是一个标准,因为你想确保你有正确的计算和逻辑在那里进行。在店面上,对…的强烈需求有所减少。

是的,…。这是对的。显然,店面呈现的是正确的信息;它不是不正确或不正确的问题,而更像是管理员是为有限类型的人服务的,而店面实际上是互联网上的任何人。

如果你把Shopify看作一个上升或下降的情景,那么大多数上升或下降的情景很可能是在店面,而不是太多的管理员或其他人。你可能会得到有限的,世界上一小部分人会关心,但大多数人会关心店面开得又快又快。

一点儿没错。所以店面--这里的主要标准是性能,尤其是当人们在世界各地的移动设备上运行时,你想让人们在任何情况下都能以尽可能快的速度加载他们的店面。

在这种情况下,对始终获取正确数据(准确地说,在任何给定的秒数)的要求较低。这实际上更多的是让他们的设备做出反应,开始做一些事情。

嗯。您可以从一个角度来考虑这一点--您提到了rails CRUD场景并不真正符合这个标准,这在某种程度上值得定义…。正如杰罗德所说,是什么准确地指出了这一点?为什么是店面?你为什么要重写这一点,你会在Shopify内部平行其他机会吗?我认为你会为正确的工作考虑正确的工具。虽然我们并不否认Rails并不令人惊叹,但正如您所说的,您同时致力于Ruby和Rails,所以这不是“不再使用Rails”的问题。再见。再见“,它更像是”嗯,也许在这个场景中,性能、速度和优化,所有这些不同的东西都比这更重要。“。我认为这里更重要的是帮助我们理解为什么Rails不再适用,以及为什么重写更有意义。特别是对于Jerod的问题,为什么Ruby还在,这是有道理的,因为嘿,你在Shopify…里面有很多关于Ruby的知识。除非你对此有一个非常好的长期计划,否则放弃它是没有什么意义的。

但更重要的是,为什么要为Ruby重写,但却为正确的工作选择了如此多的正确工具呢?Ruby仍然是合适工作的合适工具,但是Rails已经不再适合这个领域了。通过使用纯Ruby和非惯用Ruby以及所有这些东西,您得到了什么?

有趣的是,我们在新的实现中仍然在使用相当数量的Rails组件,这些组件不一定是整个Rails应用程序本身,但是出于兼容性的目的,我们使用了一些主动支持,以确保我们的整体仍然可以在新的应用程序中工作。我们在新的实现中确实使用了各种仍在使用Rails的gem。

在我看来--它更像是一个简单的Ruby应用程序和Rails提供的东西之间的混合体。我们在某种程度上搁置了Rails路由的一切,例如,我们不一定需要实现的东西,因为店面的路由可以相当简单地实现,而不需要Rails提供的路由的所有东西。但是Rails确实提供了相当多的行为和特性,我们仍然在应用程序中通过我们导入到那里的gem和模块来使用这些行为和特性。

[]在我看来,当你谈论非惯用的Ruby时,当我读到博客文章时,你正在做的很多事情都是在使用自毁风格的方法调用,比如MAP!它不会给你返回一个新的对象数组(或者不管它是什么),它实际上会在适当的地方修改它自己。您之所以在店面内这样做,是因为您正在优化最大限度的内存消耗,对吗?您正在尝试将内存消耗降低到尽可能小的程度。这就是你的“为什么不是Rails呢?”就在那里。我的意思是,当然有一个原因;有很多原因。但是,如果您可以在内存中尽可能少地加载Rails所需的代码,而不是整个堆栈,无疑可以节省内存分配;所有那些您没有使用…的对象。

一点儿没错。我想是《话语》杂志的萨姆·萨夫隆发表了一篇关于活动记录如何占用大量内存的文章,我想他把它比作简单的SQL,或者--我不记得名字了,这是他自己写的一个库。

我猜它应该是一个库,在那里你基本上是在一些帮助下编写原始SQL,我猜?

这里有一个,但也有一个关于Active Record 5.2中内存膨胀的分析,这是一个不同的…。这很有趣。这是我们在新实现中略过的内存使用的一个很好的例子。例如,因为Storefront-几乎所有的数据都是读流量,不涉及写操作,不涉及删除,也不涉及更新;它实际上是“我有一个请求,生成一个读响应,你只需要从数据库中获取数据并将其发回”--这类事情不一定需要使用活动记录或任何在ORM方面更重的东西来读取数据。直接使用SQL可以从那里获取数据,通过读取直接访问这些数据就足够了。因此,在这种情况下,减少内存分配是为了减少垃圾回收器的运行时间或频率,这对我们看到的响应时间有很大影响。

是什么真正吸引了你进行这次重写?可以说,最糟糕的情况是什么时候开始出现的?显然,Rails多年来一直运行得很好。你进行了首次公开募股(…),对一家公司来说,你值很多钱,你正在进行令人惊叹的首次公开募股(…)。弹出的主要内容是什么,说“你知道吗--我们真的需要把这件事记下来”?仅仅是速度和正常运行时间吗?是内存问题,还是服务器故障?服务器着火了吗?是什么真正触动了这根绳索,并说“我们需要在两年前真正解决这个问题”?

我认为这是一种进行性的痛点,从来不是什么大事,一夜之间就会出现。随着时间的推移,我们开始看到Perf。

.