这个问题是关于 Web 应用程序的部署。
简介(可以跳过)
我正在使用 django,而我的托管服务提供商设置 django 支持的方式最终导致 webapp 分散在至少三个位置:
- 实际应用代码在
/something/<my_apps>/
- 网站设置/网址/模板
/something/<my_site>/
- css、javascript 和其他“媒体”在
/something_else/media/
因此,当我部署/升级网站时,我需要一次更新多个目录。
实际问题:
有没有办法进行原子文件复制?无论如何,我都不是 linux 系统的“专家”,所以请原谅我的无知。
复制操作涉及几个目录树,两个或三个,基本上:
copy _tree1 to tree1
copy _tree2 to tree2
原子,我的意思是:
- 它要么完全复制,要么根本没有复制。它永远不应该处于一些复制但失败的状态。
- 它在尽可能短的时间内完成。理想情况下,系统不应该在某个时间点看到正在进行的副本,它要么看到文件的旧版本,要么看到新版本;它在任何时候都不会看到文件 A 的旧版本,而是文件 B 的新版本。如果这不完全可能,那么它应该不会超过几毫秒。
我的想法是有类似双缓冲的东西:我在一个暂存区准备好所有东西,例如,_tree_x
然后将它复制移动到tree_x
应该是一个原子操作,它只是改变磁盘上的指针。
我认为单个这样的复制移动操作在 linux 中是原子的(不是吗?),但我也需要几个这样的操作是原子的;我希望将它们视为单次移动操作。
我认为您在临时区域的正确轨道上。我不知道任何原子命令,但是如果您暂存文件,然后使用脚本删除第一个目录并移动(而不是复制)第二个目录,并对所有三个目录执行此操作,它应该非常快。
或者,您可能想要使用符号链接。这样你可以大致:
并部署
/version/23
具有相同子目录的目录。然后,实际文件所在的位置(同样,为了速度,您需要一个脚本),您可以使用符号链接,这样当任何人访问最新页面时,他们都会获得最新版本(这一切都会发生)透明地,他们不知道)。这样做的好处是您的旧作品仍然存在,直到您决定删除它。[虽然,当然,版本控制系统更适合用于保留较旧的工作。]您必须检查 1)您可以运行脚本(并且以这种方式网络用户不能!),以及 2)您可以使用符号链接(因为某些网络服务器被配置为不遵循它们。)
也许我没有完全考虑到这一点,但为什么不将您的复制操作复制到一个新目录呢?完成后,将旧目录“mv”到另一个名称,并将新目录“mv”到所需名称。
这在技术上不是原子的,因为有一段时间旧目录将被移动而新目录还没有到位,但这可能已经足够好了。
对于大型网站,可以通过让多个服务器处理请求来处理站点更新。然后,您可以使一台服务器脱机,对其进行更新,然后将其重新联机,对集群中的其他服务器重复此操作。
对于单个托管站点,通过将站点关闭页面放入根文件夹中的 index.html 中,然后进行更改来关闭网站可能是有意义的。
如果您确实需要尽可能多地保持网站正常运行,我可以建议以下内容:
不存在原子副本,但是,重命名单个文件夹确实会原子地发生。通过将重命名放入脚本并运行该脚本,您可以快速地一个接一个地进行一系列重命名。您将需要两倍于站点的磁盘空间才能在服务器上同时拥有 before 和 after 文件夹。
这并不能解决您的问题 - 只是减少了曝光。前后版本可能需要不同的数据库数据字段,因此 SQL 查询也需要运行。一个人可能在您进行更新的同时进行页面加载。网页加载的开始可以加载更改之前的页面,页面加载的最后部分可以使用复制之后的文件。
首先,最好的方法是更改您的 httpd 配置以指向新目录,然后重新启动 httpd。我认为这是不可能的。
我有一个想法,假设您的三个目录中的数据不会一直在变化,因为它需要将这三个原始目录非原子移动到这些目录的副本。我不是 100% 确定这会起作用,但你可以测试它。对我来说,把它写成脚本比用英语解释更容易。让我知道是否需要进一步解释。
假设三个标称路径是:/pathA/dir1、/pathA/dir2、/pathB/dir3
(编辑:嗯,不知何故我错过了克林顿布莱克莫尔的上述回应,这与我的建议基本相同。所以没关系。)
对于那些阅读这个老问题的人来说,这是可能的,但它需要许多符号链接。
我的答案基于 Clinton Blackmore 的答案(目前接受的答案)。
不能以原子方式更改多个目录(或多个文件)。所以我们不能直接使用目录。可以使用 rename() 系统调用自动更新单个文件(用新文件替换旧文件)。符号链接可以以相同的方式更新,使用
mv -T
不mv
取消对目标的引用。所以,如果我们有这些目录:
我们可以使它们都成为其他三个目录的符号链接:
/version/current
反过来,只是指向当前版本目录的符号链接:然后可以使用两个简单的命令更新整个 Web 应用程序,其中最后一个命令一次“替换”所有三个目录(它实际上并不替换目录,它只是替换这些目录指向的位置):
我还没有实际测试过这个,但它应该可以工作。该
-T
标志可以是非标准标志。作为替代方案,python -c "import os; os.rename('/version/next', '/version/current')"
可以改为使用(也未经测试)。我不知道性能影响会是什么,但如果您已经在运行 Django,我怀疑它是否会很重要。我认为,如果您以尽可能低的延迟(如 CDN)提供大量静态文件,这可能很重要,即便如此,它可能只会对性能产生很小的影响。简而言之,您不必担心性能。
请注意,有一些问题:Django 是一个服务器,不会同时重新启动。为了使其真正具有原子性,Django 必须以完全跳过“当前版本”概念的方式进行设置。相反,您将从当前版本启动 Django 以供生产使用。对于更新,将启动下一个版本,然后重新启动服务器(我假设大多数网络服务器都有某种方式可以在不脱机的情况下重新启动),并且整个过程应该是原子的。但我不是这方面的专家。
另一个问题(正如 Ptolemy 所提到的)是,在繁忙的服务器上,由于缓存以及在页面加载期间在不同时间请求多个资源的事实,会有一些人看到部分来自一个版本和部分来自另一个版本的页面(资源加载之间可能有几秒钟)。我想到了这两个,缓存将是最重要的一个,但也是最容易解决的。不过,我怀疑这在实践中会成为一个很大的问题。
您是否尝试过使用atomic-rsync?它使用 rsync 和 mv 命令的组合,如其他答案中所建议的那样。