几乎每个 PHP 程序员都发布过代码,多是经过 FTP 或者 **rsync ** 同步的,也多是经过 svn 或者 git 更新的。一个活跃的项目可能天天都要发布若干次代码,可是现实倒是不多有人注意其中的细节,实际上这里面有好多坑,极可能你就在坑中却浑然不知。php
一个正确实现的发布系统至少应该支持原子发布。若是说每个版本都表示一个独立的状态的话,那么在发布期间,任何一次请求只能在单一状态下被执行。如此称之为支持原子发布;反之若是在发布期间,一次请求跨越不一样的状态,那么就不能称之为原子发布。咱们不妨举个例子来讲明一下:假设一次请求须要 include
两个 PHP 文件,分别是 a.php
和 b.php
,当 include a.php
完成后,发布代码,接着 include b.php
,若是处理不当的话,那么就可能会致使旧版本的 a.php
和新版本的 b.php
同时存在于同一个请求之中,换句话说就是没有实现原子发布。html
开源世界里有不少不错的发布代码工具,好比 ruby 社区的 capistrano,其流程大体就是发布代码到一个全新的目录,而后再软连接到真正的发布目录。node
├── current -> releases/v1
└── releases
├── v1
│ ├── foo.php
│ └── bar.php
└── v2
├── foo.php
└── bar.php
不过鉴于 PHP 自己的特殊性,若是只是简单套用上面的流程,那么将很难实现真正的原子发布。要理清个中原因,还须要了解一下 PHP 中的两个 Cache
的概念:nginx
先聊聊 opcode cache
,基本就是 apc
或者 zend opcode
,关于它的做用,你们都已经很熟悉,没必要多言,须要注意的是 apc
的 bug 不少,好比开启了 apc.enable_cli 配置后就会有不少灵异问题,因此说 opcode cache
仍是尽量使用 zend opcache
吧,若是须要缓存数据,能够用 apcu。此外 apc
和 zend opcode
对缓存键的选择有所差别:apc
选择的是文件的 inode
,zend opcode
选择的是文件的 path
。git
再聊聊 realpath cache
,它的做用是缓冲获取文件信息的 IO 操做,大多数时候它对咱们而言是透明的,以致于不少人都不知道它的存在,须要注意的是 realpath cache
是进程级别的,也就是说,每个 php-fpm
进程都有本身独立的 realpath cache
。程序员
假设在发布代码期间,opcode cache
或者 realpath cache
里的数据出现过时,那么就会出现一部分缓存是旧文件,一部分缓存是新文件的非原子发布的状况,为了不出现这种状况,咱们应该保证缓存过时时间足够长,最好是除非咱们手动刷新,不然永远不过时,对应到配置上就是:关闭 apc.stat、opcache.validate_timestamps 配置,设置足够大的 realpath_cache_size、realpath_cache_ttl 配置,必要的监控老是有好处的。github
相关的技术细节特别琐碎,建议你们仔细阅读以下资料:web
在采用软连接发布代码的时候,一般遇到的第一个问题多半是新代码不生效!即使调用了 apc_clear_cache 或者 opcache_reset 方法也无效,重启 php-fpm
天然是可以解决问题,不过对脚本语言来讲重启过重了!难道除了重启就没有别的办法了么?shell
事实上之因此会出现这样的问题,主要是由于 opcode cache
是经过 realpath cache
获取文件信息,即使软连接已经指向了新位置,可是若是 realpath cache
里还保存着旧数据的话,opcode cache
依然没法知道新代码的存在,缺省状况下,realpath_cache_ttl 缓存有效期是两分钟,这意味着发布代码后,可能要两分钟才能生效。为了让发布尽快生效,须要以进程为单位清除 realpath cache
:api
<?php $key = 'php.pid_' . getmypid(); if (($rev = apc_fetch($key)) != DEPLOY_VERSION) { if($rev < DEPLOY_VERSION) { apc_store($key, DEPLOY_VERSION); } clearstatcache(true); }
如此在 apc
环境下基本就能工做了,可是在 zend opcode
环境下还可能有问题。由于在缺省状况下 opcache.revalidate_path 是关闭的,此时会缓存未解析的符号连接的值,这会致使即使软连接指向修改了,也没法生效,因此在使用 zend opcode
的时候,若是使用了软连接,视状况可能须要把 opcache.revalidate_path
激活。
详细介绍参考:PHP’s OPCache extension review。
BTW:若是须要手动重置 opcode cache
,须要注意的是由于它是基于 SAPI 的概念,因此不能直接在命令行下调用 apc_clear_cache 或者 opcache_reset 方法来重置缓存,固然办法老是有的,那就是使用 CacheTool 在命令行下模拟 fastcgi
请求。
分析到这里,咱们不妨反思一下:在 PHP 中原子发布之因此是一个棘手的问题,归根结底是由于软连接和缓存之间的的矛盾。不论是 opcode cache
仍是 realpath cache
,都是 PHP 固有的缓存特性,基于客观须要没法绕开,如此说来是否有办法绕开软连接,使其成为马奇诺防线呢?答案是 NGINX 的 $realpath_root:
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root;
有了 $realpath_root
,即使 DOCUMENT_ROOT
目录中含有软连接,NGINX 也会把软连接指向的真正的路径发给 PHP,也就是说,对 PHP 而言,软连接已经不存在了!不过做为代价,每一次请求,NGINX 都要经过相对昂贵的 IO 操做获取 $realpath_root
的值,经过 strace
命令咱们能监控这一过程,下图从 current
到 foo
的过程:
在本例中,压测发现使用 $realpath_root
后,性能降低了大约 **5% **左右,不过明眼人一下就能发现,虽然 $realpath_root
致使了 lstat
和 readlink
操做,可是 lstat
操做的次数是和目录深度成正比的,也就是说目录越深,执行的 lstat
次数越多,性能降低也就越大。若是可以下降发布目录的深度,那么能够预计还能下降一些性能损耗。
结尾介绍一下 Deployer,它是 PHP 中作得比较好的工具,有不少特点,好比支持并行发布,具体演示以下图,左边是串行,右边是并行,使用「vvv」能获得更详细信息:
不过 Deployer 在原子发布上有一点瑕疵,具体见 release/symlink
代码:
<?php // deploy:release run("cd {{deploy_path}} && if [ -h release ]; then rm release; fi"); run("ln -s $releasePath {{deploy_path}}/release"); // deploy:symlink run("cd {{deploy_path}} && ln -sfn {{release_path}} current"); run("cd {{deploy_path}} && rm release"); ?>
在 release
的时候,它是先删除再建立,是一个两步的非原子操做,在 symlink
的时候,看上去「ln -sfn」
是单步原子操做,实际上也是错误的:
shell> strace ln -sfn releases/foo current symlink("releases/foo", "current") = -1 EEXIST (File exists) unlink("current") = 0 symlink("releases/foo", "current") = 0
经过 strace
咱们能清晰的看到,虽然表面上使用「ln -sfn」
是一步操做,可是内部依然是按照先删除再建立的逻辑执行的,实际上这里应该搭配使用「ln & mv」
:
shell> ln -sfn releases/foo current.tmp shell> mv -fT current.tmp current
先经过 ln
建立一个临时的软连接,再经过 mv
实现原子操做,此时若是使用 strace
监控,会发现 mv
的 「T」
选项实际上仅仅执行了一个 rename
操做,因此是原子的。
BTW:在使用「ln -sfn」
先后,若是使用 stat
查看新旧文件的 inode
的话,可能会发现它们拥有同样的 inode
值,看上去和咱们的结论相悖,其实否则,实际上只是复用删除值而已(若是想验证,注意 Linux 会复用,Mac 不会复用)。
听说一千我的的心中就有一千个哈姆雷特,不过我但愿全部的 PHP 程序员在发布 PHP 代码的时候都能采用一种方法,那就是本文介绍的方法,正确的方法。
原文转自老王的火丁笔记,原文地址:如何正确发布PHP代码 ;若有侵权请告知删除。