【后端教程】如何正确发布PHP 代码
⼏乎每⼀个 PHP 程序员都发布过代码,可能是通过 ftp 或者 rsync 同步的,也可能是通过 svn 或者 git 更新的。⼀个活跃的项⽬可能每天都要发布若⼲次代码,但是现实却是很少有⼈注意其中的细节,实际上这⾥⾯有好多坑,很可能你就在坑中却浑然不知。
⼀个正确实现的发布系统⾄少应该⽀持原⼦发布。如果说每⼀个版本都表⽰⼀个独⽴的状态的话,那么在发布期间,任何⼀次请求只能在单⼀状态下被执⾏。如此称之为⽀持原⼦发布;反之如果在发布期间,⼀次请求跨越不同的状态,那么就不能称之为原⼦发布。我们不妨举个例⼦来说明⼀下:假设⼀次请求需要 include 两个 PHP ⽂件,分别是 a.php 和 b.php,当 include a.php 完成后,发布代码,接着include b.php,如果处理不当的话,那么就可能会导致旧版本的 a.php 和新版本的 b.php 同时存在于同⼀个请求之中,换句话说就是没有实现原⼦发布。
开源世界⾥有很多不错的发布代码⼯具,⽐如 ruby 社区的 capistrano ,其流程⼤致就是发布代码到⼀个全新的⽬录,然后再软链接到真正的发布⽬录。
.├── current -> releases/v1└── releases    ├── v1    │  ├── foo.php    │  └── bar.php    └── v2        ├── foo.php        └── bar.php
不过鉴于 PHP 本⾝的特殊性,如果只是简单套⽤上⾯的流程,那么将很难实现真正的原⼦发布。要理清个中缘由,还需要了解⼀下 PHP 中的两个 Cache 的概念:
opcode cache
realpath cache
先聊聊 opcode cache,基本就是 apc 或者 zend opcode,关于它的作⽤,⼤家都已经很熟悉,不必多⾔,需要注意的是 apc 的 bug 很多,⽐如开启了 able_cli  配置后就会有很多灵异问题,所以说 opcode cache 还是尽可能使⽤ zend opcache 吧,如果需要缓存数据,可以⽤ apcu 。此外 apc 和 zend opcode 对缓存键的选择有所差异:apc 选择的是⽂件的 inode,zend opcode 选择的是⽂件的 path。
再聊聊 realpath cache,它的作⽤是缓冲获取⽂件信息的 IO 操作,⼤多数时候它对我们⽽⾔是透明的,以⾄于很多⼈都不知道它的存在,需要注意的是 realpath cache 是进程级别的,也就是说,每⼀个 php-fpm 进程都有⾃⼰独⽴的 realpath cache。
假设在发布代码期间,opcode cache 或者 realpath cache ⾥的数据出现过期,那么就会出现⼀部分缓存是旧⽂件,⼀部分缓存是新⽂件的⾮原⼦发布的情况,为了避免出现这种情况,我们应该保证缓存
过期时间⾜够长,最好是除⾮我们⼿动刷新,否则永远不过期,对应到配置上就是:关闭 apc.stat 、opcache.validate_timestamps  配置,设置⾜够⼤的 realpath_cache_size 、realpath_cache_ttl  配置,必要的监控总是有好处的。
相关的技术细节特别琐碎,建议⼤家仔细阅读如下资料:
realpath_cache PHP’s OPCache extension review Atomic deploys at Etsy Cache invalidation for scripts in symlinked folders 在采⽤软链接发布代码的时候,通常遇到的第⼀个问题多半是新代码不⽣效!即便调⽤了 apc_clear_cache  或者 opcache_reset ⽅法也⽆效,重启 php-fpm ⾃然是能够解决问题,不过对脚本语⾔来说重启太重了!难道除了重启就没有别的办法了么?
事实上之所以会出现这样的问题,主要是因为 opcode cache 是通过 realpath cache 获取⽂件信息,即便软链接已经指向了新位置,但是如果 realpath cache ⾥还保存着旧数据的话,opcode cache 依然⽆法知道新代码的存在,缺省情况下,realpath_cache_ttl  缓存有效期是两分钟,这意味着发布代码后,可能要两分钟才能⽣效。为了让发布尽快⽣效,需要以进程为单位清除 realpath cache:
<?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 环境下还可能有问题。因为在缺省情况下 validate_path  是关闭的,此时会缓存未解析的符号链接的值,这会导致即便软链接指向修改了,也⽆法⽣效,所以在使⽤ zend opcode 的时候,如果使⽤了软链接,视情况可能需要把 validate_path 激活。
详细介绍参考:PHP’s OPCache extension review 。
[1][2][3][4][5][6][7][8][9]
[10]
[11]
[12]
[13][14][15][16][17][18]
deploy
不过 Deployer 在原⼦发布上有⼀点瑕疵,具体见 release/symlink 代码:
<?php// deploy:releaserun("cd {{deploy_path}} && if [ -h release ]; then rm release; fi");run("ln -s $releasePath {{deploy_path}}/release");// deploy:symlinkru n("cd {{deploy_path}} && ln -sfn {{release_path}} current");run("cd {{deploy_path}} && rm release");?>
在 release 的时候,它是先删除再创建,是⼀个两步的⾮原⼦操作,在 symlink 的时候,看上去「ln -sfn」是单步原⼦操作,实际上也是错误的:
shell> strace ln -sfn releases/foo currentsymlink("releases/foo", "current")      = -1 EEXIST (File exists)unlink("current")                      = 0symlink("releases/f oo", "current")      = 0
通过 strace 我们能清晰的看到,虽然表⾯上使⽤「ln -sfn」是⼀步操作,但是内部依然是按照先删除再创建的逻辑执⾏的,实际上这⾥应该搭配使⽤「ln & mv」:
shell> ln -sfn releases/pshell> mv -p current
先通过 ln 创建⼀个临时的软链接,再通过 mv 实现原⼦操作,此时如果使⽤ strace 监控,会发现 mv 的「T」选项实际上仅仅执⾏了⼀个rename 操作,所以是原⼦的。
BTW:在使⽤「ln -sfn」前后,如果使⽤ stat 查看新旧⽂件的 inode 的话,可能会发现它们拥有⼀样的 inode 值,看上去和我们的结论相悖,其实不然,实际上只是复⽤删除值⽽已(如果想验证,注意 Linux 会复⽤,Mac 不会复⽤)。
据说⼀千个⼈的⼼中就有⼀千个哈姆雷特,不过我希望所有的 PHP 程序员在发布 PHP 代码的时候都能采⽤⼀种⽅法,那就是本⽂介绍的⽅法,正确的⽅法。
服务推荐
>php如何运行代码

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。