├── .gitignore ├── README.md ├── bin └── plumber ├── composer.json ├── example ├── Example1Worker.php ├── Example2Worker.php ├── bootstrap.php ├── config.php └── put_message.php └── src ├── BeanstalkClient.php ├── ForwardWorker.php ├── IWorker.php ├── ListenerStats.php ├── Logger.php ├── PidManager.php ├── Plumber.php ├── TubeListener.php └── composer_autoload.php /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /composer.phar 3 | /example/tmp/* 4 | !/example/tmp/.gitkeep 5 | !/example/tmp/plumber.sock 6 | .DS_Store 7 | /vendor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plumber 2 | ======== 3 | 4 | 消息队列的Worker守护进程,目前支持beanstalkd。 5 | 6 | ## 运行环境 7 | 8 | * PHP >= 5.4.1 9 | * Swoole >= 1.7.18 10 | * Linux / Mac OSX 11 | 12 | ## 安装 13 | 14 | ``` 15 | composer require footstones/plumber 16 | ``` 17 | 18 | ## 使用 19 | 20 | ### 启动 21 | ``` 22 | vendor/bin/plumber start /config-path # config-path为配置文件的路径 23 | ``` 24 | 25 | ### 重启 26 | ``` 27 | vendor/bin/plumber restart /config-path 28 | ``` 29 | 30 | ### 停止 31 | ``` 32 | vendor/bin/plumber stop /config-path 33 | ``` 34 | 35 | ### 配置说明 36 | 37 | 请参考[example/config.php](example/config.php)文件。 38 | 39 | ### Worker的写法 40 | 41 | 请参考[example/Example1Worker.php](example/Example1Worker.php)。 42 | 43 | ### Worker执行的返回值 44 | 45 | 请参考[src/IWorker.php](src/IWorker.php)。 46 | 47 | ## License 48 | 49 | MIT. 50 | -------------------------------------------------------------------------------- /bin/plumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | main($argv[1]); 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "footstones/plumber", 3 | "type": "library", 4 | "description": "A PHP Message Queue Worker.", 5 | "keywords": ["plumber", "beanstalk", "message queue", "swoole"], 6 | "homepage": "https://github.com/Footstones/Plumber", 7 | "license": "MIT", 8 | "autoload": { 9 | "psr-4": { 10 | "Footstones\\Plumber\\": "src/", 11 | "Footstones\\Plumber\\Tests\\": "tests/", 12 | "Footstones\\Plumber\\Example\\": "example/" 13 | } 14 | }, 15 | "authors": [ 16 | { 17 | "name": "Wellming Li", 18 | "email": "wellming.li@howzhi.com" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.4.1", 23 | "ext-swoole": ">=1.7.17", 24 | "davidpersson/beanstalk" : "2.0.0", 25 | "psr/log": "^1.0.0 || dev-master" 26 | }, 27 | "bin": ["bin/plumber"] 28 | } 29 | -------------------------------------------------------------------------------- /example/Example1Worker.php: -------------------------------------------------------------------------------- 1 | IWorker::FINISH); 16 | } 17 | 18 | public function setLogger(LoggerInterface $logger) 19 | { 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /example/Example2Worker.php: -------------------------------------------------------------------------------- 1 | IWorker::FINISH); 14 | } 15 | 16 | public function setLogger(LoggerInterface $logger) 17 | { 18 | 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /example/bootstrap.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/bootstrap.php', 5 | 'message_server' => [ 6 | 'host' => '127.0.0.1', 7 | 'port' => 11300, 8 | ], 9 | 'tubes' => [ 10 | 'Example1' => ['worker_num' => 10, 'class' => 'Footstones\\Plumber\\Example\\Example1Worker'], 11 | 'Example2' => ['worker_num' => 10, 'class' => 'Footstones\\Plumber\\Example\\Example2Worker'], 12 | 'Example3' => [ 13 | 'worker_num' => 5, 14 | 'class' => 'Footstones\Plumber\ForwardWorker', 15 | 'destination' => [ 16 | 'host' => 'localhost', 17 | 'port' => 11300, 18 | 'tubeName' => 'Example2', 19 | ] 20 | ], 21 | ], 22 | 23 | 'log_path' => '/tmp/plumber.log', 24 | 'output_path' => '/tmp/plumber.output.log', 25 | 'pid_path' => '/tmp/plumber.pid', 26 | 'daemonize' => 1, 27 | 'reserve_timeout' => 10, 28 | 'execute_timeout' => 60, 29 | ]; 30 | -------------------------------------------------------------------------------- /example/put_message.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | connect(); 13 | $beanstalk->useTube('Example3'); 14 | 15 | $i=0; 16 | 17 | for ($i=0; $i<1; $i++) { 18 | $message = json_encode(array('id'=>uniqid(md5(gethostname())), 'name' => 'Hello ' . $i)); 19 | $result = $beanstalk->put( 20 | 500, // Give the job a priority of 23. 21 | 0, // Do not wait to put job into the ready queue. 22 | 60, // Give the job 1 minute to run. 23 | $message // The job's .body 24 | ); 25 | echo $message . "\n"; 26 | } 27 | 28 | $beanstalk->disconnect(); 29 | -------------------------------------------------------------------------------- /src/BeanstalkClient.php: -------------------------------------------------------------------------------- 1 | _latestError; 14 | $this->_latestError = null; 15 | return $error; 16 | } 17 | 18 | protected function _error($message) 19 | { 20 | parent::_error($message); 21 | $this->_latestError = $message; 22 | } 23 | } -------------------------------------------------------------------------------- /src/ForwardWorker.php: -------------------------------------------------------------------------------- 1 | config = $config; 17 | $this->tubeName = $tubeName; 18 | } 19 | 20 | public function execute($job) 21 | { 22 | try{ 23 | $body = $job['body']; 24 | $config = $this->config['destination']; 25 | $config['persistent'] = false; 26 | 27 | $queue = new BeanstalkClient($config); 28 | $queue->connect(); 29 | 30 | $tubeName = isset($this->config['destination']['tubeName']) ? $this->config['destination']['tubeName'] : $this->tubeName; 31 | $queue->useTube($tubeName); 32 | 33 | $this->logger->info("use tube host:{$this->config['destination']['host']} port:{$this->config['destination']['port']} tube:{$tubeName} "); 34 | 35 | $pri = isset($this->config['pri']) ? $this->config['pri']: 0; 36 | $delay = isset($this->config['delay']) ? $this->config['delay']: 0; 37 | $ttr = isset($this->config['ttr']) ? $this->config['ttr']: 60; 38 | 39 | if (!isset($body['retry'])) { 40 | unset($body['retry']); 41 | } 42 | 43 | $queue->put($pri, $delay, $ttr, json_encode($body)); 44 | $queue->disconnect(); 45 | 46 | $this->logger->info("put job to host:{$this->config['destination']['host']} port:{$this->config['destination']['port']} tube:{$tubeName} ", $body); 47 | 48 | return IWorker::FINISH; 49 | } catch (\Exception $e) { 50 | $body = $job['body']; 51 | if (!isset($body['retry'])) { 52 | $retry = 0; 53 | } else { 54 | $retry = $body['retry']+1; 55 | } 56 | if($retry < 3){ 57 | $this->logger->error("job #{$job['id']} forwarded error, job is retry. error message: {$e->getMessage()}", $job); 58 | return array('code' => IWorker::RETRY, 'delay'=>$this->delays[$retry]); 59 | } 60 | $this->logger->error("job #{$job['id']} forwarded error, job is burried. error message: {$e->getMessage()}", $job); 61 | return IWorker::BURY; 62 | 63 | } 64 | } 65 | 66 | public function setLogger(LoggerInterface $logger) 67 | { 68 | $this->logger = $logger; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/IWorker.php: -------------------------------------------------------------------------------- 1 | calTableSize($size)); 18 | $table->column('count', swoole_table::TYPE_INT); 19 | $table->column('last_update', swoole_table::TYPE_INT); 20 | $table->column('tube', swoole_table::TYPE_STRING, 128); 21 | $table->column('job_id', swoole_table::TYPE_INT); 22 | $table->column('timeout', swoole_table::TYPE_INT); 23 | $table->create(); 24 | $this->table = $table; 25 | 26 | $pids = new swoole_table(1); 27 | $pids->column('pids', swoole_table::TYPE_STRING, $size*10); 28 | $pids->create(); 29 | $this->pids = $pids; 30 | 31 | $stoping = new swoole_table(1); 32 | $stoping->column('status', swoole_table::TYPE_INT); 33 | $stoping->create(); 34 | $this->stoping = $stoping; 35 | 36 | $this->logger = $logger; 37 | 38 | } 39 | 40 | /** 41 | * 更新状态 42 | */ 43 | public function touch($tube, $pid, $incr = false, $jobId = 0) 44 | { 45 | $key = $this->getKey($pid); 46 | 47 | $stats = $this->table->get($key); 48 | if ($stats === false) { 49 | $this->table->set($key, array( 50 | 'count' => $incr ? 1 : 0, 51 | 'last_update' => time(), 52 | 'tube' => $tube, 53 | 'job_id' => $jobId, 54 | 'timeout' => 0, 55 | )); 56 | 57 | $pids = $this->pids->get('data') ? : array('pids' => ''); 58 | $this->pids->set('data', array('pids' => $pids['pids']. $pid. ' ')); 59 | } else { 60 | $this->table->set($key, array( 61 | 'count' => $incr ? ($stats['count'] + 1) : $stats['count'], 62 | 'last_update' => time(), 63 | 'tube' => $tube, 64 | 'job_id' => $jobId, 65 | 'timeout' => 0, 66 | )); 67 | } 68 | } 69 | 70 | public function timeout($pid) 71 | { 72 | $this->table->incr($this->getKey($pid), 'timeout'); 73 | } 74 | 75 | public function get($pid) 76 | { 77 | $key = $this->getKey($pid); 78 | $stats = $this->table->get($key); 79 | if ($stats === false) { 80 | return array('count' => 0, 'last_update' => 0, 'tube' => '', 'job_id' => 0, 'timeout' => 0); 81 | } 82 | return $stats; 83 | } 84 | 85 | public function remove($pid) 86 | { 87 | $pids = $this->pids->get('data') ? : array('pids' => ''); 88 | $pids = trim($pids['pids']); 89 | $pids = empty($pids) ? array() : explode(' ', $pids); 90 | 91 | $newPids = array(); 92 | foreach ($pids as $p) { 93 | if ($p == $pid) { 94 | continue; 95 | } 96 | $newPids[] = $p; 97 | } 98 | 99 | $newPids = implode(' ', $newPids); 100 | $this->pids->set('data', array('pids' => $newPids)); 101 | // $this->table->del($this->getKey($pid)); 102 | return ; 103 | } 104 | 105 | public function getAll() 106 | { 107 | $pids = $this->pids->get('data') ? : array('pids' => ''); 108 | $pids = trim($pids['pids']); 109 | $pids = empty($pids) ? array() : explode(' ', $pids); 110 | 111 | $statses = array(); 112 | foreach ($pids as $pid) { 113 | $statses[$pid] = $this->get($pid); 114 | } 115 | return $statses; 116 | } 117 | 118 | public function stop() 119 | { 120 | $this->stoping->set('data', array('status' => 1)); 121 | } 122 | 123 | public function isStoping() 124 | { 125 | $stoping = $this->stoping->get('data') ? : array('status' => 0); 126 | return $stoping['status'] ? true : false; 127 | } 128 | 129 | private function getKey($pid) 130 | { 131 | return 'p_' . $pid; 132 | } 133 | 134 | private function calTableSize($size) 135 | { 136 | for($i=1; $i<=100; $i++) { 137 | $tableSize = pow(2, $i); 138 | if ($tableSize >= $size) { 139 | return $tableSize; 140 | } 141 | } 142 | 143 | throw new \RuntimeException(""); 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | options = $options; 30 | } 31 | 32 | public function emergency($message, array $context = array()) 33 | { 34 | $this->log('emergency', $message, $context); 35 | } 36 | 37 | public function alert($message, array $context = array()) 38 | { 39 | $this->log('alert', $message, $context); 40 | } 41 | 42 | public function critical($message, array $context = array()) 43 | { 44 | $this->log('critical', $message, $context); 45 | } 46 | 47 | public function error($message, array $context = array()) 48 | { 49 | $this->log('error', $message, $context); 50 | } 51 | 52 | public function warning($message, array $context = array()) 53 | { 54 | $this->log('warning', $message, $context); 55 | } 56 | 57 | public function notice($message, array $context = array()) 58 | { 59 | $this->log('notice', $message, $context); 60 | } 61 | 62 | public function info($message, array $context = array()) 63 | { 64 | $this->log('info', $message, $context); 65 | } 66 | 67 | public function debug($message, array $context = array()) 68 | { 69 | $this->log('debug', $message, $context); 70 | } 71 | 72 | public function log($level, $message, array $context = array()) 73 | { 74 | $content = '[' . date('Y-m-d H:i:s') . '] ' . strtoupper($level) . ': '; 75 | $content .= $message . ' ' . json_encode($context) . "\n"; 76 | file_put_contents($this->options['log_path'], $content, FILE_APPEND); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/PidManager.php: -------------------------------------------------------------------------------- 1 | path = $path; 12 | } 13 | 14 | public function get() 15 | { 16 | if (!file_exists($this->path)) { 17 | return 0; 18 | } 19 | return intval(file_get_contents($this->path)); 20 | } 21 | 22 | public function save($pid) 23 | { 24 | $pid = intval($pid); 25 | file_put_contents($this->path, $pid); 26 | } 27 | 28 | public function clear() 29 | { 30 | if (!file_exists($this->path)) { 31 | return; 32 | } 33 | 34 | unlink($this->path); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Plumber.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | $this->pidManager = new PidManager($this->config['pid_path']); 35 | } 36 | 37 | public function main($op) 38 | { 39 | $this->{$op}(); 40 | } 41 | 42 | protected function start() 43 | { 44 | if ($this->pidManager->get()) { 45 | echo "ERROR: plumber is already running.\n"; 46 | return; 47 | } 48 | 49 | 50 | echo "plumber started.\n"; 51 | 52 | if ($this->config['daemonize']) { 53 | swoole_process::daemon(); 54 | } 55 | 56 | $this->logger = new Logger(['log_path' => $this->config['log_path']]); 57 | $this->output = new Logger(['log_path' => $this->config['output_path']]); 58 | 59 | $this->logger->info('plumber starting...'); 60 | 61 | $this->stats = $stats = $this->createListenerStats(); 62 | 63 | swoole_set_process_name('plumber: master'); 64 | $this->workers = $this->createWorkers($stats); 65 | $this->registerSignal(); 66 | 67 | $this->pidManager->save(posix_getpid()); 68 | 69 | swoole_timer_tick(1000, function($timerId) { 70 | $statses = $this->stats->getAll(); 71 | foreach ($statses as $pid => $s) { 72 | if ( ($s['last_update'] + $this->config['reserve_timeout'] + $this->config['execute_timeout']) > time()) { 73 | continue; 74 | } 75 | if (!$s['timeout']) { 76 | $this->logger->notice("process #{$pid} last upadte at ". date('Y-m-d H:i:s') . ', it is timeout.', $s); 77 | $this->stats->timeout($pid); 78 | } 79 | } 80 | }); 81 | 82 | } 83 | 84 | protected function stop() 85 | { 86 | $pid = $this->pidManager->get(); 87 | if (empty($pid)) { 88 | echo "plumber is not running...\n"; 89 | return ; 90 | } 91 | 92 | echo "plumber is stoping...."; 93 | exec("kill -15 {$pid}"); 94 | while(1) { 95 | if ($this->pidManager->get()) { 96 | sleep(1); 97 | continue; 98 | } 99 | 100 | echo "[OK]\n"; 101 | break; 102 | } 103 | } 104 | 105 | protected function restart() 106 | { 107 | $this->stop(); 108 | sleep(1); 109 | $this->start(); 110 | } 111 | 112 | private function createListenerStats() 113 | { 114 | $size = 0; 115 | foreach ($this->config['tubes'] as $tubeName => $tubeConfig) { 116 | $size += $tubeConfig['worker_num']; 117 | } 118 | return new ListenerStats($size, $this->logger); 119 | } 120 | 121 | /** 122 | * 创建队列的监听器 123 | */ 124 | private function createWorkers($stats) 125 | { 126 | $workers = []; 127 | foreach ($this->config['tubes'] as $tubeName => $tubeConfig) { 128 | for($i=0; $i<$tubeConfig['worker_num']; $i++) { 129 | $worker = new \swoole_process($this->createTubeLoop($tubeName, $stats), true); 130 | $worker->start(); 131 | 132 | swoole_event_add($worker->pipe, function($pipe) use ($worker) { 133 | $recv = $worker->read(); 134 | $this->output->info($recv); 135 | echo "recv:" . $recv . " {$pipe} " . "\n"; 136 | }); 137 | 138 | $workers[$worker->pid] = $worker; 139 | } 140 | } 141 | 142 | return $workers; 143 | } 144 | 145 | /** 146 | * 创建队列处理Loop 147 | */ 148 | private function createTubeLoop($tubeName, $stats) 149 | { 150 | return function($process) use ($tubeName, $stats) { 151 | $process->name("plumber: tube `{$tubeName}` task worker"); 152 | 153 | $listener = new TubeListener($tubeName, $process, $this->config, $this->logger, $stats); 154 | $listener->connect(); 155 | 156 | $beanstalk = $listener->getQueue(); 157 | 158 | $listener->loop(); 159 | }; 160 | } 161 | 162 | private function registerSignal() 163 | { 164 | swoole_process::signal(SIGCHLD, function() { 165 | while(1) { 166 | $ret = swoole_process::wait(false); 167 | if (!$ret) { 168 | break; 169 | } 170 | $this->logger->info("process #{$ret['pid']} exited.", $ret); 171 | unset($this->workers[$ret['pid']]); 172 | $this->stats->remove($ret['pid']); 173 | } 174 | }); 175 | 176 | $softkill = function($signo) { 177 | if ($this->state == 'stoping') { 178 | return ; 179 | } 180 | $this->state = 'stoping'; 181 | $this->logger->info("plumber is stoping...."); 182 | 183 | $this->stats->stop(); 184 | 185 | // 确保worker进程都退出后,再退出主进程 186 | swoole_timer_tick(1000, function($timerId) { 187 | if (!empty($this->workers)) { 188 | return ; 189 | } 190 | swoole_timer_clear($timerId); 191 | $this->pidManager->clear(); 192 | $this->logger->info('plumber is stopped.'); 193 | exit(); 194 | }); 195 | }; 196 | 197 | swoole_process::signal(SIGTERM, $softkill); 198 | swoole_process::signal(SIGINT, $softkill); 199 | } 200 | } -------------------------------------------------------------------------------- /src/TubeListener.php: -------------------------------------------------------------------------------- 1 | tubeName = $tubeName; 24 | $this->process = $process; 25 | $this->config = $config; 26 | $this->logger = $logger; 27 | $this->stats = $stats; 28 | } 29 | 30 | public function connect() 31 | { 32 | $tubeName = $this->tubeName; 33 | $process = $this->process; 34 | $logger = $this->logger; 35 | $queue = $this->queue = new BeanstalkClient($this->config['message_server']); 36 | 37 | $connected = $queue->connect(); 38 | if (!$connected) { 39 | $logger->critical("tube({$tubeName}, #{$process->pid}): worker start failed(connect queue failed), {$queue->getLatestError()}."); 40 | $process->exit(1); 41 | return ; 42 | } 43 | 44 | $watched = $queue->watch($tubeName); 45 | if (!$watched) { 46 | $logger->critical("tube({$tubeName}, #{$process->pid}): worker start failed(watch tube failed), {$queue->getLatestError()}."); 47 | $process->exit(1); 48 | return ; 49 | } 50 | 51 | $used = $queue->useTube($tubeName); 52 | if (!$used) { 53 | $logger->critical("tube({$tubeName}, #{$process->pid}): worker start failed(use tube failed), {$queue->getLatestError()}."); 54 | $process->exit(1); 55 | return ; 56 | } 57 | 58 | $logger->info("tube({$tubeName}, #{$process->pid}): watching."); 59 | 60 | return true; 61 | } 62 | 63 | public function loop() 64 | { 65 | $tubeName = $this->tubeName; 66 | $queue = $this->queue; 67 | $logger = $this->logger; 68 | $process = $this->process; 69 | $worker = $this->createQueueWorker($tubeName); 70 | 71 | while(true) { 72 | $this->stats->touch($tubeName, $process->pid, false, 0); 73 | $stoping = $this->stats->isStoping(); 74 | 75 | if ($stoping) { 76 | $this->logger->info("process #{$process->pid} is exiting."); 77 | $process->exit(1); 78 | break; 79 | } 80 | 81 | $job = $this->reserveJob(); 82 | if (empty($job)) { 83 | continue; 84 | } 85 | 86 | try { 87 | $result = $worker->execute($job); 88 | $this->stats->touch($tubeName, $process->pid, false, 0); 89 | } catch(\Exception $e) { 90 | $message = sprintf('tube({$tubeName}, #%d): execute job #%d exception, `%s`', $process->pid, $job['id'], $e->getMessage()); 91 | $logger->error($message, $job); 92 | continue; 93 | } 94 | 95 | $code = is_array($result) ? $result['code'] : $result; 96 | 97 | switch ($code) { 98 | case IWorker::FINISH: 99 | $this->finishJob($job, $result); 100 | break; 101 | case IWorker::RETRY: 102 | $this->retryJob($job, $result); 103 | break; 104 | case IWorker::BURY: 105 | $this->buryJob($job, $result); 106 | break; 107 | default: 108 | break; 109 | } 110 | 111 | } 112 | 113 | 114 | } 115 | 116 | private function reserveJob() 117 | { 118 | $tubeName = $this->tubeName; 119 | $queue = $this->queue; 120 | $logger = $this->logger; 121 | $process = $this->process; 122 | 123 | if ($this->times % 10 === 0) { 124 | $logger->info("tube({$tubeName}, #{$process->pid}): reserving {$this->times} times."); 125 | } 126 | $job = $queue->reserve($this->config['reserve_timeout']); 127 | $this->times ++; 128 | 129 | $this->stats->touch($tubeName, $process->pid, true, empty($job['id']) ? 0 : $job['id']); 130 | 131 | if (!$job) { 132 | $error = $queue->getLatestError(); 133 | $exit = false; 134 | switch ($error) { 135 | case 'DEADLINE_SOON': 136 | $logger->notice("tube({$tubeName}, #{$process->pid}): reserved DEADLINE_SOON."); 137 | break; 138 | case 'TIMED_OUT': 139 | break; 140 | default: 141 | $retry = 3; 142 | while($retry) { 143 | $connected = $queue->connect(); 144 | if ($connected) { 145 | $logger->info("tube({$tubeName}, #{$process->pid}): reconnected."); 146 | break; 147 | } 148 | 149 | $logger->info("tube({$tubeName}, #{$process->pid}): retry connection #{$retry}."); 150 | 151 | $retry -- ; 152 | sleep(1); 153 | } 154 | 155 | if ($retry === 0) { 156 | $logger->critical("tube({$tubeName}, #{$process->pid}): retry connection failed."); 157 | $exit = true; 158 | } 159 | break; 160 | } 161 | 162 | if ($exit) { 163 | $this->process->exit(1); 164 | } 165 | return null; 166 | } 167 | 168 | $job['body'] = json_decode($job['body'], true); 169 | $logger->info("tube({$tubeName}, #{$process->pid}): job #{$job['id']} reserved.", $job); 170 | 171 | return $job; 172 | } 173 | 174 | private function finishJob($job, $result) 175 | { 176 | $tubeName = $this->tubeName; 177 | $queue = $this->queue; 178 | $logger = $this->logger; 179 | $process = $this->process; 180 | 181 | $logger->info("tube({$tubeName}, #{$process->pid}): job #{$job['id']} execute finished."); 182 | 183 | $deleted = $queue->delete($job['id']); 184 | if (!$deleted) { 185 | $logger->error("tube({$tubeName}, #{$process->pid}): job #{$job['id']} delete failed, in successful executed.", $job); 186 | } 187 | 188 | } 189 | 190 | private function retryJob($job, $result) 191 | { 192 | $tubeName = $this->tubeName; 193 | $queue = $this->queue; 194 | $logger = $this->logger; 195 | $process = $this->process; 196 | 197 | $message = $job['body']; 198 | if (!isset($message['retry'])) { 199 | $message['retry'] = 0; 200 | } else { 201 | $message['retry'] = $message['retry'] + 1; 202 | } 203 | $stats = $queue->statsJob($job['id']); 204 | if ($stats === false) { 205 | $logger->error("tube({$tubeName}, #{$process->pid}): job #{$job['id']} get stats failed, in retry executed.", $job); 206 | return; 207 | } 208 | 209 | $logger->info("tube({$tubeName}, #{$process->pid}): job #{$job['id']} retry {$message['retry']} times."); 210 | $deleted = $queue->delete($job['id']); 211 | if (!$deleted) { 212 | $logger->error("tube({$tubeName}, #{$process->pid}): job #{$job['id']} delete failed, in retry executed.", $job); 213 | return; 214 | } 215 | 216 | $pri = isset($result['pri']) ? $result['pri'] : $stats['pri']; 217 | $delay = isset($result['delay']) ? $result['delay'] : $stats['delay']; 218 | $ttr = isset($result['ttr']) ? $result['ttr'] : $stats['ttr']; 219 | 220 | $puted = $queue->put($pri, $delay, $ttr, json_encode($message)); 221 | if (!$puted) { 222 | $logger->error("tube({$tubeName}, #{$process->pid}): job #{$job['id']} reput failed, in retry executed.", $job); 223 | return; 224 | } 225 | 226 | $logger->info("tube({$tubeName}, #{$process->pid}): job #{$job['id']} reputed, new job id is #{$puted}"); 227 | 228 | } 229 | 230 | private function buryJob($job, $result) 231 | { 232 | $tubeName = $this->tubeName; 233 | $queue = $this->queue; 234 | $logger = $this->logger; 235 | $process = $this->process; 236 | 237 | $stats = $queue->statsJob($job['id']); 238 | if ($stats === false) { 239 | $logger->error("tube({$tubeName}, #{$process->pid}): job #{$job['id']} get stats failed, in bury executed.", $job); 240 | return; 241 | } 242 | 243 | $pri = isset($result['pri']) ? $result['pri'] : $stats['pri']; 244 | $burried = $queue->bury($job['id'], $pri); 245 | if ($burried === false) { 246 | $logger->error("tube({$tubeName}, #{$process->pid}): job #{$job['id']} bury failed", $job); 247 | return; 248 | } 249 | 250 | $logger->info("tube({$tubeName}, #{$process->pid}): job #{$job['id']} buried."); 251 | 252 | } 253 | 254 | private function createQueueWorker($name) 255 | { 256 | $class = $this->config['tubes'][$name]['class']; 257 | $worker = new $class($name, $this->config['tubes'][$name]); 258 | $worker->setLogger($this->logger); 259 | return $worker; 260 | } 261 | 262 | public function getQueue() 263 | { 264 | return $this->queue; 265 | } 266 | 267 | } -------------------------------------------------------------------------------- /src/composer_autoload.php: -------------------------------------------------------------------------------- 1 |