如果可以的话,请贴我:我们如何调试生产。 第二部分

在我的文章的第一部分中我谈到了Badoo如何创建补丁系统的第一个版本。 简而言之,我们需要找到一种方法来修复生产中的严重错误,所有开发人员都可以使用。 但是,第一个版本并非没有缺点:我们使用了一种特殊的布局方法,该方法不能保证补丁程序布局的原子性和代码的一致性。

在本文的这一部分中,我将讨论一种新的方式来布置我们试图解决问题时提出的代码,以及如何用它来转换补丁系统。


图片: 来源

通用解决方案-多版本部署套件


在对我们的Jura系统进行了另一次审查之后, youROCK Nasretdinov表示他对如何解决所有问题有一个想法。 他只要求花费大量时间来重新制作版式系统。 这就是Multiversional Deployment Kit的概念,或者是普通人所说的MDK的出现(Jura在其关于HighLoad ++的报告中将其与其他代码布局方式进行了比较)。

新系统旨在更改我们的布局程序。 对于那些没有阅读我的第一部分的人,我将简要地告诉您部署过程的样子:首先,我们将所有必需的文件收集在一个目录中,然后将目录的状态保存并传递到服务器。

在MDK时代之前,我们使用称为循环的块设备(即文件系统映像)进行存储和传递。 该目录已复制到一个空循环,已存档并发送到服务器。

在新系统中,我们不是对整个目录进行版本控制,而是对每个文件分别进行版本控制,以便文件的版本与内容明确相关。 对于目录,有地图(map)-特殊文件,其中记录了目录中所有文件的版本。 这些卡也经过版本控制,看起来都像这样:



看起来很熟悉吗? 这就是在Git中安排对象的方式(您可以在此处阅读有关内容,但这对于理解本文不是必需的)。

对于版本控制,我们使用MD5哈希的前八个字符(准确地说是十六进制表示形式),取自文件的内容。 此版本写在文件名的末尾或映射名的开头(以便您可以将映射文件与生成的版本映射区分开来):



代码版本是www根目录的映射版本。 为了找到当前地图,我们有一个符号链接(symlink)current.map。

为什么不使用git?
尽管MDK部分地借鉴了Git的想法,但它们还是有一些区别。 最重要的是文件如何存储在工作目录中(即在计算机上)。 如果Git在此仅存储一个最新版本,则MDK会将文件的所有可用版本保存在那里。 同时,只有一个symlink current.map指向代码的当前版本,该代码在其工作中使用自动加载,并且可以自动更改。 为了进行比较,Git使用git-checkout更改版本,该版本依次更改文件,而不是原子的。

用MDK编译


需要MDK才能在程序集末尾保存目录的状态。 为此,我们有一个特殊的地方,我们称之为存储库,它是对我们有价值的所有文件版本的存储库(也就是我们可能想分解的文件)。 当目录中的新内容准备就绪时,我们将计算其中所有文件的版本,并将缺少的文件报告给存储库。

使用MDK进行布局


在每个接收服务器上进行布局期间,我们运行一个脚本,该脚本检查服务器上是否所有必需的文件都已存在,并请求存储库中缺少的文件。 我们只能通过更改current.map符号链接将版本切换到新版本。

它应该如何解决我们的问题


假定如果在新版本中仅更改了几个文件,则使用新系统进行的组装和布局在时间上至少应与单独文件的补丁布局具有可比性。 如果是这样,那么对于每个补丁,我们将只生成一个新版本。

MDK实施


MDK有一个缺点:在最终计算机上,每个文件的名称都应具有其版本。 这是允许您一次在一个目录中存储一个文件的很多版本的方法,但是它不允许您从代码中包含user.php-您必须指定特定的版本。 除此之外,布局系统代码中的各种错误很可能仍然存在,新的布局算法比旧的算法要复杂得多,而为什么我们决定分步实施新系统也将变得很清楚。 从字面上看,我们从一两个服务器开始,逐渐扩展了它们的列表,同时纠正了出现的问题。

鉴于切换到新系统应该花费很多时间,因此我们必须考虑补丁在过渡期间的工作方式。 当时,对于补丁的布局,我们使用了mscp自行编写的实用程序,该实用程序一次可放置一个文件。 我们教她提前用MDK替换服务器上的当前文件,但是我们无法向此类服务器添加新文件(因为我不得不更改文件映射)。 我不想介绍一些非常复杂的中间解决方案-因为我们的前途一片光明,不需要mscp。 结果,我不得不忍受这个问题。 总的来说,在过渡时期,开发人员设法遭受了损失,但现在对我们而言,这是值得的。

不要相信任何人



图片: 来源

问题可能是合乎逻辑的,但是MDK中的版本是否会发生冲突(即,两个内容不同的文件被分配了相同的版本)?

实际上,我们很容易受到这种错误的保护。 我们{ }.{}文件{ }.{}{ }.{} ,这意味着必须匹配八个以上的字符才能发生错误。

但是一旦出了问题,就会出错。 在进行下一次计算之后,我们注意到HTTP代码404(找不到文件)的错误越来越多。 小型调查显示,某些静态文件丢失了。 原来,我们布置了一个非常旧的静态映射,并提供了到服务器上不应该存在的文件的链接。 但是这张卡是从哪里来的? 在本文的第一部分中,我指出了静态是由一个单独的过程分解的,只有版本映射保留了PHP代码。 当我们生成新版本的MDK时,我们会将文件的丢失版本报告给存储库,该存储库不会删除任何内容(有很多空间,我们不在乎)。 而且,我们通常会尽最大努力进行登台,因此静态版本映射是那些更改频率比其他文件高的文件之一。 所有这些导致我们面对冲突。 检查版本后,MDK认为一切都很好,因为已经存在该版本的文件,并将其放在服务器上。 赶紧发现错误是一件好事。

现在,除了版本,我们还要检查文件大小:如果存储库中文件大小相同,则很可能是相同文件。 在最坏的情况下,我们会有一个故事写一篇新文章。

MDK-圣诞小偷



图片: 来源

我想告诉您另一个错误,因为它至少很有趣。 很容易猜到我们已经清理了目标服务器上文件的旧版本。 为了快速解决其中一个问题,我们做出了一个决定性的决定:将清洁期设置为一天(而不是以前的7天)。 它奏效了-问题消失了。 我们甚至住了一段时间。

周日凌晨五点左右,我的卧室里响了一个电话,电话中的监视器响了:“脚本对我们不起作用。 他们说你知道怎么回事。” 对我来说,这听起来像是“办公室里的榨汁机烧坏了。 他们说你知道怎么回事。” 我只从文章和故事中了解我们脚本框架的原理,我与他没有任何“个人关系”,而且更是如此,我从未修复过他。 但是我爬到服务器上查找正在发生的事情,发现问题确实在“我们这边”:服务器上根本没有代码。

我再次上​​传了代码-并且有效。 顺便说一句,该错误被证明是原始的:在星期六,没有发布一个新版本的MDK,并且事实证明,清理脚本没有进行任何检查以删除当前版本。 结果,早上五点(按计划)他从所有服务器上删除了代码。 讲完这个故事后,我们意识到,使用旧的设置,它将在7天的假期中出现,例如,在圣诞节前夕的新年假期中。 “基督出生了-代码不见了”-很长一段时间我们都可以听到这个笑话。

新补丁系统


最后,我们引入了一个新的布局系统-现在该重做补丁系统了。 不再需要mscp,也无需避免生成新版本。 首先,我们更改了补丁的生命周期。 现在,在确认更改之后,他返回到开发人员,由开发人员决定何时准备好计算补丁。 他单击“部署”按钮,然后将补丁添加到主服务器,生成并部署新版本的MDK。 在此阶段,不再需要开发人员参与。

我们已经实现了非常好的布局速度:更改几乎在一分钟内即可到达服务器。 但是,要做到这一点,我们不得不采取一些技巧:例如,我们仍然不生成静态值或翻译-而是从上次分解的版本中获取版本。 因此,我们对JS和CSS文件的补丁程序进行了限制。

实验


我们确实设法解决了以前遇到的所有问题。 您不再需要考虑如何正确地进行更改,而这不会在逐个文件的布局中引起麻烦-只需不接触静态变量,一切都会正常进行。

但是出现了新的困难。 以前,我们允许开发人员将所做的更改发布到一台或多台服务器上,只是为了确保一切都可以使用它们。 在新系统中,此功能消失了,因为master毫无例外地成为所有服务器的当前版本。


图片: 来源

因此,出现了对修补程序系统的新要求:您需要能够在少数服务器上检查更改,而又不向主服务器添加更改。 我们称之为新功能实验。

对于开发人员,该过程类似于以下内容:收到升级后,修补程序系统界面中会出现一个新页面,您可以在其中选择要进行实验的服务器。 它可以是一组服务器,一个服务器或我们的系统可以理解的任何组合。 系统部署补丁并通知开发人员。 同时,页面上还会显示来自受影响服务器的最新错误的日志。

我们不以任何方式限制开发人员;他们可以在同一服务器上创建实验。 一个可以在10%的集群上进行实验,另一个可以在整个集群上进行实验。

由于我们拥有非常缺乏的“补丁版本”这一事实,因此这成为可能。 从理论上讲,此版本对于每个服务器可能都是唯一的。 它看起来像是由逗号分隔的标识符字符串,例如“ 32,45,79”。 这意味着服务器必须将向导和补丁中的所有更改分别编号为32、45和79。对于每个这样的版本,我们都会生成自己的MDK版本。 我们从主分支获取最新的更改,然后依次应用每个补丁。 如果在其中一个版本的生成过程中发生冲突,我们只需取消最新补丁程序的实验并通知开发人员。

生成的文件


从补丁程序系统存在的第一天开始,我们就来了一个窍门:我们拒绝生成静态文件,以便使更改尽快到达服务器。 当然,我们确实希望有机会以与更改PHP代码相同的方式更改JS代码,但是构建此过程的所有尝试均未成功。

大约六个月前,我们再次回到这个问题。 目的:您需要更改静态变量,但不能牺牲PHP代码布局的速度。 主要问题:一次完整的组装需要八分钟。 怎么办

你必须妥协。 我们从不能将JS代码作为实验的一部分开始这一事实开始。 这应该节省大量时间:只需保持一个版本的静态更新,而不是为不同的计算机组生成数十个不同的版本。 但是仍然很长一段时间。 您还能节省什么? 我们没有弄清楚如何减少时间,但决定如果程序集不阻塞PHP代码的布局就不会有问题。

我们开始异步生成静态变量。 通过更改JS或CSS文件,我们开始了一个单独的过程,该过程创建了静态版本的新映射。 在工作开始时组装PHP代码的过程将检查是否有新的静态映射,如果有,则将其拾取并放在所有服务器上。 您解决问题了吗? 在实践中。 采用这种方法,我们遇到了一个新的限制:您无法在一个补丁中更改JS和PHP代码,因为我们会异步分解这些更改,并且不能保证它们会同时出现在计算机上。

总结


我们对更新感到非常满意。 对于我们来说这并不容易,但是这使我们的系统更加可靠。 开发人员找到了用于实验的替代应用程序:使用它们,您可以轻松地从一对服务器收集特定日志,而无需将更改添加到主服务器。

我们仍然有改进系统的想法,但尚无足够的时间来实施。 例如,我们要重做创建补丁的过程,并添加与主要代码同时更改JS文件的功能,以摆脱最新的限制。

每天,我们发布大约60个补丁,有时还会发布更多补丁,例如,到目前为止,某些功能仅在测试人员中可用。 约有三分之一的补丁在布局之前都要经过实验。 总体而言,在系统存在期间,我们为母版安装了约46,000个补丁。

Source: https://habr.com/ru/post/zh-CN413991/


All Articles