代码方式实现PHP服务定时拉取
定时拉取是一种很常见的操作,比如需要定时从配置中心拉取一下配置,或者同步一下状态等。这对于编译型语言来说十分简单,只需要开一个独立的线程,拉一下、sleep一会就好了。但是对于无状态的PHP服务,比如wordpress,或者一些API层的服务,由于他们没有常驻进程,想跑定时任务就比较麻烦了(swoole的除外,我们只讨论fpm的)。
其实搞定时任务的方式有挺多,比如起一个cron脚本,定时拉(配置或者什么)写到一个文件里,php每次读这个文件结果。这么搞是最简单的,但是当你维护的是一个几百台docker的集群的时候,这么搞运维可能会骂死你;如果是用git的webhook部署的话,如果改了脚本逻辑得手动上去改一下……我就是遇到了一个类似的复杂情况,不得已用了这种方法。留着以后当个面试题也是极好的。
这种方式需要几个小部分来配合:
- xxx.lock 文件锁,里面存有一个timestamp,表示更新时间。
- xxx.conf 拉取到的配置(或其他什么东西,存在里面)。
- lock.sh 脚本,用户检查锁
- sync.sh 脚本,用于执行拉取操作
- xxx.php php文件
他们之间相互的关系大概是这样:

xxx.php及钩子
xxx.php主要任务有两个:
- 从lockfile读一下时间,看需不需要启动任务
- 如果需要,启动lock脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
class Sync { private $lockFile; private $confFile; private static $instance = null; public static function getInstance() { if (self::$instance == null) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->lockFile = dirname(__FILE__) . '/' . 'xxx.lock'; $this->confFile = dirname(__FILE__) . '/' . 'xxx.conf'; } public function updateConfHook() { try { $fpTime = fopen($this->lockFile, 'r'); if ($fpTime !== false) { if (flock($fpTime, LOCK_EX | LOCK_NB)) { $size = filesize($this->lockFile); if ($size > 0) { $content = fread($fpTime, $size); if (time() - (int)($content) > 60) { @flock($fpTime, LOCK_UN); @exec('nohup sh lock.sh &'); } else { @flock($fpTime, LOCK_UN); } } else { @flock($fpTime, LOCK_UN); @exec('nohup sh lock.sh &'); } } @fclose($fpTime); } } catch (\Exception $e) { } } } |
注意updateConfHook这个函数,他首先会尝试从lockfile把更新时间读出来,读之前会使用flock对文件加锁,如果成功,并且当前时间距离上次更新时间超过1分钟,那么我们解锁lockfile并启动lock.sh,后面的事就不用管了。
当然了,还需要哪里触发一下这个函数,不妨把钩子写成一个middleware,对代码侵入性较小。flock这里使用的目的是保证只有一个脚本存在,且尽可能不影响php性能。
lock.sh
1 2 3 4 5 6 |
#!/bin/sh flock -n xxx.lock sh sync.sh > /dev/null if [ 0 -ne $? ]; then exit fi |
lock.sh尝试对文件上锁,如果成功则进行下一步,失败则说明有其他脚本在跑,自己退出就好。
sync.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/sh curl --connect-timeout 1 -s $yourConfigCenterUrl > xxx.conf.tmp if [ 0 -ne $? ]; then exit fi # do something here # do check here mv -f xxx.conf.tmp xxx.conf date +%s > xxx.lock sleep 60 |
首先拉取配置,或者做你想做的事,然后保存、检查。如果合法则替换,并把更新日期写入lock文件。因为sync.sh是被lock.sh调用的,sync.sh不退出,文件锁就不会释放,所以这里sleep 60秒是为了避免其他脚本被启动,毕竟1分钟内不需要更新。
PS:虽然xxx.lock是在锁死状态,但是并不妨碍它被修改,大家可以试下。
流程图
