거의 모든 PHP 프로그래머가 코드를 출시했습니다. 이 코드는 FTP 또는 rsync 를 통해 동기화되거나 svn 또는 git을 통해 업데이트될 수 있습니다. 활발한 프로젝트는 하루에 여러 번 코드를 공개할 수도 있지만, 세부 사항에 주의를 기울이는 사람은 거의 없으며, 실제로는 함정이 많고, 자신도 모르게 수렁에 빠져 있을 가능성이 있습니다.
제대로 구현된 게시 시스템은 최소한 원자 게시를 지원해야 합니다. 각 버전이 독립적인 상태를 나타내는 경우 릴리스 기간 동안 모든 요청은 단일 상태에서만 실행될 수 있습니다. 이를 원자 릴리스 지원이라고 합니다. 반대로, 요청이 릴리스 중에 여러 상태에 걸쳐 있으면 원자 릴리스라고 할 수 없습니다. 설명을 위해 예를 들어 보겠습니다. include a.php일 때 요청에 두 개의 <code>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
PHP 코드를 올바르게 게시하는 예 공유 cache
先聊聊 opcode cache
,基本就是 apc
或者 zend opcode
,关于它的作用,大家都已经很熟悉,不必多言,需要注意的是 apc
的 bug 很多,比如开启了 apc.enable_cli 配置后就会有很多灵异问题,所以说 opcode cache
还是尽可能使用 zend opcache
吧,如果需要缓存数据,可以用 apcu。此外 apc
和 zend opcode
对缓存键的选择有所差异:apc
选择的是文件的 inode
,zend opcode
选择的是文件的 path
。
再聊聊 PHP 코드를 올바르게 게시하는 예 공유 cache
,它的作用是缓冲获取文件信息的 IO 操作,大多数时候它对我们而言是透明的,以至于很多人都不知道它的存在,需要注意的是 PHP 코드를 올바르게 게시하는 예 공유 cache
是进程级别的,也就是说,每一个 php-fpm
进程都有自己独立的 PHP 코드를 올바르게 게시하는 예 공유 cache
。
假设在发布代码期间,opcode cache
或者 PHP 코드를 올바르게 게시하는 예 공유 cache
里的数据出现过期,那么就会出现一部分缓存是旧文件,一部分缓存是新文件的非原子发布的情况,为了避免出现这种情况,我们应该保证缓存过期时间足够长,最好是除非我们手动刷新,否则永远不过期,对应到配置上就是:关闭 apc.stat、opcache.validate_timestamps 配置,设置足够大的 PHP 코드를 올바르게 게시하는 예 공유_cache_size、PHP 코드를 올바르게 게시하는 예 공유_cache_ttl 配置,必要的监控总是有好处的。
相关的技术细节特别琐碎,建议大家仔细阅读如下资料:
PHP 코드를 올바르게 게시하는 예 공유_cache PHP’s OPCache extension review Atomic PHP 코드를 올바르게 게시하는 예 공유s at Etsy Cache invalidation for scripts in symlinked folders
在采用软链接发布代码的时候,通常遇到的第一个问题多半是新代码不生效!即便调用了 apc_clear_cache 或者 opcache_reset 方法也无效,重启 php-fpm
自然是能够解决问题,不过对脚本语言来说重启太重了!难道除了重启就没有别的办法了么?
事实上之所以会出现这样的问题,主要是因为 opcode cache
是通过 PHP 코드를 올바르게 게시하는 예 공유 cache
获取文件信息,即便软链接已经指向了新位置,但是如果 PHP 코드를 올바르게 게시하는 예 공유 cache
里还保存着旧数据的话,opcode cache
依然无法知道新代码的存在,缺省情况下,PHP 코드를 올바르게 게시하는 예 공유_cache_ttl 缓存有效期是两分钟,这意味着发布代码后,可能要两分钟才能生效。为了让发布尽快生效,需要以进程为单位清除 PHP 코드를 올바르게 게시하는 예 공유 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
环境下还可能有问题。因为在缺省情况下 opcache.revalidate_path 是关闭的,此时会缓存未解析的符号链接的值,这会导致即便软链接指向修改了,也无法生效,所以在使用 zend opcode
的时候,如果使用了软链接,视情况可能需要把 opcache.revalidate_path
PHP
a.php
및 b.php
>가 필요하다고 가정해 보겠습니다. 가 완료되면 코드를 게시한 다음 b.php를 포함
하세요. 제대로 처리되지 않으면 a.php
의 이전 버전이 생성될 수 있습니다. b.php
의 새 버전이 동일한 요청에 동시에 존재합니다. 즉, 원자 게시가 구현되지 않습니다. 🎜🎜오픈 소스 세계에는 🎜ruby🎜 커뮤니티의 capistrano와 같은 좋은 코드 게시 도구가 많이 있습니다. 프로세스는 대략적으로 코드를 새로운 디렉터리에 게시한 다음 실제 릴리스 디렉터리에 대한 소프트 링크입니다. 🎜fastcgi_param SCRIPT_FILENAME $PHP 코드를 올바르게 게시하는 예 공유_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $PHP 코드를 올바르게 게시하는 예 공유_root;🎜그러나 🎜PHP🎜 자체의 특수성으로 인해 단순히 위의 프로세스를 적용한다면 진정한 원자 방출을 달성하기 어려울 것입니다. 이유를 명확히 하려면 🎜PHP🎜에 있는 두
캐시
의 개념도 이해해야 합니다. 🎜apc
또는 인 <code>opcode 캐시
에 대해 이야기해 보겠습니다. > zend opcode, 모두가 이미 그 기능에 익숙합니다. 말할 필요도 없이 apc
에는 많은 버그가 있습니다. 예를 들어, apc.enable_cli 구성을 켠 후에는 많을 겁니다. 이상한 문제이므로 opcode 캐시
는 최대한 zend opcache
를 사용해야 합니다. 데이터를 캐시해야 하는 경우 apcu를 사용하면 됩니다. 또한 apc
와 zend opcode
에는 서로 다른 캐시 키 선택이 있습니다. apc
는 파일의 inode
를 선택합니다. zend opcode
는 파일의 경로
를 선택합니다. 🎜🎜 PHP 코드를 올바르게 게시하는 예 공유 캐시
에 대해 다시 이야기해 보겠습니다. 그 기능은 🎜IO🎜 작업을 버퍼링하여 파일 정보를 얻는 것입니다. 대부분의 경우 이는 우리에게 투명하므로 많은 사람들이 이를 모릅니다. 존재하지만 PHP 코드를 올바르게 게시하는 예 공유 캐시
는 프로세스 수준에 있다는 점에 유의해야 합니다. 즉, 각 php-fpm
프로세스에는 자체적인 독립적인 PHP 코드를 올바르게 게시하는 예 공유 캐시
가 있습니다. 코드> >. 🎜🎜코드를 릴리스하는 동안 opcode 캐시
또는 PHP 코드를 올바르게 게시하는 예 공유 캐시
의 데이터가 만료되고 캐시의 일부가 오래된 파일과 캐시의 일부는 새 파일입니다. 원자 게시의 경우 이러한 상황을 방지하려면 캐시 만료 시간을 수동으로 새로 고치지 않는 한 만료되지 않는 것이 가장 좋습니다. 구성은 다음과 같습니다. apc.stat, opcache.validate_timestamps 구성을 닫습니다. PHP 코드를 올바르게 게시하는 예 공유_cache_size 및 PHP 코드를 올바르게 게시하는 예 공유_cache_ttl 구성을 충분히 크게 설정하고 필요한 모니터링을 수행하는 것이 항상 유익합니다. 🎜🎜관련 기술 세부 사항은 매우 사소한 것이므로 다음 정보를 주의 깊게 읽어 보시기 바랍니다. 🎜<?php// PHP 코드를 올바르게 게시하는 예 공유:releaserun("cd {{PHP 코드를 올바르게 게시하는 예 공유_path}} && if [ -h release ]; then rm release; fi"); run("ln -s $releasePath {{PHP 코드를 올바르게 게시하는 예 공유_path}}/release");// PHP 코드를 올바르게 게시하는 예 공유:symlinkrun("cd {{PHP 코드를 올바르게 게시하는 예 공유_path}} && ln -sfn {{release_path}} current"); run("cd {{PHP 코드를 올바르게 게시하는 예 공유_path}} && rm release");?>
php-fpm
을 다시 시작하면 문제가 자연스럽게 해결되지만 스크립트 언어에서는 다시 시작하는 것이 너무 무겁습니다! 다시 시작하는 것 외에 다른 방법은 없나요? 🎜🎜사실 이런 문제가 발생하는 이유는 주로 opcode 캐시
가 PHP 코드를 올바르게 게시하는 예 공유 캐시
를 통해 파일 정보를 얻기 때문입니다. 소프트 링크가 새로운 위치를 가리켰음에도 불구하고, 그러나 이전 데이터가 여전히 PHP 코드를 올바르게 게시하는 예 공유 캐시
에 저장되어 있는 경우 opcode 캐시
는 여전히 새 코드의 존재를 알 수 없습니다. 기본적으로 PHP 코드를 올바르게 게시하는 예 공유_cache_ttl 캐시 유효 기간은 2분입니다. 코드가 발급된 후 적용되기까지 최대 2분이 걸릴 수 있다는 의미입니다. 릴리스가 최대한 빨리 적용되려면 프로세스 단위에서 PHP 코드를 올바르게 게시하는 예 공유 캐시
를 지워야 합니다. 🎜shell> strace ln -sfn releases/foo currentsymlink("releases/foo", "current") = -1 EEXIST (File exists)unlink("current") = 0symlink("releases/foo", "current") = 0🎜 이는 기본적으로
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
还是 PHP 코드를 올바르게 게시하는 예 공유 cache
,都是 PHP 固有的缓存特性,基于客观需要无法绕开,如此说来是否有办法绕开软链接,使其成为马奇诺防线呢?答案是 NGINX 的 $PHP 코드를 올바르게 게시하는 예 공유_root:
fastcgi_param SCRIPT_FILENAME $PHP 코드를 올바르게 게시하는 예 공유_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $PHP 코드를 올바르게 게시하는 예 공유_root;
有了 $PHP 코드를 올바르게 게시하는 예 공유_root
,即便 DOCUMENT_ROOT
目录中含有软链接,NGINX 也会把软链接指向的真正的路径发给 PHP,也就是说,对 PHP 而言,软链接已经不存在了!不过作为代价,每一次请求,NGINX 都要通过相对昂贵的 IO 操作获取 $PHP 코드를 올바르게 게시하는 예 공유_root
的值,通过 strace
命令我们能监控这一过程,下图从 current
到 foo
的过程:
在本例中,压测发现使用 $PHP 코드를 올바르게 게시하는 예 공유_root
后,性能下降了大约 5% 左右,不过明眼人一下就能发现,虽然 $PHP 코드를 올바르게 게시하는 예 공유_root
导致了 lstat
和 readlink
操作,但是 lstat
操作的次数是和目录深度成正比的,也就是说目录越深,执行的 lstat
次数越多,性能下降也就越大。如果能够降低发布目录的深度,那么可以预计还能降低一些性能损耗。
结尾介绍一下 Deployer,它是 PHP 中做得比较好的工具,有很多特色,比如支持并行发布,具体演示如下图,左边是串行,右边是并行,使用「vvv」能得到更详细信息:
不过 Deployer 在原子发布上有一点瑕疵,具体见 release/symlink
代码:
<?php// PHP 코드를 올바르게 게시하는 예 공유:releaserun("cd {{PHP 코드를 올바르게 게시하는 예 공유_path}} && if [ -h release ]; then rm release; fi"); run("ln -s $releasePath {{PHP 코드를 올바르게 게시하는 예 공유_path}}/release");// PHP 코드를 올바르게 게시하는 예 공유:symlinkrun("cd {{PHP 코드를 올바르게 게시하는 예 공유_path}} && ln -sfn {{release_path}} current"); run("cd {{PHP 코드를 올바르게 게시하는 예 공유_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/foo", "current") = 0
通过 strace
我们能清晰的看到,虽然表面上使用「ln -sfn」
是一步操作,但是内部依然是按照先删除再创建的逻辑执行的,实际上这里应该搭配使用「ln & mv」
:
shell> ln -sfn releases/foo current.tmpshell> mv -fT current.tmp current
先通过 ln
创建一个临时的软链接,再通过 mv
实现原子操作,此时如果使用 strace
监控,会发现 mv
的 「T」
选项实际上仅仅执行了一个 rename
操作,所以是原子的。
BTW:在使用「ln -sfn」
前后,如果使用 stat
查看新旧文件的 inode
的话,可能会发现它们拥有一样的 inode
值,看上去和我们的结论相悖,其实不然,实际上只是复用删除值而已(如果想验证,注意 Linux 会复用,Mac 不会复用)。
据说一千个人的心中就有一千个哈姆雷特,不过我希望所有的 PHP 程序员在发布 PHP 代码的时候都能采用一种方法,那就是本文介绍的方法,正确的方法。
相关推荐:
위 내용은 PHP 코드를 올바르게 게시하는 예 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!