├── pid └── .gitignore ├── todo.txt ├── tests ├── bootstrap.php ├── stop_multi.php ├── push.php ├── listen.php ├── listen_multi.php ├── TestHandler.php └── config.php ├── src ├── Exception │ └── Exception.php ├── Daemon │ ├── Command │ │ ├── MultipleWork │ │ │ ├── N.php │ │ │ ├── Status.php │ │ │ ├── Start.php │ │ │ ├── WakeUp.php │ │ │ ├── ReStart.php │ │ │ ├── Stop.php │ │ │ ├── D.php │ │ │ ├── Name.php │ │ │ ├── DaemonMultipleQueueTemplate.php │ │ │ └── MultipleWork.php │ │ └── SingleWork │ │ │ ├── Stop.php │ │ │ ├── WakeUp.php │ │ │ ├── Status.php │ │ │ ├── Start.php │ │ │ ├── ReStart.php │ │ │ ├── DaemonSingleQueueTemplate.php │ │ │ ├── D.php │ │ │ └── SingleWork.php │ ├── MultipleWorkDaemon.php │ ├── SingleWorkDaemon.php │ └── Work │ │ └── Work.php ├── Helpers │ ├── LoadConfig.php │ └── Log.php ├── Load.php ├── Handler │ └── JobHandler.php ├── Process │ ├── Pid.php │ ├── ProcessObserver.php │ ├── WorkerProcess.php │ └── ManageProcess.php ├── MultiWorker.php ├── Connection │ ├── RabbitMQ │ │ ├── Producer.php │ │ ├── RabbitMQ.php │ │ ├── Consumer.php │ │ └── OpenConnect.php │ ├── ConnectionFactory.php │ ├── Connection.php │ ├── Redis │ │ └── Redis.php │ └── Mns │ │ └── Mns.php ├── Queue.php ├── Worker.php └── Job.php ├── .gitignore ├── composer.json ├── README.md └── composer.lock /pid/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | 1、增加File存储介质 2 | 2、mysql死锁 3 | 3、mysql链接监控 4 | 4、多进程试试使用swoole 5 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | stop(); 17 | 18 | }catch (Exception $e){ 19 | echo $e->getCode()." -- ".$e->getFile() . " -- ". $e->getLine() . " : ".$e->getMessage(); 20 | } 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "mojiehai/queue_task", 3 | "description": "queue task", 4 | "license": "MIT", 5 | "keywords": ["queue task"], 6 | "authors": [ 7 | { 8 | "name": "mojiehai", 9 | "email": "804527505@qq.com" 10 | } 11 | ], 12 | "require": { 13 | "aliyun/aliyun-mns-php-sdk": "^1.1", 14 | "php-amqplib/php-amqplib": "^2.10", 15 | "php": ">=7.0", 16 | "ext-json": "*", 17 | "ext-redis": "*", 18 | "ext-pcntl": "*", 19 | "ext-posix": "*" 20 | }, 21 | "autoload": { 22 | "psr-4": {"QueueTask\\": "./src/"} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/push.php: -------------------------------------------------------------------------------- 1 | pushOn(new TestHandler(),'test',['test'=>'test'],'testQueue'); 16 | //$r = $res->pushOn(new TestHandler(),'test',['test'=>'test'],'testQueue1'); 17 | //$r = $res->pushOn(new TestHandler(),'test',['test'=>'test'],'testQueue2'); 18 | //$r = $res->pushOn(new TestHandler(),'test',['test'=>'test'],'testQueue3'); 19 | //$r = $res->laterOn(5,new TestHandler(),'test',['test'=>'test'],'testQueue'); 20 | echo date("Y-m-d H:i:s",time()); 21 | var_dump($r);die; -------------------------------------------------------------------------------- /tests/listen.php: -------------------------------------------------------------------------------- 1 | 'testQueue', //队列名称 15 | 'attempt' => 3, //队列任务失败尝试次数,0为不限制 16 | 'memory' => 128, //允许使用的最大内存 单位:M 17 | 'maxRunTime' => 100, // 最大运行时间 100s 18 | ]; 19 | 20 | try{ 21 | $start = microtime(true); 22 | 23 | (new Worker($config))->listen(); 24 | 25 | echo 'end, run: '.(microtime(true) - $start).PHP_EOL; 26 | }catch (Exception $e){ 27 | echo $e->getCode()." -- ".$e->getFile() . " -- ". $e->getLine() . " : ".$e->getMessage(); 28 | } 29 | -------------------------------------------------------------------------------- /src/Helpers/LoadConfig.php: -------------------------------------------------------------------------------- 1 | $v) { 27 | if (in_array($k, $this->configNameList)) { 28 | if (!is_null($v)) { 29 | $this->$k = $v; 30 | } 31 | } 32 | } 33 | return $this; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/Load.php: -------------------------------------------------------------------------------- 1 | setConfig($config['log']); 25 | } 26 | 27 | // 加载链接列表 28 | if (isset($config['connectList'])) { 29 | ConnectionFactory::$connectList = $config['connectList']; 30 | } 31 | 32 | // 加载当前链接 33 | if (isset($config['currentConnect'])) { 34 | ConnectionFactory::$currentConnect = $config['currentConnect']; 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /tests/listen_multi.php: -------------------------------------------------------------------------------- 1 | 'testQueue', //队列名称 16 | 'attempt' => 3, //队列任务失败尝试次数,0为不限制 17 | 'memory' => 128, //允许使用的最大内存 单位:M 18 | 'maxRunTime' => 100, // 最大运行时间 100s 19 | ]; 20 | 21 | $config2 = [ 22 | 'queueName' => 'testQueue1', //队列名称 23 | 'attempt' => 3, //队列任务失败尝试次数,0为不限制 24 | 'memory' => 128, //允许使用的最大内存 单位:M 25 | 'maxRunTime' => 100, // 最大运行时间 100s 26 | ]; 27 | 28 | try{ 29 | 30 | (new MultiWorker('tag1')) 31 | ->addWorker($config1, 1) 32 | ->addWorker($config2, 2) 33 | ->start(); 34 | 35 | }catch (Exception $e){ 36 | echo $e->getCode()." -- ".$e->getFile() . " -- ". $e->getLine() . " : ".$e->getMessage(); 37 | } 38 | -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/Status.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 26 | $multipleWork->commandStatus(); 27 | } 28 | 29 | /** 30 | * 获取命令 31 | * @return string 32 | */ 33 | public static function getCommandStr() 34 | { 35 | return 'status'; 36 | } 37 | 38 | /** 39 | * 获取命令描述 40 | * @return string 41 | */ 42 | public static function getCommandDescription() 43 | { 44 | return 'process status'; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/Start.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 26 | 27 | $multipleWork->commandStart(); 28 | 29 | } 30 | 31 | /** 32 | * 获取命令 33 | * @return string 34 | */ 35 | public static function getCommandStr() 36 | { 37 | return 'start'; 38 | } 39 | 40 | /** 41 | * 获取命令描述 42 | * @return string 43 | */ 44 | public static function getCommandDescription() 45 | { 46 | return 'start process'; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/WakeUp.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 26 | $multipleWork->commandWakeup(); 27 | 28 | } 29 | 30 | /** 31 | * 获取命令 32 | * @return string 33 | */ 34 | public static function getCommandStr() 35 | { 36 | return 'wakeup'; 37 | } 38 | 39 | /** 40 | * 获取命令描述 41 | * @return string 42 | */ 43 | public static function getCommandDescription() 44 | { 45 | return 'wakeup worker process'; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/Stop.php: -------------------------------------------------------------------------------- 1 | getSingleWork(); 26 | $singleWork->commandStop(); 27 | 28 | } 29 | 30 | /** 31 | * 获取命令 32 | * @return string 33 | */ 34 | public static function getCommandStr() 35 | { 36 | return 'stop'; 37 | } 38 | 39 | /** 40 | * 获取命令描述 41 | * @return string 42 | */ 43 | public static function getCommandDescription() 44 | { 45 | return 'stop process'; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/ReStart.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 26 | 27 | $multipleWork->commandRestart(); 28 | 29 | } 30 | 31 | /** 32 | * 获取命令 33 | * @return string 34 | */ 35 | public static function getCommandStr() 36 | { 37 | return 'restart'; 38 | } 39 | 40 | /** 41 | * 获取命令描述 42 | * @return string 43 | */ 44 | public static function getCommandDescription() 45 | { 46 | return 'restart process'; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/WakeUp.php: -------------------------------------------------------------------------------- 1 | getSingleWork(); 26 | $singleWork->commandWakeup(); 27 | 28 | } 29 | 30 | /** 31 | * 获取命令 32 | * @return string 33 | */ 34 | public static function getCommandStr() 35 | { 36 | return 'wakeup'; 37 | } 38 | 39 | /** 40 | * 获取命令描述 41 | * @return string 42 | */ 43 | public static function getCommandDescription() 44 | { 45 | return 'wakeup worker process'; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/Status.php: -------------------------------------------------------------------------------- 1 | getSingleWork(); 27 | $singleWork->commandStatus(); 28 | 29 | } 30 | 31 | /** 32 | * 获取命令 33 | * @return string 34 | */ 35 | public static function getCommandStr() 36 | { 37 | return 'status'; 38 | } 39 | 40 | /** 41 | * 获取命令描述 42 | * @return string 43 | */ 44 | public static function getCommandDescription() 45 | { 46 | return 'process status'; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/Start.php: -------------------------------------------------------------------------------- 1 | getSingleWork(); 28 | 29 | $singleWork->commandStart(); 30 | 31 | } 32 | 33 | /** 34 | * 获取命令 35 | * @return string 36 | */ 37 | public static function getCommandStr() 38 | { 39 | return 'start'; 40 | } 41 | 42 | /** 43 | * 获取命令描述 44 | * @return string 45 | */ 46 | public static function getCommandDescription() 47 | { 48 | return 'start process'; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/ReStart.php: -------------------------------------------------------------------------------- 1 | getSingleWork(); 28 | 29 | $singleWork->commandRestart(); 30 | 31 | } 32 | 33 | /** 34 | * 获取命令 35 | * @return string 36 | */ 37 | public static function getCommandStr() 38 | { 39 | return 'restart'; 40 | } 41 | 42 | /** 43 | * 获取命令描述 44 | * @return string 45 | */ 46 | public static function getCommandDescription() 47 | { 48 | return 'restart process'; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/Stop.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 29 | $multipleWork->commandStop(); 30 | 31 | } 32 | 33 | /** 34 | * 获取命令 35 | * @return string 36 | */ 37 | public static function getCommandStr() 38 | { 39 | return 'stop'; 40 | } 41 | 42 | /** 43 | * 获取命令描述 44 | * @return string 45 | */ 46 | public static function getCommandDescription() 47 | { 48 | return 'stop process'; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/D.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 47 | $multipleWork->background = true; 48 | } 49 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/Name.php: -------------------------------------------------------------------------------- 1 | getMultipleWork(); 47 | $multipleWork->queueName = $this->param; 48 | } 49 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/DaemonSingleQueueTemplate.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'start' => '\QueueTask\Daemon\Command\SingleWork\Start', 23 | 'stop' => '\QueueTask\Daemon\Command\SingleWork\Stop', 24 | 'restart' => '\QueueTask\Daemon\Command\SingleWork\ReStart', 25 | 'status' => '\QueueTask\Daemon\Command\SingleWork\Status', 26 | 'wakeup' => '\QueueTask\Daemon\Command\SingleWork\WakeUp', 27 | ], 28 | 'options' => [ 29 | 'd' => '\QueueTask\Daemon\Command\SingleWork\D', 30 | ], 31 | ]; 32 | 33 | /** 34 | * 获取模板内容 35 | * @return string 36 | */ 37 | public function getTemplateStr() 38 | { 39 | return ' -[d]'; 40 | } 41 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/D.php: -------------------------------------------------------------------------------- 1 | getSingleWork(); 49 | $singleWork->background = true; 50 | } 51 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/DaemonMultipleQueueTemplate.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'start' => '\QueueTask\Daemon\Command\MultipleWork\Start', 23 | 'stop' => '\QueueTask\Daemon\Command\MultipleWork\Stop', 24 | 'restart' => '\QueueTask\Daemon\Command\MultipleWork\ReStart', 25 | 'status' => '\QueueTask\Daemon\Command\MultipleWork\Status', 26 | 'wakeup' => '\QueueTask\Daemon\Command\MultipleWork\WakeUp', 27 | ], 28 | 'options' => [ 29 | 'd' => '\QueueTask\Daemon\Command\MultipleWork\D', 30 | 'n' => '\QueueTask\Daemon\Command\MultipleWork\N', 31 | 'name' => '\QueueTask\Daemon\Command\MultipleWork\Name', 32 | ], 33 | ]; 34 | 35 | /** 36 | * 获取模板内容 37 | * @return string 38 | */ 39 | public function getTemplateStr() 40 | { 41 | return ' -[n|name] -[d]'; 42 | } 43 | } -------------------------------------------------------------------------------- /tests/TestHandler.php: -------------------------------------------------------------------------------- 1 | getErrors())); 22 | } 23 | 24 | /** 25 | * 任务成功回调 26 | * @param Job $job 任务 27 | * @param string $func 执行的方法 28 | * @param array $data 参数 29 | * @return mixed 30 | */ 31 | public function success(Job $job, $func, $data) 32 | { 33 | Log::info('success run handler -- func: '.$func.' -- params: '.json_encode($data).',error:'.json_encode($job->getErrors())); 34 | } 35 | 36 | 37 | public function test(Job $job,$data) 38 | { 39 | if(true) { 40 | Log::info('run handler -- func: test -- params: '.json_encode($data). '; result : true'); 41 | } else { 42 | Log::info('run handler -- func: test -- params: '.json_encode($data). '; result : false'); 43 | $job->setOnceFailure('error info , times:'.$job->getAttempts()); 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/Handler/JobHandler.php: -------------------------------------------------------------------------------- 1 | $func($job, $data); 28 | } else { 29 | $job->setForceFailure('method "'.$func .'" does not exist'); 30 | } 31 | } catch (\Exception $e) { 32 | $job->setOnceFailure($e->getMessage()); 33 | } 34 | } 35 | 36 | 37 | /** 38 | * 失败回调方法 39 | * @param Job $job 任务 40 | * @param string $func 执行的方法 41 | * @param array $data 参数 42 | * @return mixed 43 | */ 44 | abstract public function failed(Job $job, $func, $data); 45 | 46 | 47 | /** 48 | * 任务成功回调 49 | * @param Job $job 任务 50 | * @param string $func 执行的方法 51 | * @param array $data 参数 52 | * @return mixed 53 | */ 54 | abstract public function success(Job $job, $func, $data); 55 | 56 | 57 | /** 58 | * 回调方法 59 | * @param $job 60 | * @param $data 61 | */ 62 | /** 63 | * public function func($job,$data){} 64 | */ 65 | 66 | } -------------------------------------------------------------------------------- /src/Process/Pid.php: -------------------------------------------------------------------------------- 1 | uniqueTag = $uniqueTag; 30 | } 31 | 32 | /** 33 | * 获取pid 34 | * @return int 35 | */ 36 | public function get() 37 | { 38 | if ($this->pid > 0) { 39 | return $this->pid; 40 | } else { 41 | $pid = @file_get_contents($this->pidPath()); 42 | return intval($pid); 43 | } 44 | } 45 | 46 | /** 47 | * 设置pid 48 | * @param int $pid 49 | * @return bool 50 | */ 51 | public function set(int $pid) 52 | { 53 | $result = @file_put_contents($this->pidPath(), $pid); 54 | $this->pid = $this->get(); 55 | return $result ? true : false; 56 | } 57 | 58 | /** 59 | * 是否存活 60 | * @return bool 61 | */ 62 | public function isAlive() 63 | { 64 | $pid = $this->get(); 65 | if ($pid != 0 && Process::kill($pid, 0)) { 66 | return true; 67 | } else { 68 | return false; 69 | } 70 | } 71 | 72 | /** 73 | * 获取pid文件 74 | * @return string 75 | */ 76 | protected function pidPath() 77 | { 78 | return __DIR__.'/../../pid/manage_'.$this->uniqueTag.'.pid'; 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/MultiWorker.php: -------------------------------------------------------------------------------- 1 | 1.10.3'); 41 | } 42 | 43 | // 初始化进程管理器 44 | $this->manage = new ManageProcess($uniqueTag); 45 | } 46 | 47 | /** 48 | * 添加worker 49 | * @param array $workerConfig 50 | * @param int $processNum 51 | * @return $this 52 | */ 53 | public function addWorker(array $workerConfig, int $processNum = 1) 54 | { 55 | $processNum = $processNum < 1 ? 1 : $processNum; 56 | $this->manage->addWorker($workerConfig, $processNum); 57 | return $this; 58 | } 59 | 60 | /** 61 | * 开始运行 62 | */ 63 | public function start() 64 | { 65 | $this->manage->start(); 66 | } 67 | 68 | /** 69 | * 停止运行 70 | */ 71 | public function stop() 72 | { 73 | $this->manage->stop(); 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/Process/ProcessObserver.php: -------------------------------------------------------------------------------- 1 | process = $process; 40 | $this->parentPid = $parentPid; 41 | } 42 | 43 | /** 44 | * 注册信号 45 | */ 46 | public function registerSignal() 47 | { 48 | pcntl_signal(SIGTERM, [$this, 'signalCall'], false); 49 | } 50 | 51 | /** 52 | * 进程检测 53 | * @return void 54 | */ 55 | public function check() 56 | { 57 | // 判断父进程是否存在 58 | if (!Process::kill($this->parentPid, 0)) { 59 | $this->isStop = true; 60 | return; 61 | } 62 | 63 | // 监听信号,执行回调 64 | pcntl_signal_dispatch(); 65 | } 66 | 67 | /** 68 | * 上报进程停止信息给主进程 69 | */ 70 | public function reportStopInfo() 71 | { 72 | $this->process->write('exit'); 73 | } 74 | 75 | /** 76 | * 信号回调 77 | * @param $signal 78 | */ 79 | public function signalCall($signal) 80 | { 81 | switch ($signal) { 82 | case SIGTERM: 83 | $this->isStop = true; 84 | break; 85 | } 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /tests/config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'logRoot' => __DIR__ . '/../runtime/log', 6 | 'fileName' => '\q\u\e\u\e_Y-m-d.\l\o\g', 7 | ], 8 | 9 | 'connectList' => [ 10 | 'Redis' => [ 11 | 'class' => '\\QueueTask\\Connection\\Redis\\Redis', 12 | 'config' => [ 13 | 'popTimeout' => 3, // pop阻塞的超时时长 s 14 | 'host' => '127.0.0.1', // 数据库地址 15 | 'port' => 6379, // 数据库端口 16 | 'db' => 0, // 库 17 | 'password' => null, // 密码 18 | 'connTimeout' => 1, // 链接超时 19 | ], 20 | ], 21 | 'Mns' => [ 22 | 'class' => '\\QueueTask\\Connection\\Mns\\Mns', 23 | 'config' => [ 24 | 'popTimeout' => 3, // pop阻塞的超时时长 s 25 | 'accessKeyID' => '', // Mns key id 26 | 'accessKeySecret' => '', // Mns key secret 27 | 'endpoint' => '', // Mns end point 28 | ], 29 | ], 30 | 'RabbitMQ' => [ 31 | 'class' => '\\QueueTask\\Connection\\RabbitMQ\\RabbitMQ', 32 | 'config' => [ 33 | // exchanges需要设置为direct,持久化存储,不自动确认消息 34 | 'popTimeout' => 3, // pop阻塞的超时时长 s 35 | 'host' => '127.0.0.1', 36 | 'port' => 5672, 37 | 'username' => '', 38 | 'password' => '', 39 | 'vhost' => '/', // 虚拟主机 40 | 'exChanges' => '', // 直连交换机名称 41 | ], 42 | ], 43 | ], 44 | 45 | 'currentConnect' => 'RabbitMQ', 46 | ]; 47 | -------------------------------------------------------------------------------- /src/Daemon/MultipleWorkDaemon.php: -------------------------------------------------------------------------------- 1 | multipleWork = $multipleWork; 64 | return $this; 65 | } 66 | 67 | /** 68 | * 获取工作集合 69 | * @return MultipleWork 70 | */ 71 | public function getMultipleWork() 72 | { 73 | return $this->multipleWork; 74 | } 75 | 76 | /** 77 | * 监听命令 78 | */ 79 | public function listenCommand() 80 | { 81 | (new Command(new DaemonMultipleQueueTemplate()))->run(); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/Daemon/SingleWorkDaemon.php: -------------------------------------------------------------------------------- 1 | setSingleWork($singleWork); 36 | } 37 | 38 | /** 39 | * clone 40 | * @throws Exception 41 | */ 42 | public function __clone() 43 | { 44 | throw new Exception("This class cannot be cloned" , -101); 45 | } 46 | 47 | /** 48 | * 单例 49 | * @param SingleWork $singleWork 单工作类 50 | * @return SingleWorkDaemon 51 | * @throws Exception 52 | */ 53 | public static function getInstance(SingleWork $singleWork = null) 54 | { 55 | if (!(static::$instance instanceof static)) { 56 | if (!empty($singleWork)) { 57 | static::$instance = new SingleWorkDaemon($singleWork); 58 | } else { 59 | throw new Exception('There is no work'); 60 | } 61 | } else { 62 | if (!empty($singleWork)) { 63 | static::$instance->setSingleWork($singleWork); 64 | } 65 | } 66 | return static::$instance; 67 | } 68 | 69 | /** 70 | * @param SingleWork $singleWork 71 | */ 72 | private function setSingleWork(SingleWork $singleWork) 73 | { 74 | $this->singleWork = $singleWork; 75 | } 76 | 77 | /** 78 | * @return SingleWork 79 | */ 80 | public function getSingleWork() 81 | { 82 | return $this->singleWork; 83 | } 84 | 85 | /** 86 | * 监听命令 87 | */ 88 | public function listenCommand() 89 | { 90 | (new Command(new DaemonSingleQueueTemplate()))->run(); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/Connection/RabbitMQ/Producer.php: -------------------------------------------------------------------------------- 1 | initQueueName != $queueName) // 当前初始化的队列不是参数指定的队列,需要重新初始化 39 | ) { 40 | parent::initQueue($queueName, $force); 41 | $this->initQueueName = $queueName; 42 | } 43 | } 44 | 45 | /** 46 | * 压入队列 47 | * @param Job $job 48 | * @param string $queueName 队列名 49 | * @param int $times 次数 50 | * @return boolean 51 | */ 52 | public function push(Job $job, $queueName, $times = 1) 53 | { 54 | try { 55 | // 初始化队列 56 | $this->initQueue($queueName); 57 | 58 | // 发送消息 声明持久消息 59 | $message = new AMQPMessage( 60 | Job::Encode($job), 61 | ['content_type' => 'text/plain', 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT] 62 | ); 63 | $this->channel->basic_publish($message, $this->exChanges, $this->routingKey); 64 | 65 | return true; 66 | 67 | } catch (\Exception $e) { 68 | if ($times < $this->pushMaxTryTimes) { 69 | // 异常则压入出错 70 | Log::warning('RabbitMQ SendMessage Failed(time:'.$times.'): '.$e); 71 | // 重新连接 72 | $this->initQueue($queueName, true); 73 | return $this->push($job, $queueName, $times + 1); 74 | } else { 75 | // 异常则压入出错 76 | Log::error('RabbitMQ SendMessage Failed(time:'.$times.'): '.$e); 77 | return false; 78 | } 79 | } 80 | } 81 | 82 | 83 | } -------------------------------------------------------------------------------- /src/Process/WorkerProcess.php: -------------------------------------------------------------------------------- 1 | workerConfig = $workerConfig; 44 | } 45 | 46 | /** 47 | * start 48 | * @param int $parentPid 父进程pid 49 | * @param string $uniqueTag 父进程的唯一标识 50 | * @return int 51 | */ 52 | public function start($parentPid, $uniqueTag) 53 | { 54 | $workerConfig = $this->workerConfig; 55 | $this->process = new Process(function (Process $process) use ($workerConfig, $parentPid, $uniqueTag) { 56 | // 子进程 57 | 58 | // 初始化worker 59 | $worker = new Worker($workerConfig); 60 | 61 | // 设置进程名称 62 | @swoole_set_process_name('queue_task_'.$uniqueTag.':'.$worker->queueName); 63 | 64 | // 初始化进程观察者 65 | $observer = new ProcessObserver($process, $parentPid); 66 | 67 | // 注册信号 68 | $observer->registerSignal(); 69 | 70 | // 绑定观察者 71 | $worker->bindProcessObserver($observer); 72 | 73 | // 启动worker 74 | $worker->listen(); 75 | 76 | }, false, SOCK_DGRAM); 77 | 78 | // 设置管道通讯非阻塞模式 79 | $this->process->setBlocking(false); 80 | 81 | $this->isStop = false; 82 | 83 | $this->pid = $this->process->start(); 84 | 85 | Log::info('process start, pid: ' . $this->pid); 86 | 87 | return $this->pid; 88 | } 89 | 90 | /** 91 | * 读取命令 92 | */ 93 | public function read() 94 | { 95 | $command = @$this->process->read(); 96 | switch ($command) { 97 | case 'exit': 98 | $this->isStop = true; 99 | break; 100 | } 101 | } 102 | 103 | /** 104 | * 进程是否存活 105 | * @return bool 106 | */ 107 | public function isAlive() 108 | { 109 | if ($this->pid != 0 && Process::kill($this->pid, 0)) { 110 | return true; 111 | } else { 112 | return false; 113 | } 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /src/Connection/ConnectionFactory.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'class' => '\\QueueTask\\Connection\\Redis\\Redis', 22 | 'config' => [ 23 | 'popTimeout' => 3, // pop阻塞的超时时长 s 24 | 'host' => '127.0.0.1', // 数据库地址 25 | 'port' => 6379, // 数据库端口 26 | 'db' => 0, // 库 27 | 'password' => null, // 密码 28 | 'connTimeout' => 1, // 链接超时 29 | ], 30 | ], 31 | 32 | 'Mns' => [ 33 | 'class' => '\\QueueTask\\Connection\\Mns\\Mns', 34 | 'config' => [ 35 | 'popTimeout' => 3, // pop阻塞的超时时长 s 36 | 'accessKeyID' => '', // Mns key id 37 | 'accessKeySecret' => '', // Mns key secret 38 | 'endpoint' => '', // Mns end point 39 | ], 40 | ], 41 | 42 | 'RabbitMQ' => [ 43 | 'class' => '\\QueueTask\\Connection\\RabbitMQ\\RabbitMQ', 44 | 'config' => [ 45 | // exchanges需要设置为direct,持久化存储,不自动确认消息 46 | 'popTimeout' => 3, // pop阻塞的超时时长 s 47 | 'host' => '127.0.0.1', // 主机 48 | 'port' => 5672, // 端口 49 | 'username' => '', // 用户名 50 | 'password' => '', // 密码 51 | 'vhost' => '/', // 虚拟主机 52 | 'exChanges' => '', // 直连交换机名称 53 | ], 54 | ], 55 | 56 | ]; 57 | 58 | /** 59 | * @var string 当前默认使用的链接 60 | */ 61 | public static $currentConnect = 'Redis'; 62 | 63 | /** 64 | * 获取链接对象 65 | * @param string $currentName 当前链接方式 66 | * @return Connection 67 | */ 68 | public static function getInstance($currentName) 69 | { 70 | $connect = isset(self::$connectList[$currentName]) ? self::$connectList[$currentName] : []; 71 | if (empty($connect) || !isset($connect['class']) || empty($connect['class'])) { 72 | Log::error('There is no connection available type'); 73 | return null; 74 | } else { 75 | $class = $connect['class']; 76 | $config = isset($connect['config']) ? $connect['config'] : []; 77 | return $class::getInstance($config); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/Connection/RabbitMQ/RabbitMQ.php: -------------------------------------------------------------------------------- 1 | producer instanceof Producer)) { 51 | $this->producer = new Producer($this->config); 52 | } 53 | return $this->producer; 54 | } 55 | 56 | /** 57 | * 获取消费者 58 | * @return Consumer 59 | */ 60 | protected function getConsumer() 61 | { 62 | if (!($this->consumer instanceof Consumer)) { 63 | $this->consumer = new Consumer($this->config, $this); 64 | } 65 | return $this->consumer; 66 | } 67 | 68 | 69 | /** 70 | * 执行pop出来的任务(阻塞方法) 71 | * @param string $queueName 72 | */ 73 | public function popRun($queueName) 74 | { 75 | $this->pop($queueName); 76 | } 77 | 78 | /** 79 | * 关闭连接 80 | * @return boolean 81 | */ 82 | public function close() 83 | { 84 | if (!empty($this->producer)) { 85 | $this->producer->close(); 86 | } 87 | if (!empty($this->consumer)) { 88 | $this->consumer->close(); 89 | } 90 | return true; 91 | } 92 | 93 | /** 94 | * 弹出队头任务(blocking) 95 | * @param string $queueName 队列名称 96 | * @param array & $extends 额外需要传递给ack方法的参数 97 | * @return null 98 | */ 99 | protected function pop($queueName, & $extends = []) 100 | { 101 | $this->getConsumer()->pop($queueName); 102 | } 103 | 104 | /** 105 | * 确认任务 106 | * @param string $queueName 107 | * @param Job $job 108 | * @param array $extends 109 | */ 110 | protected function ack($queueName, Job $job = null, $extends = []) 111 | { 112 | // rabbitmq的ack在pop的回调函数内部,不在此声明 113 | } 114 | 115 | /** 116 | * 压入队列 117 | * @param Job $job 118 | * @param String $queueName 队列名 119 | * @return boolean 120 | */ 121 | public function push(Job $job, $queueName) 122 | { 123 | return $this->getProducer()->push($job, $queueName); 124 | } 125 | 126 | /** 127 | * 添加一条延迟任务 128 | * @param int $delay 129 | * @param Job $job 130 | * @param String $queueName 131 | * @return bool 132 | */ 133 | public function later($delay, Job $job, $queueName) 134 | { 135 | // mq不支持延迟队列 136 | Log::error('RabbitMQ Not Support Later Push,delay: '.$delay.',Job: '. Job::Encode($job)); 137 | return false; 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /src/Helpers/Log.php: -------------------------------------------------------------------------------- 1 | configNameList = ['logRoot', 'fileName']; 32 | } 33 | 34 | /** 35 | * @return Log 36 | */ 37 | public static function getInstance() 38 | { 39 | if (!(self::$instance instanceof self)) { 40 | self::$instance = new Log(); 41 | } 42 | return self::$instance; 43 | } 44 | 45 | /** 46 | * @param $level 47 | * @param string $message 48 | * @param array $extends 49 | * @return bool 50 | */ 51 | protected function write($level, $message = '', $extends = []) 52 | { 53 | if (!is_dir($this->logRoot)) { 54 | $mkdir = mkdir($this->logRoot, 0777, true); 55 | if (!$mkdir) { 56 | return false; 57 | } 58 | } 59 | 60 | $time = time(); 61 | 62 | $filePath = rtrim($this->logRoot, '/') . '/' . date($this->fileName, $time); 63 | 64 | // 信息 65 | $string = sprintf( 66 | "[%s][%s]: %s; extends=%s \n", 67 | $level, date('Y-m-d m:d:s', $time), $message, json_encode($extends) 68 | ); 69 | 70 | if (file_put_contents($filePath, $string, FILE_APPEND)) { 71 | return true; 72 | } else { 73 | return false; 74 | } 75 | } 76 | 77 | /** 78 | * notice 79 | * @param string $message 80 | * @param array $extends 81 | * @return bool 82 | */ 83 | public static function notice($message = '', $extends = []) 84 | { 85 | return self::getInstance()->write('notice', $message, $extends); 86 | } 87 | 88 | /** 89 | * info 90 | * @param string $message 91 | * @param array $extends 92 | * @return bool 93 | */ 94 | public static function info($message = '', $extends = []) 95 | { 96 | return self::getInstance()->write('info', $message, $extends); 97 | } 98 | 99 | /** 100 | * warning 101 | * @param string $message 102 | * @param array $extends 103 | * @return bool 104 | */ 105 | public static function warning($message = '', $extends = []) 106 | { 107 | return self::getInstance()->write('warning', $message, $extends); 108 | } 109 | 110 | /** 111 | * error 112 | * @param string $message 113 | * @param array $extends 114 | * @return bool 115 | */ 116 | public static function error($message = '', $extends = []) 117 | { 118 | return self::getInstance()->write('error', $message, $extends); 119 | } 120 | 121 | /** 122 | * fatal 123 | * @param string $message 124 | * @param array $extends 125 | * @return bool 126 | */ 127 | public static function fatal($message = '', $extends = []) 128 | { 129 | return self::getInstance()->write('fatal', $message, $extends); 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /src/Connection/RabbitMQ/Consumer.php: -------------------------------------------------------------------------------- 1 | rabbitMq = $rabbitMq; 36 | } 37 | 38 | /** 39 | * 初始化队列 40 | * @param string $queueName 41 | * @param bool $force 是否强制初始化 42 | */ 43 | protected function initQueue($queueName, $force = false) 44 | { 45 | if ( 46 | $force || // 强制初始化 47 | ($this->initQueueName != $queueName) // 当前初始化的队列不是参数指定的队列,需要重新初始化 48 | ) { 49 | // 初始化队列 50 | parent::initQueue($queueName, $force); 51 | 52 | // rabbit Connection对象 53 | $rabbitMq = $this->rabbitMq; 54 | 55 | // 消费回调 56 | $callback = function (AMQPMessage $message) use ($rabbitMq, $queueName) { 57 | // 任务 58 | $job = null; 59 | 60 | // 消息 61 | $body = $message->getBody(); 62 | if (empty($body)) { 63 | // 任务为空 64 | Log::warning('RabbitMQ Pop Message Is Empty'); 65 | } else { 66 | // 解析成任务 67 | $job = Job::Decode($body); 68 | if ($job instanceof Job) { 69 | $rabbitMq->runJob($job, $queueName); 70 | } else { 71 | // 消息没有解析成任务 72 | Log::error('RabbitMQ Pop Message Not Job, Message: '.$body); 73 | } 74 | } 75 | 76 | // 确认任务 77 | $message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']); 78 | }; 79 | 80 | // 在处理并确认前一个消息之前,不要向消费者发送新消息 81 | $this->channel->basic_qos(null, 1, null); 82 | 83 | // 配置消费者 no_ack:false需要手动确认消息完成 84 | $this->channel->basic_consume($queueName, '', false, false, 85 | false, false, $callback); 86 | 87 | $this->initQueueName = $queueName; 88 | } 89 | 90 | } 91 | 92 | /** 93 | * 任务出队 阻塞 94 | * @param $queueName 95 | * @return void 96 | */ 97 | public function pop($queueName) 98 | { 99 | // 初始化队列 100 | $this->initQueue($queueName); 101 | try { 102 | 103 | //等待mq弹出数据,超时时间为popTimeOut 104 | $this->channel->wait(null, false, $this->rabbitMq->popTimeOut); 105 | 106 | } catch (\ErrorException $error) { 107 | 108 | // 重新连接 109 | $this->initQueue($queueName, true); 110 | 111 | Log::error('RabbitMQ Pop Failed:'.$error->getMessage()); 112 | 113 | } catch(AMQPRuntimeException $error) { 114 | 115 | // 重新连接 116 | $this->initQueue($queueName, true); 117 | 118 | Log::error('RabbitMQ AMQPRuntimeException:'.$error->getMessage()); 119 | 120 | } catch(AMQPTimeoutException $error) { 121 | // 超时,说明当前队列的消费者中没有任务 122 | } 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /src/Connection/Connection.php: -------------------------------------------------------------------------------- 1 | config = $config; 45 | if (isset($config['popTimeout']) && $config['popTimeout'] > 0) { 46 | $this->popTimeOut = $config['popTimeout']; 47 | } 48 | } 49 | 50 | /** 51 | * 设置处理程序 52 | * @param \Closure $handler 53 | */ 54 | public function setHandler(\Closure $handler) 55 | { 56 | $this->handler = $handler; 57 | } 58 | 59 | /** 60 | * Connection destruct. 61 | */ 62 | public function __destruct() 63 | { 64 | $this->close(); 65 | static::$instance = null; 66 | } 67 | 68 | /** 69 | * 不允许被克隆 70 | * @throws Exception 71 | */ 72 | protected function __clone() 73 | { 74 | throw new Exception("This class cannot be cloned" , -101); 75 | } 76 | 77 | /** 78 | * 获取单例 79 | * @param array $config 配置参数 80 | * @return Connection|null 81 | */ 82 | public static function getInstance($config = []) 83 | { 84 | if(!(static::$instance instanceof Connection)) { 85 | static::$instance = new static($config); 86 | } 87 | return static::$instance; 88 | } 89 | 90 | /** 91 | * 关闭连接 92 | * @return boolean 93 | */ 94 | abstract public function close(); 95 | 96 | /** 97 | * 执行pop出来的任务(阻塞方法) 98 | * @param string $queueName 99 | */ 100 | public function popRun($queueName) 101 | { 102 | $extends = []; 103 | $job = $this->pop($queueName, $extends); 104 | if ($job instanceof Job) { 105 | // 执行任务 106 | $this->runJob($job, $queueName); 107 | 108 | // 确认任务 109 | $this->ack($queueName, $job, $extends); 110 | } 111 | } 112 | 113 | /** 114 | * 执行任务 115 | * @param Job $job 116 | * @param $queueName 117 | */ 118 | public function runJob(Job $job, $queueName) 119 | { 120 | // 执行任务 121 | $handler = $this->handler; 122 | $handler($job, $queueName); 123 | } 124 | 125 | /** 126 | * 弹出队头任务(blocking) 127 | * @param string $queueName 队列名称 128 | * @param array & $extends 额外需要传递给ack方法的参数 129 | * @return Job|null 130 | */ 131 | abstract protected function pop($queueName, & $extends = []); 132 | 133 | /** 134 | * 确认任务 135 | * @param string $queueName 136 | * @param Job $job 137 | * @param array $extends 138 | */ 139 | abstract protected function ack($queueName, Job $job = null, $extends = []); 140 | 141 | /** 142 | * 压入队列 143 | * @param Job $job 144 | * @param String $queueName 队列名 145 | * @return boolean 146 | */ 147 | abstract public function push(Job $job, $queueName); 148 | 149 | 150 | /** 151 | * 添加一条延迟任务 152 | * @param int $delay 延迟的秒数 153 | * @param Job $job 任务 154 | * @param String $queueName 队列名 155 | * @return boolean 156 | */ 157 | abstract public function later($delay, Job $job, $queueName); 158 | 159 | } -------------------------------------------------------------------------------- /src/Daemon/Command/SingleWork/SingleWork.php: -------------------------------------------------------------------------------- 1 | work = $work; 35 | } 36 | 37 | /** 38 | * 检测命令是否可用 39 | * @return bool 40 | * @throws Exception 41 | */ 42 | public function checkCommand() 43 | { 44 | // 如果工作为空,则退出 45 | if (empty($this->work)) { 46 | throw new Exception('there is no queue configured to run'); 47 | } 48 | return true; 49 | } 50 | 51 | ############################# command ############################### 52 | /** 53 | * 命令:start 54 | * @throws Exception 55 | */ 56 | public function commandStart() 57 | { 58 | $this->checkCommand(); 59 | 60 | $manage = new Manage($this->work->getProcessConfig()); 61 | 62 | $manage->setWorkInit($this->work->getWorkInit())->setWork($this->work->getWork()); 63 | 64 | if ($this->background) { 65 | // 后台运行 66 | $manage->setBackground(); 67 | } 68 | $manage->start(); 69 | 70 | } 71 | 72 | /** 73 | * 命令:stop 74 | * @throws Exception 75 | */ 76 | public function commandStop() 77 | { 78 | $this->checkCommand(); 79 | 80 | (new Manage($this->work->getProcessConfig()))->stop(); 81 | } 82 | 83 | /** 84 | * 命令:restart 85 | * @throws Exception 86 | */ 87 | public function commandRestart() 88 | { 89 | $this->checkCommand(); 90 | 91 | (new Manage($this->work->getProcessConfig())) 92 | ->setWorkInit($this->work->getWorkInit()) // 设置初始化 93 | ->setWork($this->work->getWork()) // 设置任务 94 | ->setBackground() // 后台执行 95 | ->restart(); // restart 96 | 97 | } 98 | 99 | /** 100 | * 命令: wakeup 101 | * @throws Exception 102 | */ 103 | public function commandWakeup() 104 | { 105 | $this->checkCommand(); 106 | 107 | (new Manage($this->work->getProcessConfig()))->wakeup(); 108 | 109 | } 110 | 111 | 112 | /** 113 | * 命令: status 114 | * @param bool $isReturn 是否返回数组(不返回数组则直接显示) 115 | * @return array|null 116 | * @throws Exception 117 | */ 118 | public function commandStatus($isReturn = false) 119 | { 120 | $this->checkCommand(); 121 | 122 | $status = (new Manage($this->work->getProcessConfig()))->status(); 123 | 124 | $queueConfig = ['QueueConfig' => []]; 125 | 126 | $status = $this->work->formatQueueStatus($status); 127 | $queueConfig['QueueConfig'] = $this->work->getQueueConfig(); 128 | $status = array_merge($queueConfig, $status); 129 | 130 | if ($isReturn) { 131 | return $status; 132 | } else { 133 | // 格式化显示 134 | echo "########################################### status ###########################################\n"; 135 | Manage::showStatus($status); 136 | echo "########################################### status ###########################################\n"; 137 | echo "queue process running ok !\n"; 138 | echo "\n"; 139 | } 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /src/Queue.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 34 | } 35 | 36 | public function __destruct() 37 | { 38 | static::$instances = null; 39 | } 40 | 41 | /** 42 | * 不允许被克隆 43 | * @throws Exception 44 | */ 45 | protected function __clone() 46 | { 47 | throw new Exception("This class cannot be cloned" , -101); 48 | } 49 | 50 | /** 51 | * @param string $connectName 链接类型(默认走配置) 52 | * @return Queue|null 53 | */ 54 | public static function getInstance($connectName = '') 55 | { 56 | if (empty($connectName)) { 57 | $connectName = ConnectionFactory::$currentConnect; 58 | } 59 | if (!isset(static::$instances[$connectName]) || !(static::$instances[$connectName] instanceof Queue)) { 60 | $connect = ConnectionFactory::getInstance($connectName); 61 | if ($connect) { 62 | static::$instances[$connectName] = new static($connect); 63 | } else { 64 | return null; 65 | } 66 | } 67 | return static::$instances[$connectName]; 68 | } 69 | 70 | /** 71 | * 设置处理程序 72 | * @param \Closure $handler 73 | */ 74 | public function setHandler(\Closure $handler) 75 | { 76 | $this->connection->setHandler($handler); 77 | } 78 | 79 | /** 80 | * 执行pop出来的任务(阻塞方法) 81 | * @param string $queueName 82 | */ 83 | public function popRun($queueName) 84 | { 85 | $this->connection->popRun($queueName); 86 | } 87 | 88 | /** 89 | * 入队列 90 | * @param Job $job 91 | * @param string $queueName 队列名 92 | * @return boolean 93 | */ 94 | public function push(Job $job, $queueName) 95 | { 96 | return $this->connection->push($job, $queueName); 97 | } 98 | 99 | /** 100 | * 延迟入队列 101 | * @param int $delay 延迟的秒数 102 | * @param Job $job 103 | * @param string $queueName 队列名 104 | * @return boolean 105 | */ 106 | public function later($delay, Job $job, $queueName) 107 | { 108 | if ($delay <= 0) { 109 | return $this->push($job, $queueName); 110 | } else { 111 | return $this->connection->later($delay, $job, $queueName); 112 | } 113 | } 114 | 115 | /** 116 | * 入队列 (对外) 117 | * @param JobHandler $handler 回调类 118 | * @param String $func 方法名 119 | * @param mixed $param 参数 120 | * @param String $queueName 队列名 121 | * @return boolean 122 | */ 123 | public function pushOn(JobHandler $handler, $func, $param, $queueName) 124 | { 125 | $job = new Job($handler, $func, $param); 126 | return $this->push($job, $queueName); 127 | } 128 | 129 | /** 130 | * 延迟入队列 (对外) 131 | * @param Int $delay 延迟时间/秒 132 | * @param JobHandler $handler 回调类 133 | * @param String $func 方法名 134 | * @param mixed $param 参数 135 | * @param String $queueName 队列名 136 | * @return boolean 137 | */ 138 | public function laterOn($delay, JobHandler $handler, $func, $param, $queueName) 139 | { 140 | $job = new Job($handler, $func, $param); 141 | return $this->later($delay,$job, $queueName); 142 | } 143 | 144 | 145 | /** 146 | * 关闭数据库连接 147 | */ 148 | public function close() 149 | { 150 | $this->connection->close(); 151 | } 152 | 153 | 154 | } -------------------------------------------------------------------------------- /src/Connection/RabbitMQ/OpenConnect.php: -------------------------------------------------------------------------------- 1 | host = $config['host']; 75 | } 76 | if (isset($config['port']) && !empty($config['port'])) { 77 | $this->port = $config['port']; 78 | } 79 | if (isset($config['username']) && !empty($config['username'])) { 80 | $this->userName = $config['username']; 81 | } 82 | if (isset($config['password']) && !empty($config['password'])) { 83 | $this->password = $config['password']; 84 | } 85 | if (isset($config['vhost']) && !empty($config['vhost'])) { 86 | $this->vhost = $config['vhost']; 87 | } 88 | if (isset($config['exChanges']) && !empty($config['exChanges'])) { 89 | $this->exChanges = $config['exChanges']; 90 | } 91 | } 92 | 93 | /** 94 | * 链接 95 | * @param bool $force 是否强制重连 96 | */ 97 | protected function open($force = false) 98 | { 99 | if ($force || empty($this->connect)) { 100 | // 初始化链接 101 | $this->connect = new AMQPStreamConnection($this->host, $this->port, $this->userName, 102 | $this->password, $this->vhost); 103 | // 创建通道 104 | $this->channel = $this->connect->channel(); 105 | } 106 | } 107 | 108 | /** 109 | * 关闭连接 110 | * @return boolean 111 | */ 112 | public function close() 113 | { 114 | try { 115 | if (!empty($this->channel)) { 116 | $this->channel->close(); 117 | } 118 | if (!empty($this->connect)) { 119 | $this->connect->close(); 120 | } 121 | } catch (\Exception $e) { 122 | Log::error('RabbitMQ close failed, reason: ' . $e->getMessage()); 123 | } 124 | return true; 125 | } 126 | 127 | /** 128 | * 初始化队列 129 | * @param string $queueName 130 | * @param bool $force 是否强制初始化 (强制初始化还会初始化链接) 131 | */ 132 | protected function initQueue($queueName, $force = false) 133 | { 134 | 135 | if (empty($queueName)) { 136 | return; 137 | } 138 | 139 | $this->open($force); 140 | 141 | // 关键字使用队列名称 142 | $this->routingKey = $this->exChanges . '.' . $queueName; 143 | 144 | // 声明初始化交换机 145 | $this->channel->exchange_declare($this->exChanges, 'direct', false, true, false); 146 | 147 | // 声明队列 148 | $this->channel->queue_declare($queueName, false, true, false, false); 149 | 150 | // 将队列与某个交换机进行绑定,并使用路由关键字 151 | $this->channel->queue_bind($queueName, $this->exChanges, $this->routingKey); 152 | 153 | } 154 | 155 | } -------------------------------------------------------------------------------- /src/Worker.php: -------------------------------------------------------------------------------- 1 | configNameList = ['queueName', 'attempt', 'memory', 'maxRunTime']; 66 | $this->setConfig($config); 67 | 68 | $this->queue = Queue::getInstance(); 69 | } 70 | 71 | /** 72 | * 进程观察者 73 | * @param ProcessObserver $observer 74 | */ 75 | public function bindProcessObserver(ProcessObserver $observer) 76 | { 77 | $this->observer = $observer; 78 | } 79 | 80 | /** 81 | * 启用一个队列的监听任务 82 | */ 83 | public function listen() 84 | { 85 | // 设置执行函数 86 | $this->queue->setHandler(function(Job $job, $queueName) { 87 | // 执行任务 88 | $job->execute(); 89 | 90 | // 判断任务是否执行成功 91 | if ($job->isExec()) { 92 | //任务成功,触发回调 93 | $job->success(); 94 | } else { 95 | // 是否需要重试该任务 96 | if ($job->isRetry($this->attempt)) { 97 | // 需要重试,则重新将任务放入队尾 98 | $job->reTry($this->queue, $queueName); 99 | } else { 100 | // 不需要重试,则任务失败,触发回调 101 | $job->failed(); 102 | } 103 | } 104 | }); 105 | 106 | $endTime = time() + $this->maxRunTime; 107 | while (true) { 108 | 109 | // 消费一次队列任务 110 | $this->queue->popRun($this->queueName); 111 | 112 | // 检查最大执行时间 113 | $this->checkMaxRunTime($endTime); 114 | 115 | // 检查内存超出 116 | $this->checkMemoryExceeded(); 117 | 118 | // 进程观察者检查是否需要停止 119 | $this->observeCheckStop(); 120 | 121 | // 退出监听 122 | if ($this->isStop()) { 123 | $this->reportStopInfo(); 124 | break; 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * 是否需要退出 131 | * @return bool 132 | */ 133 | public function isStop() 134 | { 135 | return $this->isStop; 136 | } 137 | 138 | /** 139 | * 关闭相关资源 140 | */ 141 | protected function close() 142 | { 143 | $this->queue->close(); 144 | } 145 | 146 | /** 147 | * 设置退出监听 148 | */ 149 | public function setStop() 150 | { 151 | $this->close(); 152 | $this->isStop = true; 153 | } 154 | 155 | /** 156 | * 判断内存使用是否超出 157 | */ 158 | protected function checkMemoryExceeded() 159 | { 160 | if ((memory_get_usage() / 1024 / 1024) >= $this->memory) { 161 | Log::error('Memory out of range'); 162 | $this->setStop(); 163 | } 164 | } 165 | 166 | /** 167 | * 检查最大运行时间 168 | * @param $endTime 169 | */ 170 | protected function checkMaxRunTime($endTime) 171 | { 172 | if ($this->maxRunTime > 0 && time() > $endTime) { 173 | $this->setStop(); 174 | } 175 | } 176 | 177 | /**************************** 多任务模式 *********************************/ 178 | /** 179 | * 进程观察者检查是否需要停止 180 | */ 181 | protected function observeCheckStop() 182 | { 183 | if ($this->observer instanceof ProcessObserver) { 184 | // 检测 185 | $this->observer->check(); 186 | // 判断是否停止 187 | if ($this->observer->isStop) { 188 | $this->setStop(); 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * 让进程观察者上报进程停止信息 195 | */ 196 | protected function reportStopInfo() 197 | { 198 | if ($this->observer instanceof ProcessObserver) { 199 | $this->observer->reportStopInfo(); 200 | } 201 | } 202 | 203 | } -------------------------------------------------------------------------------- /src/Daemon/Work/Work.php: -------------------------------------------------------------------------------- 1 | 'default', //队列名称(同时默认为进程的基础名称)(字符串或者数组) 29 | 'attempt' => 10, //队列任务失败尝试次数,0为不限制 30 | 'memory' => 128, //允许使用的最大内存 单位:M 31 | 'sleep' => 3, //每次检测的时间间隔 32 | 'delay' => 0, //失败后延迟的秒数重新入队列 33 | ]; 34 | 35 | 36 | /** 37 | * 进程配置信息 38 | * @var array 39 | */ 40 | protected $processConfig = [ 41 | 'baseTitle' => '', // 进程名称 42 | 43 | // master 进程配置 44 | 'checkWorkerInterval' => 600, // 10分钟检测一次进程数量 45 | 'maxWorkerNum' => 1, //1个进程 46 | 47 | // worker 进程配置 48 | 'executeTimes' => 0, // 任务的最大执行次数(到次数后停止,master进程重新启动)(0为不限制) 49 | 'limitSeconds' => 86400, // 工作进程最大执行时长(秒)(到时间后停止,master进程重新启动)(0为不限制) (默认1天重启一次) 50 | 'executeUSleep' => 0, // 每次工作任务中间睡眠时长(微秒),0微秒执行一次 51 | ]; 52 | 53 | /** 54 | * Work constructor. 55 | * @param array $queueConfig 队列配置信息 56 | */ 57 | public function __construct(array $queueConfig) 58 | { 59 | $this->setQueueConfig($queueConfig); 60 | } 61 | 62 | /** 63 | * 设置配置 64 | * @param $variable 65 | * @param array $config 66 | */ 67 | protected function setConfig($variable, array $config = []) 68 | { 69 | foreach ($config as $k => $v) { 70 | $this->$variable[$k] = $v; 71 | } 72 | } 73 | 74 | /** 75 | * 设置队列配置 76 | * @param array $config 77 | * @return $this 78 | */ 79 | protected function setQueueConfig(array $config = []) 80 | { 81 | $this->setConfig('queueConfig', $config); 82 | if (is_array($this->queueConfig['queueName'])) { 83 | $this->queueName = implode('|', $this->queueConfig['queueName']); 84 | } else { 85 | $this->queueName = $this->queueConfig['queueName']; 86 | } 87 | return $this; 88 | } 89 | 90 | /** 91 | * 设置进程配置 92 | * @param array $config 93 | * @return $this 94 | */ 95 | public function setProcessConfig(array $config = []) 96 | { 97 | $this->setConfig('processConfig', $config); 98 | ##################### 不允许修改的值 ##################### 99 | if (empty($this->processConfig['baseTitle'])) { 100 | // 如果为空,队列基础名称修改成队列名 101 | $this->processConfig['baseTitle'] = $this->queueName; 102 | } 103 | // 进程前缀不允许局部配置,只能通过全局加载配置 104 | $this->processConfig['titlePrefix'] = QueueConfig::$Process['TitlePrefix']; 105 | ##################### 不允许修改的值 ##################### 106 | return $this; 107 | } 108 | 109 | /** 110 | * 获取进程配置 111 | * @return array 112 | */ 113 | public function getProcessConfig() 114 | { 115 | return $this->processConfig; 116 | } 117 | 118 | /** 119 | * 获取队列配置 120 | * @return array 121 | */ 122 | public function getQueueConfig() 123 | { 124 | return $this->queueConfig; 125 | } 126 | 127 | /** 128 | * 工作进程的初始化 129 | * @return \Closure 130 | */ 131 | public function getWorkInit() 132 | { 133 | $config = $this->getQueueConfig(); 134 | // 初始化队列消费者 135 | return function (ProcessWorker $process) use ($config) { 136 | return (new Worker(Queue::getInstance()))->setConfig($config); 137 | }; 138 | } 139 | 140 | /** 141 | * 工作进程的运行 142 | * @return \Closure 143 | */ 144 | public function getWork() 145 | { 146 | // 执行的工作内容 147 | return function(ProcessWorker $process, Worker $worker) { 148 | $worker->runOnce(); 149 | // 如果任务需要退出 150 | if ($worker->isStop()) { 151 | // 则调用进程的退出操作 152 | $process->setStop(); 153 | } 154 | }; 155 | } 156 | 157 | 158 | /** 159 | * 将title换成queueName 160 | * @param array $status 161 | * @return array 162 | */ 163 | public function formatQueueStatus(array $status) 164 | { 165 | $processConfig = $this->processConfig; 166 | $titlePrefix = $processConfig['titlePrefix']; 167 | $baseTitle = $processConfig['baseTitle']; 168 | $title = [ 169 | $titlePrefix.':Master:'.$baseTitle, 170 | $titlePrefix.':Worker:'.$baseTitle, 171 | ]; 172 | foreach ($status as $type => $typeArr) { 173 | foreach ($typeArr as $pid => $statusArr) { 174 | foreach ($statusArr as $field => $value) { 175 | if ($field == 'title' && in_array($value, $title)) { 176 | $status[$type][$pid][$field] = $this->queueName; 177 | } 178 | } 179 | } 180 | } 181 | return $status; 182 | } 183 | 184 | } -------------------------------------------------------------------------------- /src/Job.php: -------------------------------------------------------------------------------- 1 | resetId(); 61 | 62 | $this->handler = $handler; 63 | $this->func = $func; 64 | $this->param = $param; 65 | 66 | $this->init(); 67 | } 68 | 69 | /** 70 | * 生成id 71 | */ 72 | public function resetId() 73 | { 74 | $this->id = md5(uniqid(rand(0,9999).microtime(true),true)); 75 | } 76 | 77 | /** 78 | * 初始化默认任务参数 79 | */ 80 | public function init() 81 | { 82 | $this->isExec = false; 83 | $this->attempts = 0; 84 | } 85 | 86 | /** 87 | * 该任务已经执行的次数 88 | * @return int 89 | */ 90 | public function getAttempts() 91 | { 92 | return $this->attempts; 93 | } 94 | 95 | /** 96 | * 任务失败回调 97 | * @return void 98 | */ 99 | public function failed() 100 | { 101 | $this->handler->failed($this, $this->func, $this->param); 102 | } 103 | 104 | /** 105 | * 任务成功回调 106 | * @return void 107 | */ 108 | public function success() 109 | { 110 | $this->handler->success($this, $this->func, $this->param); 111 | } 112 | 113 | /** 114 | * 执行任务 115 | * @return void 116 | */ 117 | public function execute() 118 | { 119 | $this->attempts++; 120 | 121 | $this->resetStatus(); 122 | 123 | //执行handler回调 124 | $this->handler->handler($this, $this->func, $this->param); 125 | 126 | if ($this->lastStatus) { 127 | $this->isExec = true; 128 | } 129 | 130 | } 131 | 132 | /** 133 | * 重制执行状态 134 | */ 135 | protected function resetStatus() 136 | { 137 | $this->lastStatus = true; 138 | $this->forceFailed = false; 139 | } 140 | 141 | /** 142 | * 设置本次执行失败(会重试) 143 | * @param string $message 错误信息 144 | */ 145 | public function setOnceFailure($message = "") 146 | { 147 | $this->lastStatus = false; 148 | $this->forceFailed = false; 149 | $this->errorArr[] = $message; 150 | } 151 | 152 | /** 153 | * 设置本次任务为强制失败(不会重试) 154 | * @param string $message 错误信息 155 | */ 156 | public function setForceFailure($message = "") 157 | { 158 | $this->lastStatus = false; 159 | $this->forceFailed = true; 160 | $this->errorArr[] = $message; 161 | } 162 | 163 | /** 164 | * 任务是否执行成功 165 | * @return boolean 166 | */ 167 | public function isExec() 168 | { 169 | return $this->isExec; 170 | } 171 | 172 | 173 | /** 174 | * 获取任务失败的异常信息数组 175 | * @return string[] 176 | */ 177 | public function getErrors() 178 | { 179 | return $this->errorArr; 180 | } 181 | 182 | /** 183 | * 是否需要重试该任务 184 | * @param int $maxAttempt 185 | * @return bool 186 | */ 187 | public function isRetry(int $maxAttempt) 188 | { 189 | if ($this->isExec()) { 190 | // 执行成功不需要重试 191 | return false; 192 | } else { 193 | if ( 194 | // 判断是否强制失败,如果强制失败,则任务不需要重试 195 | ($this->forceFailed) || 196 | // 判断当前是否超出指定执行次数,如果超过最大限制,则任务不需要重试 197 | ($maxAttempt > 0 && $this->getAttempts() >= $maxAttempt) 198 | ) { 199 | return false; 200 | } else { 201 | return true; 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * 重试该任务 208 | * @param Queue $queue 队列 209 | * @param string $queueName 队列名 210 | * @return boolean 211 | */ 212 | public function reTry(Queue $queue, $queueName) 213 | { 214 | return $queue->push($this, $queueName); 215 | } 216 | 217 | 218 | /** 219 | * 序列化对象 220 | * @param Job $job 221 | * @return string 222 | */ 223 | public static function Encode(Job $job) 224 | { 225 | return base64_encode(serialize($job)); 226 | } 227 | 228 | 229 | /** 230 | * 反序列化对象 231 | * @param string $jobStr 232 | * @return Job 233 | */ 234 | public static function Decode($jobStr) 235 | { 236 | return unserialize(base64_decode($jobStr)); 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # queue_task 2 | PHP队列任务管理器,支持多种存储方式 3 | 4 |
5 | 6 | ## 业务场景 7 | 在实际业务场景中,我们可能需要执行一些耗时操作,例如:发送邮件等。但这样的操作并不适合放在主流程中,则可以使用队列来异步处理任务。 8 | 9 |
10 | 11 | ## 简述 12 | 基于php-cli模式多进程的生产者-消费者模式,自定义存储介质,也可用系统内置存储:Redis/Mns/RabbitMq。 13 | 14 |
15 | 16 | ## 依赖 17 | ##### 必须 18 | - php: `>=7.0` 19 | - aliyun/aliyun-mns-php-sdk: `^1.1` 20 | - php-amqplib/php-amqplib: `^2.10` 21 | - ext-redis: `*` 22 | - ext-json: `*` 23 | ##### 非必须(多进程模式必须) 24 | - ext-pcntl: `*` 25 | - ext-posix: `*` 26 | - swoole: `>=1.10.3` 27 | 28 |
29 | 30 | ## 安装 31 | > linux:`composer require mojiehai/queue_task` 32 | 33 | > windows:`composer require mojiehai/queue_task --ignore-platform-reqs` (忽略环境检查)(windows不支持多进程) 34 | 35 |
36 | 37 | ## 使用 38 | ### 全局配置 39 | ```php 40 | ############################## 全局配置 ############################## 41 | $config = [ 42 | 'log' => [ 43 | 'logRoot' => __DIR__ . '/../runtime/log', // 日志文件根目录 44 | 'fileName' => '\q\u\e\u\e_Y-m-d.\l\o\g', // 日志文件分割规则(date()函数第一个参数) 45 | ], 46 | 47 | 'connectList' => [ 48 | 'Redis' => [ 49 | 'class' => '\\QueueTask\\Connection\\Redis\\Redis', 50 | 'config' => [ // 应用初始化的参数 51 | 'popTimeout' => 3, // pop阻塞的超时时长 s 52 | 'host' => '127.0.0.1', // 数据库地址 53 | 'port' => 6379, // 数据库端口 54 | 'db' => 0, // 库 55 | 'password' => null, // 密码 56 | 'connTimeout' => 1, // 链接超时 57 | ], 58 | ], 59 | 'Mns' => [ 60 | 'class' => '\\QueueTask\\Connection\\Mns\\Mns', 61 | 'config' => [ // 应用初始化的参数 62 | 'popTimeout' => 3, // pop阻塞的超时时长 s 63 | 'accessKeyID' => '', // Mns key id 64 | 'accessKeySecret' => '', // Mns key secret 65 | 'endpoint' => '', // Mns end point 66 | ], 67 | ], 68 | 'RabbitMQ' => [ 69 | 'class' => '\\QueueTask\\Connection\\RabbitMQ\\RabbitMQ', 70 | 'config' => [ // 应用初始化的参数 71 | // exchanges需要设置为direct,持久化存储,不自动确认消息 72 | 'popTimeout' => 3, // pop阻塞的超时时长 s 73 | 'host' => '127.0.0.1', 74 | 'port' => 5672, 75 | 'username' => '', 76 | 'password' => '', 77 | 'vhost' => '/', // 虚拟主机 78 | 'exChanges' => '', // 直连交换机名称 79 | ], 80 | ], 81 | ], 82 | 83 | 'currentConnect' => 'RabbitMQ', // 当前使用的应用类型 84 | ]; 85 | Load::Queue($config); 86 | ############################## 全局配置 ############################## 87 | ``` 88 | 89 | 90 | ### 压入任务 91 | 1. 首先定义处理类,例如:TestHandler继承JobHandler,并定义任务test方法 92 | 2. test方法接收两个参数,第一个为Job对象,第二个为自定义参数 93 | ```php 94 | class TestHandler extends JobHandler 95 | { 96 | 97 | /** 98 | * 失败回调方法 99 | * @param Job $job 任务 100 | * @param string $func 执行的方法 101 | * @param array $data 参数 102 | * @return mixed 103 | */ 104 | public function failed(Job $job, $func, $data) 105 | { 106 | \QueueTask\Log\WorkLog::info('failed run handler -- func: '.$func.' -- params: '.json_encode($data)); 107 | } 108 | 109 | /** 110 | * 任务成功回调 111 | * @param Job $job 任务 112 | * @param string $func 执行的方法 113 | * @param array $data 参数 114 | * @return mixed 115 | */ 116 | public function success(Job $job, $func, $data) 117 | { 118 | \QueueTask\Log\WorkLog::info('success run handler -- func: '.$func.' -- params: '.json_encode($data)); 119 | } 120 | 121 | 122 | public function test(Job $job,$data) 123 | { 124 | \QueueTask\Log\WorkLog::info('run handler -- func: test -- params: '.json_encode($data). '; result : '.var_export($res, true)); 125 | } 126 | 127 | } 128 | ``` 129 | 3. 压入 130 | ```php 131 | // 获取队列对象 132 | $queue = Queue::getInstance(); 133 | 134 | // 直接压入队列,参数:handler对象,方法,自定义参数,队列名称 135 | $r = $queue->pushOn(new TestHandler(),'test',['test'=>'test'],'testQueue'); 136 | 137 | // 延迟5s压入队列(部分队列不支持延迟操作,例如rabbitmq),参数:延迟秒数,handler对象,方法,自定义参数,队列名称 138 | $r = $queue->laterOn(5,new TestHandler(),'test',['test'=>'test'],'testQueue'); 139 | 140 | ``` 141 | ### 监听任务 142 | 1. 以普通方式启动监听任务(单进程) 143 | ```php 144 | $config = [ 145 | 'queueName' => 'testQueue', //队列名称 146 | 'attempt' => 3, //队列任务失败尝试次数,0为不限制 147 | 'memory' => 128, //允许使用的最大内存 单位:M 148 | 'maxRunTime' => 100, // 最大运行时间 100s 0为不限制(单进程模式建议设置为0,否则需要手动定时拉取) 149 | ]; 150 | 151 | try{ 152 | (new Worker($config))->listen(); 153 | }catch (Exception $e){ 154 | echo $e->getCode()." -- ".$e->getFile() . " -- ". $e->getLine() . " : ".$e->getMessage(); 155 | } 156 | ``` 157 | 158 |
159 | 160 | 2. 以守护进程方式启动监听任务(多进程) 161 | ```php 162 | $config1 = [ 163 | 'queueName' => 'testQueue', //队列名称 164 | 'attempt' => 3, //队列任务失败尝试次数,0为不限制 165 | 'memory' => 128, //允许使用的最大内存 单位:M 166 | 'maxRunTime' => 100, // 最大运行时间 100s (多进程模式建议设置为进程重启的间隔时间,例如,需要1个小时重启一次,则设置为3600) 167 | ]; 168 | 169 | $config2 = [ 170 | 'queueName' => 'testQueue1', //队列名称 171 | 'attempt' => 3, //队列任务失败尝试次数,0为不限制 172 | 'memory' => 128, //允许使用的最大内存 单位:M 173 | 'maxRunTime' => 100, // 最大运行时间 100s 174 | ]; 175 | 176 | 177 | try { 178 | (new MultiWorker('tag1')) // tag1为唯一标识,不同任务组使用不同标识 179 | ->addWorker($config1, 1) // 第二个参数为进程数 180 | ->addWorker($config2, 2) 181 | ->start(); 182 | 183 | } catch (Exception $e) { 184 | } 185 | ``` 186 | 187 |
188 | 189 | -------------------------------------------------------------------------------- /src/Process/ManageProcess.php: -------------------------------------------------------------------------------- 1 | pidManage = new Pid($uniqueTag); 52 | } 53 | 54 | /** 55 | * 设置进程任务 56 | * @param array $workerConfig 57 | * @param int $processNum 58 | */ 59 | public function addWorker(array $workerConfig, int $processNum) 60 | { 61 | for ($i = 0; $i < $processNum; $i ++) { 62 | $this->workerProcess[] = new WorkerProcess($workerConfig); 63 | } 64 | } 65 | 66 | /** 67 | * start 68 | */ 69 | public function start() 70 | { 71 | // 守护进程 72 | Process::daemon(); 73 | 74 | // 重设pid 75 | if (!$this->resetPid()) { 76 | return; 77 | } 78 | 79 | // 设置进程名称 80 | @swoole_set_process_name('queue_task_'.$this->pidManage->uniqueTag); 81 | 82 | // 设置信号 83 | $this->registerSignal(); 84 | 85 | // 检查worker是否存在(启动子进程) 86 | $this->isCheck = true; 87 | 88 | while (true) { 89 | 90 | // 阻塞等待子进程退出,并唤醒 91 | $this->waitProcess(); 92 | 93 | // 读取信号/信息 94 | $this->readSignal(); 95 | 96 | // 是否检查worker,并拉起已经死掉的进程 97 | if ($this->isCheck) { 98 | $this->isCheck = false; 99 | $this->checkCreateProcess(); 100 | // 设置下次闹钟 101 | pcntl_alarm($this->alarmInterval); 102 | } 103 | 104 | if ($this->isStop) { 105 | Log::info('manage process stopping ...'); 106 | $this->waitStop(); 107 | Log::info('manage process exit,pid: ' . $this->pidManage->get()); 108 | break; 109 | } 110 | 111 | } 112 | } 113 | 114 | /** 115 | * 重设pid 116 | * @return bool 117 | */ 118 | protected function resetPid() 119 | { 120 | // 检查pid是否存在 121 | $pid = $this->pidManage->get(); 122 | 123 | if ($this->pidManage->isAlive()) { 124 | // master还活着 125 | Log::error('the process already exists, pid: '. $pid); 126 | return false; 127 | } else { 128 | $this->pidManage->set(posix_getpid()); 129 | return true; 130 | } 131 | 132 | } 133 | 134 | /** 135 | * 注册信号 136 | */ 137 | protected function registerSignal() 138 | { 139 | // 添加15信号 140 | pcntl_signal(SIGTERM, [$this, 'signalCall'], false); 141 | // 添加闹钟信号,检查拉取停止了的worker进程 142 | pcntl_signal(SIGALRM, [$this, 'signalCall'], false); 143 | } 144 | 145 | /** 146 | * 检查创建子进程 147 | */ 148 | protected function checkCreateProcess() 149 | { 150 | $pid = $this->pidManage->get(); 151 | foreach ($this->workerProcess as $workerProcess) { 152 | if (!($workerProcess->isAlive())) { 153 | $workerProcess->start($pid, $this->pidManage->uniqueTag); 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * 读取信号 160 | */ 161 | protected function readSignal() 162 | { 163 | pcntl_signal_dispatch(); 164 | } 165 | 166 | /** 167 | * 阻塞等待子进程退出并唤醒或者监听其他信号 168 | */ 169 | protected function waitProcess() 170 | { 171 | $pid = pcntl_wait($status, WUNTRACED); 172 | if ($pid > 0) { 173 | // 子进程退出信号,调用子进程管理器清理退出的进程 174 | foreach ($this->workerProcess as $workerProcess) { 175 | if ($pid == $workerProcess->pid) { 176 | // 读取信息 177 | $workerProcess->read(); 178 | // 检查是否正常退出 179 | if ($workerProcess->isStop) { 180 | Log::info('process exit, pid: ' . $pid); 181 | // 正常退出,重启 182 | $workerProcess->start($this->pidManage->get(), $this->pidManage->uniqueTag); 183 | } else { 184 | // 非正常退出,警报 185 | Log::error('abnormal exit of process, pid: '. $pid, $workerProcess->workerConfig); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * 发送停止信号给子进程,并且等待停止 194 | */ 195 | protected function waitStop() 196 | { 197 | // 发送停止信号 198 | foreach ($this->workerProcess as $workerProcess) { 199 | if ($workerProcess->isAlive()) { 200 | Process::kill($workerProcess->pid, SIGTERM); 201 | } 202 | } 203 | 204 | // 阻塞等待子进程停止 205 | for ($i = 0; $i < count($this->workerProcess); $i ++) { 206 | $result = Process::wait(true); 207 | if ($result === false) { 208 | break; 209 | } 210 | } 211 | 212 | // 检查子进程是否存在 213 | foreach ($this->workerProcess as $workerProcess) { 214 | if ($workerProcess->isAlive()) { 215 | Log::error('kill process failed, pid: '. $workerProcess->pid); 216 | } 217 | } 218 | } 219 | 220 | 221 | /** 222 | * 停止 223 | */ 224 | public function stop() 225 | { 226 | if ($this->pidManage->isAlive()) { 227 | Process::kill($this->pidManage->get(), SIGTERM); 228 | } 229 | } 230 | 231 | 232 | /** 233 | * 信号回调 234 | * @param $signal 235 | */ 236 | public function signalCall($signal) 237 | { 238 | switch ($signal) { 239 | case SIGALRM: 240 | $this->isCheck = true; 241 | break; 242 | case SIGTERM: 243 | $this->isStop = true; 244 | break; 245 | } 246 | } 247 | 248 | } -------------------------------------------------------------------------------- /src/Daemon/Command/MultipleWork/MultipleWork.php: -------------------------------------------------------------------------------- 1 | works[$work->queueName] = $work; 49 | return $this; 50 | } 51 | 52 | ############################# manage ############################### 53 | /** 54 | * 获取多进程多任务管理器 55 | * @return ManageMultiple 56 | */ 57 | protected function getManageMultiple() 58 | { 59 | if (!($this->manageMultiple instanceof ManageMultiple)) { 60 | $multipleManage = new ManageMultiple(); 61 | foreach ($this->works as $work) { 62 | // 添加多个manage 63 | $multipleManage->addManage( 64 | (new Manage($work->getProcessConfig())) 65 | ->setWorkInit($work->getWorkInit()) 66 | ->setWork($work->getWork()) 67 | ); 68 | } 69 | $this->manageMultiple = $multipleManage; 70 | } 71 | return $this->manageMultiple; 72 | } 73 | 74 | 75 | /** 76 | * 获取多进程单任务管理器 77 | * @param string $queueName 队列名称 78 | * @return Manage 79 | */ 80 | protected function getManage(string $queueName) 81 | { 82 | $work = $this->works[$queueName]; 83 | return (new Manage($work->getProcessConfig())) 84 | ->setWorkInit($work->getWorkInit()) 85 | ->setWork($work->getWork()); 86 | } 87 | 88 | 89 | /** 90 | * 检测命令是否可用 91 | * @return bool 92 | * @throws Exception 93 | */ 94 | public function checkCommand() 95 | { 96 | // 如果工作为空,则退出 97 | if (empty($this->works)) { 98 | throw new Exception('there is no queue configured to run'); 99 | } 100 | 101 | // 如果队列名称不为空,且工作中没有该队列,则退出 102 | if (!empty($this->queueName) && !isset($this->works[$this->queueName])) { 103 | throw new Exception('queue does not exist'); 104 | } 105 | return true; 106 | } 107 | 108 | ############################# command ############################### 109 | /** 110 | * 命令:start 111 | * @throws Exception 112 | */ 113 | public function commandStart() 114 | { 115 | $this->checkCommand(); 116 | // 设置了队列名称 117 | if (!empty($this->queueName)) { 118 | // 执行单任务 119 | $manage = $this->getManage($this->queueName); 120 | 121 | if ($this->background) { 122 | // 后台运行 123 | $manage->setBackground(); 124 | } 125 | $manage->start(); 126 | 127 | } else { 128 | // 执行多任务 129 | $multipleManage = $this->getManageMultiple(); 130 | 131 | $multipleManage->start(); 132 | } 133 | } 134 | 135 | /** 136 | * 命令:stop 137 | * @throws Exception 138 | */ 139 | public function commandStop() 140 | { 141 | $this->checkCommand(); 142 | // 设置了队列名称 143 | if (!empty($this->queueName)) { 144 | // 执行单任务 145 | $manage = $this->getManage($this->queueName); 146 | 147 | $manage->stop(); 148 | 149 | } else { 150 | // 执行多任务 151 | $multipleManage = $this->getManageMultiple(); 152 | 153 | $multipleManage->stop(); 154 | } 155 | } 156 | 157 | /** 158 | * 命令:restart 159 | * @throws Exception 160 | */ 161 | public function commandRestart() 162 | { 163 | $this->checkCommand(); 164 | // 设置了队列名称 165 | if (!empty($this->queueName)) { 166 | // 执行单任务 167 | $manage = $this->getManage($this->queueName); 168 | 169 | $manage->setBackground()->restart(); 170 | 171 | } else { 172 | // 执行多任务 173 | $multipleManage = $this->getManageMultiple(); 174 | 175 | $multipleManage->restart(); 176 | } 177 | } 178 | 179 | /** 180 | * 命令: wakeup 181 | * @throws Exception 182 | */ 183 | public function commandWakeup() 184 | { 185 | $this->checkCommand(); 186 | // 设置了队列名称 187 | if (!empty($this->queueName)) { 188 | // 执行单任务 189 | $manage = $this->getManage($this->queueName); 190 | 191 | $manage->wakeup(); 192 | 193 | } else { 194 | // 执行多任务 195 | $multipleManage = $this->getManageMultiple(); 196 | 197 | $multipleManage->wakeup(); 198 | } 199 | } 200 | 201 | 202 | /** 203 | * 命令: status 204 | * @param bool $isReturn 是否返回数组(不返回数组则直接显示) 205 | * @return array|null 206 | * @throws Exception 207 | */ 208 | public function commandStatus($isReturn = false) 209 | { 210 | $this->checkCommand(); 211 | // 设置了队列名称 212 | if (!empty($this->queueName)) { 213 | $workList = [$this->works[$this->queueName]]; 214 | // 执行单任务 215 | $manage = $this->getManage($this->queueName); 216 | 217 | $status = $manage->status(); 218 | 219 | } else { 220 | $workList = $this->works; 221 | // 执行多任务 222 | $multipleManage = $this->getManageMultiple(); 223 | 224 | $status = $multipleManage->status(); 225 | } 226 | 227 | $queueConfig = ['QueueConfig' => []]; 228 | // 转换queue_name 229 | foreach ($workList as $work) { 230 | $status = $work->formatQueueStatus($status); 231 | $queueConfig['QueueConfig'][] = $work->getQueueConfig(); 232 | } 233 | 234 | $status = array_merge($queueConfig, $status); 235 | if ($isReturn) { 236 | return $status; 237 | } else { 238 | // 格式化显示 239 | echo "########################################### status ###########################################\n"; 240 | Manage::showStatus($status); 241 | echo "########################################### status ###########################################\n"; 242 | echo "queue process running ok !\n"; 243 | echo "\n"; 244 | } 245 | } 246 | 247 | } -------------------------------------------------------------------------------- /src/Connection/Redis/Redis.php: -------------------------------------------------------------------------------- 1 | host = (isset($config['host']) && !empty($config['host'])) ? $config['host'] : $this->host; 69 | $this->port = (isset($config['port']) && !empty($config['port'])) ? $config['port'] : $this->port; 70 | $this->database = (isset($config['db']) && !empty($config['db'])) ? $config['db'] : $this->database; 71 | $this->password = (isset($config['password']) && !empty($config['password'])) ? $config['password'] : $this->password; 72 | $this->connTimeout = (isset($config['connTimeout']) && !empty($config['connTimeout'])) ? $config['connTimeout'] : $this->connTimeout; 73 | } 74 | 75 | /** 76 | * get connect 77 | * @return \Redis 78 | */ 79 | private function getConnect() 80 | { 81 | if (empty($this->connect)) { 82 | $this->connect = new \Redis(); 83 | $this->open(); 84 | } else { 85 | if (@$this->connect->ping() !== '+PONG') { 86 | $this->open(); 87 | } 88 | } 89 | return $this->connect; 90 | } 91 | 92 | /** 93 | * open redis connect 94 | */ 95 | private function open() 96 | { 97 | $this->connect->connect($this->host, $this->port, $this->connTimeout); 98 | if (!empty($this->password)) { 99 | $this->connect->auth($this->password); 100 | } 101 | $this->connect->select($this->database); 102 | } 103 | 104 | /** 105 | * 关闭连接 106 | * @return boolean 107 | */ 108 | public function close() 109 | { 110 | if (!empty($this->connect)) { 111 | $this->connect->close(); 112 | } 113 | return true; 114 | } 115 | 116 | /** 117 | * 弹出队头任务(blocking) 118 | * @param string $queueName 队列名称 119 | * @param array & $extends 额外需要传递给ack方法的参数 120 | * @return Job|null 121 | */ 122 | protected function pop($queueName, & $extends = []) 123 | { 124 | //从延迟集合中合并到主执行队列 125 | $this->migrateAllExpiredJobs($queueName); 126 | 127 | $jobStr = $this->getConnect()->blPop($queueName, $this->popTimeOut); 128 | if (empty($jobStr)) { 129 | return null; 130 | } else { 131 | if (isset($jobStr[1]) && !empty($jobStr[1])) { 132 | return Job::Decode($jobStr[1]); 133 | } else { 134 | return null; 135 | } 136 | } 137 | } 138 | 139 | /** 140 | * 确认任务 141 | * @param string $queueName 142 | * @param Job $job 143 | * @param array $extends 144 | */ 145 | public function ack($queueName, Job $job = null, $extends = []) 146 | { 147 | // redis不需要确认任务 148 | } 149 | 150 | 151 | /** 152 | * 压入队列(直接压入主执行队列) 153 | * 154 | * @param Job $job 155 | * @param string $queueName 队列名称 156 | * 157 | * @return boolean 158 | */ 159 | public function push(Job $job, $queueName) 160 | { 161 | //命令:rpush 队列名 任务 162 | $length = $this->getConnect()->rPush($queueName, Job::Encode($job)); 163 | if ($length) { 164 | return true; 165 | } else { 166 | return false; 167 | } 168 | } 169 | 170 | /** 171 | * 添加一条延迟任务 172 | * 放入等待执行任务的有序集合中 173 | * 174 | * @param int $delay 延迟的秒数 175 | * @param Job $job 任务 176 | * @param string $queueName 队列名称 177 | * 178 | * @return boolean 179 | */ 180 | public function later($delay, Job $job, $queueName) 181 | { 182 | //命令:zadd 主队列名:delayed 当前时间戳+延迟秒数 任务 183 | $result = $this->getConnect()->zAdd($queueName . ':delayed', time() + $delay, Job::Encode($job)); 184 | if ($result) { 185 | return true; 186 | } else { 187 | return false; 188 | } 189 | } 190 | 191 | 192 | /** 193 | * 合并等待执行的任务 194 | * @param string $queueName 195 | * @return void 196 | */ 197 | protected function migrateAllExpiredJobs($queueName) 198 | { 199 | $this->migrateExpiredJobs($queueName . ':delayed', $queueName); 200 | } 201 | 202 | 203 | /** 204 | * 当延时任务到大执行时间时,将延时任务从延时任务集合中移动到主执行队列中 205 | * @param string $from 集合名称 206 | * @param string $to 队列名称 207 | * @return void 208 | */ 209 | protected function migrateExpiredJobs($from, $to) 210 | { 211 | $time = time(); 212 | $jobs = $this->getExpiredJobs($from, $time); 213 | if (count($jobs) > 0) { 214 | $connect = $this->getConnect(); 215 | //开始redis事物 216 | $connect->watch($from); 217 | $connect->multi(); 218 | 219 | $this->removeExpiredJobs($from, $time); 220 | $this->pushExpiredJobsOntoNewQueue($to, $jobs); 221 | 222 | $connect->exec(); 223 | } 224 | } 225 | 226 | 227 | /** 228 | * 从指定集合中获取所有超时的任务 229 | * @param String $name 集合名称 230 | * @param int $time 超时时间(集合中小于该时间为超时) 231 | * @return mixed 232 | */ 233 | public function getExpiredJobs($name , $time) 234 | { 235 | return $this->getConnect()->zRangeByScore($name, '-inf', $time); 236 | } 237 | 238 | 239 | /** 240 | * 从指定集合删除过期任务 241 | * @param string $from 242 | * @param int $time 243 | * @return void 244 | */ 245 | protected function removeExpiredJobs($from, $time) 246 | { 247 | $this->getConnect()->zRemRangeByScore($from, '-inf', $time); 248 | } 249 | 250 | 251 | /** 252 | * 将多个任务从添加到队列 253 | * 254 | * 场景:将有序集合中的延迟任务入主队列 255 | * @param string $to 256 | * @param array $jobs 257 | * @return void 258 | */ 259 | protected function pushExpiredJobsOntoNewQueue($to, $jobs) 260 | { 261 | //等价于 $connect->rPush($to,$jobs[0],$jobs[1]... ); 262 | $connect = $this->getConnect(); 263 | call_user_func_array([$connect, 'rPush'], array_merge([$to], $jobs)); 264 | } 265 | 266 | } -------------------------------------------------------------------------------- /src/Connection/Mns/Mns.php: -------------------------------------------------------------------------------- 1 | accessKeyID = $config['accessKeyID']; 73 | } 74 | if (isset($config['accessKeySecret']) && !empty($config['accessKeySecret'])) { 75 | $this->accessKeySecret = $config['accessKeySecret']; 76 | } 77 | if (isset($config['endpoint']) && !empty($config['endpoint'])) { 78 | $this->endpoint = $config['endpoint']; 79 | } 80 | } 81 | 82 | /** 83 | * 创建Mns实例 84 | */ 85 | private function open() 86 | { 87 | if (empty(self::$client)) { 88 | self::$client = new Client($this->endpoint, $this->accessKeyID, $this->accessKeySecret); 89 | } 90 | } 91 | 92 | /** 93 | * 关闭连接 94 | * @return boolean 95 | */ 96 | public function close() 97 | { 98 | self::$client = null; 99 | return true; 100 | } 101 | 102 | /** 103 | * 弹出队头任务(blocking) 104 | * @param string $queueName 105 | * @param array $extends 106 | * @return Job|null 107 | */ 108 | protected function pop($queueName, & $extends = []) 109 | { 110 | $this->open(); 111 | 112 | // 不需要对消息做base64操作,Job自己会处理 113 | $queue = self::$client->getQueueRef($queueName, false); 114 | 115 | // 获取消息 116 | $response = $this->receiveMessage($queue); 117 | if (empty($response)) { 118 | return null; 119 | } 120 | 121 | // 消息内容 122 | $messageBody = $response->getMessageBody(); 123 | 124 | // 给ack动作传递参数 125 | $extends = [ 126 | 'queue' => $queue, 127 | 'response' => $response, 128 | ]; 129 | 130 | // 消息删除成功,生成Job返回 131 | return Job::Decode($messageBody); 132 | 133 | } 134 | 135 | /** 136 | * 确认任务 137 | * @param string $queueName 138 | * @param Job $job 139 | * @param array $extends 140 | */ 141 | protected function ack($queueName, Job $job = null, $extends = []) 142 | { 143 | // 删除消息 144 | $this->deleteMessage($extends['queue'], $extends['response']); 145 | } 146 | 147 | 148 | /** 149 | * 获取消息响应对象 150 | * @param Queue $queue 151 | * @return ReceiveMessageResponse|null 152 | */ 153 | private function receiveMessage(Queue $queue) 154 | { 155 | try { 156 | // 获取消息,参数为等待时长,设置为0就是立即返回,大于0该方法就为阻塞方法 157 | return $queue->receiveMessage($this->popTimeOut); 158 | } catch (MessageNotExistException $e) { 159 | // 消息不存在,直接返回null 160 | return null; 161 | } catch (MnsException $e) { 162 | Log::warning('Mns ReceiveMessage Failed: '.$e); 163 | return null; 164 | } 165 | } 166 | 167 | /** 168 | * 删除消息 169 | * @param Queue $queue 170 | * @param ReceiveMessageResponse $response 171 | * @param int $times 当前尝试的次数 172 | * @return bool 173 | */ 174 | private function deleteMessage(Queue $queue, ReceiveMessageResponse $response, $times = 1) 175 | { 176 | try { 177 | // 获取ReceiptHandle,这是一个有时效性的Handle,可以用来设置Message的各种属性和删除Message。 178 | $receiptHandle = $response->getReceiptHandle(); 179 | 180 | // 使用这个时效性的Handle删除Message 181 | $queue->deleteMessage($receiptHandle); 182 | return true; 183 | } catch (MnsException $e) { 184 | 185 | // 是否重试 186 | if ($e->getMnsErrorCode() == 'MessageNotExist') { 187 | // 如果是receiptHandle已经过期,那么ErrorCode是MessageNotExist,表示通过这个receiptHandle已经找不到对应的消息。 188 | // 所以此区间尝试也没有用,所以不用尝试 189 | $isReTry = false; 190 | } else { 191 | $isReTry = true; 192 | } 193 | 194 | if ($isReTry && $times < $this->deleteMaxTryTimes) { 195 | // 重试 196 | Log::warning('Mns DeleteMessage Failed(times: '.$times.'): '.$e); 197 | return $this->deleteMessage($queue, $response, $times + 1); 198 | } else { 199 | // 不需要重试的或者重试完成还是报错的错误直接记录 200 | Log::error('Mns DeleteMessage Failed(times: '.$times.',no longer try): '.$e); 201 | return false; 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * 压入队列 208 | * @param Job $job 209 | * @param String $queueName 队列名 210 | * @return boolean 211 | */ 212 | public function push(Job $job, $queueName) 213 | { 214 | return $this->pushMns($queueName, $job, 0); 215 | } 216 | 217 | /** 218 | * 添加一条延迟任务 219 | * @param int $delay 延迟的秒数 220 | * @param Job $job 任务 221 | * @param String $queueName 队列名 222 | * @return boolean 223 | */ 224 | public function later($delay, Job $job, $queueName) 225 | { 226 | return $this->pushMns($queueName, $job, $delay); 227 | } 228 | 229 | 230 | /** 231 | * 推送消息到Mns 232 | * @param string $queueName 队列名称 233 | * @param Job $job 任务对象 234 | * @param int|null $delay 消息的DelaySeconds参数(延迟消费时间),如果为null,则延迟时间由aliyun控制台决定 235 | * @param int $times 当前尝试的次数 236 | * @return bool 237 | */ 238 | private function pushMns($queueName, Job $job, $delay = null, $times = 1) 239 | { 240 | $this->open(); 241 | 242 | // 不需要对消息做base64操作,Job自己会处理 243 | $queue = self::$client->getQueueRef($queueName, false); 244 | 245 | // 消息体 246 | $messageBody = Job::Encode($job); 247 | 248 | // 发送消息对象 249 | $sendMessage = new SendMessageRequest($messageBody, $delay); 250 | 251 | try { 252 | 253 | // 消息发送成功 254 | $queue->sendMessage($sendMessage); 255 | return true; 256 | 257 | } catch (MnsException $e) { 258 | // 可能因为网络错误,或MessageBody过大等原因造成发送消息失败 259 | 260 | // 是否重试 261 | $isReTry = false; 262 | if ($e->getMnsErrorCode() == 'ServerError') { 263 | $isReTry = true; 264 | } else if (strpos($e->getMessage(), 'cURL error 28: Operation timed out') !== false) { 265 | $isReTry = true; 266 | } 267 | 268 | if ($isReTry && $times < $this->pushMaxTryTimes) { 269 | // 重试 270 | Log::warning('Mns SendMessage Failed(times: '.$times.'): '.$e); 271 | $times ++; 272 | return $this->pushMns($queueName, $job, $delay, $times); 273 | } else { 274 | // 不需要重试的或者重试完成还是报错的错误直接记录并报警 275 | Log::error('Mns SendMessage Failed(times: '.$times.',no longer try): '.$e); 276 | return false; 277 | } 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "43381da7a4013b345caf43be3216557f", 8 | "packages": [ 9 | { 10 | "name": "aliyun/aliyun-mns-php-sdk", 11 | "version": "1.1.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/aliyun/aliyun-mns-php-sdk.git", 15 | "reference": "f4b32a964d6e411b6dcc0137e00e94c47dd8b3e1" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/aliyun/aliyun-mns-php-sdk/zipball/f4b32a964d6e411b6dcc0137e00e94c47dd8b3e1", 20 | "reference": "f4b32a964d6e411b6dcc0137e00e94c47dd8b3e1", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "guzzlehttp/guzzle": ">=6.0.0", 25 | "php": ">=5.5.0", 26 | "psr/http-message": "^1.0" 27 | }, 28 | "type": "library", 29 | "autoload": { 30 | "psr-4": { 31 | "AliyunMNS\\": "AliyunMNS/" 32 | } 33 | }, 34 | "notification-url": "https://packagist.org/downloads/", 35 | "license": [ 36 | "MIT" 37 | ], 38 | "authors": [ 39 | { 40 | "name": "Aliyun MNS Team", 41 | "homepage": "http://www.aliyun.com/product/mns" 42 | } 43 | ], 44 | "description": "Aliyun Message and Notification Service SDK for PHP, PHP>=5.5.0", 45 | "homepage": "https://github.com/aliyun/aliyun-mns-php-sdk", 46 | "keywords": [ 47 | "aliyun", 48 | "message", 49 | "message service", 50 | "mns", 51 | "notification" 52 | ], 53 | "time": "2019-06-14T04:35:52+00:00" 54 | }, 55 | { 56 | "name": "guzzlehttp/guzzle", 57 | "version": "6.3.3", 58 | "source": { 59 | "type": "git", 60 | "url": "https://github.com/guzzle/guzzle.git", 61 | "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" 62 | }, 63 | "dist": { 64 | "type": "zip", 65 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", 66 | "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", 67 | "shasum": "" 68 | }, 69 | "require": { 70 | "guzzlehttp/promises": "^1.0", 71 | "guzzlehttp/psr7": "^1.4", 72 | "php": ">=5.5" 73 | }, 74 | "require-dev": { 75 | "ext-curl": "*", 76 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", 77 | "psr/log": "^1.0" 78 | }, 79 | "suggest": { 80 | "psr/log": "Required for using the Log middleware" 81 | }, 82 | "type": "library", 83 | "extra": { 84 | "branch-alias": { 85 | "dev-master": "6.3-dev" 86 | } 87 | }, 88 | "autoload": { 89 | "files": [ 90 | "src/functions_include.php" 91 | ], 92 | "psr-4": { 93 | "GuzzleHttp\\": "src/" 94 | } 95 | }, 96 | "notification-url": "https://packagist.org/downloads/", 97 | "license": [ 98 | "MIT" 99 | ], 100 | "authors": [ 101 | { 102 | "name": "Michael Dowling", 103 | "email": "mtdowling@gmail.com", 104 | "homepage": "https://github.com/mtdowling" 105 | } 106 | ], 107 | "description": "Guzzle is a PHP HTTP client library", 108 | "homepage": "http://guzzlephp.org/", 109 | "keywords": [ 110 | "client", 111 | "curl", 112 | "framework", 113 | "http", 114 | "http client", 115 | "rest", 116 | "web service" 117 | ], 118 | "time": "2018-04-22T15:46:56+00:00" 119 | }, 120 | { 121 | "name": "guzzlehttp/promises", 122 | "version": "v1.3.1", 123 | "source": { 124 | "type": "git", 125 | "url": "https://github.com/guzzle/promises.git", 126 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" 127 | }, 128 | "dist": { 129 | "type": "zip", 130 | "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", 131 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", 132 | "shasum": "" 133 | }, 134 | "require": { 135 | "php": ">=5.5.0" 136 | }, 137 | "require-dev": { 138 | "phpunit/phpunit": "^4.0" 139 | }, 140 | "type": "library", 141 | "extra": { 142 | "branch-alias": { 143 | "dev-master": "1.4-dev" 144 | } 145 | }, 146 | "autoload": { 147 | "psr-4": { 148 | "GuzzleHttp\\Promise\\": "src/" 149 | }, 150 | "files": [ 151 | "src/functions_include.php" 152 | ] 153 | }, 154 | "notification-url": "https://packagist.org/downloads/", 155 | "license": [ 156 | "MIT" 157 | ], 158 | "authors": [ 159 | { 160 | "name": "Michael Dowling", 161 | "email": "mtdowling@gmail.com", 162 | "homepage": "https://github.com/mtdowling" 163 | } 164 | ], 165 | "description": "Guzzle promises library", 166 | "keywords": [ 167 | "promise" 168 | ], 169 | "time": "2016-12-20T10:07:11+00:00" 170 | }, 171 | { 172 | "name": "guzzlehttp/psr7", 173 | "version": "1.6.1", 174 | "source": { 175 | "type": "git", 176 | "url": "https://github.com/guzzle/psr7.git", 177 | "reference": "239400de7a173fe9901b9ac7c06497751f00727a" 178 | }, 179 | "dist": { 180 | "type": "zip", 181 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", 182 | "reference": "239400de7a173fe9901b9ac7c06497751f00727a", 183 | "shasum": "" 184 | }, 185 | "require": { 186 | "php": ">=5.4.0", 187 | "psr/http-message": "~1.0", 188 | "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" 189 | }, 190 | "provide": { 191 | "psr/http-message-implementation": "1.0" 192 | }, 193 | "require-dev": { 194 | "ext-zlib": "*", 195 | "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" 196 | }, 197 | "suggest": { 198 | "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" 199 | }, 200 | "type": "library", 201 | "extra": { 202 | "branch-alias": { 203 | "dev-master": "1.6-dev" 204 | } 205 | }, 206 | "autoload": { 207 | "psr-4": { 208 | "GuzzleHttp\\Psr7\\": "src/" 209 | }, 210 | "files": [ 211 | "src/functions_include.php" 212 | ] 213 | }, 214 | "notification-url": "https://packagist.org/downloads/", 215 | "license": [ 216 | "MIT" 217 | ], 218 | "authors": [ 219 | { 220 | "name": "Michael Dowling", 221 | "email": "mtdowling@gmail.com", 222 | "homepage": "https://github.com/mtdowling" 223 | }, 224 | { 225 | "name": "Tobias Schultze", 226 | "homepage": "https://github.com/Tobion" 227 | } 228 | ], 229 | "description": "PSR-7 message implementation that also provides common utility methods", 230 | "keywords": [ 231 | "http", 232 | "message", 233 | "psr-7", 234 | "request", 235 | "response", 236 | "stream", 237 | "uri", 238 | "url" 239 | ], 240 | "time": "2019-07-01T23:21:34+00:00" 241 | }, 242 | { 243 | "name": "mojiehai/process_manage", 244 | "version": "1.0.3", 245 | "source": { 246 | "type": "git", 247 | "url": "https://github.com/mojiehai/process_manage.git", 248 | "reference": "3ba037be1f0d45ca958995d2aefdf494481788b1" 249 | }, 250 | "dist": { 251 | "type": "zip", 252 | "url": "https://api.github.com/repos/mojiehai/process_manage/zipball/3ba037be1f0d45ca958995d2aefdf494481788b1", 253 | "reference": "3ba037be1f0d45ca958995d2aefdf494481788b1", 254 | "shasum": "" 255 | }, 256 | "require": { 257 | "ext-json": "*", 258 | "ext-mbstring": "*", 259 | "ext-pcntl": "*", 260 | "ext-posix": "*", 261 | "php": ">=7.0" 262 | }, 263 | "type": "library", 264 | "autoload": { 265 | "psr-4": { 266 | "ProcessManage\\": "./src/" 267 | } 268 | }, 269 | "notification-url": "https://packagist.org/downloads/", 270 | "license": [ 271 | "MIT" 272 | ], 273 | "authors": [ 274 | { 275 | "name": "mojiehai", 276 | "email": "804527505@qq.com" 277 | } 278 | ], 279 | "description": "process manage", 280 | "keywords": [ 281 | "manage", 282 | "process" 283 | ], 284 | "time": "2019-01-30T07:36:36+00:00" 285 | }, 286 | { 287 | "name": "php-amqplib/php-amqplib", 288 | "version": "v2.10.0", 289 | "source": { 290 | "type": "git", 291 | "url": "https://github.com/php-amqplib/php-amqplib.git", 292 | "reference": "04e5366f032906d5f716890427e425e71307d3a8" 293 | }, 294 | "dist": { 295 | "type": "zip", 296 | "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/04e5366f032906d5f716890427e425e71307d3a8", 297 | "reference": "04e5366f032906d5f716890427e425e71307d3a8", 298 | "shasum": "" 299 | }, 300 | "require": { 301 | "ext-bcmath": "*", 302 | "ext-sockets": "*", 303 | "php": ">=5.6" 304 | }, 305 | "replace": { 306 | "videlalvaro/php-amqplib": "self.version" 307 | }, 308 | "require-dev": { 309 | "ext-curl": "*", 310 | "nategood/httpful": "^0.2.20", 311 | "phpdocumentor/phpdocumentor": "dev-master", 312 | "phpunit/phpunit": "^5.7|^6.5|^7.0", 313 | "squizlabs/php_codesniffer": "^2.5" 314 | }, 315 | "type": "library", 316 | "extra": { 317 | "branch-alias": { 318 | "dev-master": "2.10-dev" 319 | } 320 | }, 321 | "autoload": { 322 | "psr-4": { 323 | "PhpAmqpLib\\": "PhpAmqpLib/" 324 | } 325 | }, 326 | "notification-url": "https://packagist.org/downloads/", 327 | "license": [ 328 | "LGPL-2.1-or-later" 329 | ], 330 | "authors": [ 331 | { 332 | "name": "Alvaro Videla", 333 | "role": "Original Maintainer" 334 | }, 335 | { 336 | "name": "John Kelly", 337 | "email": "johnmkelly86@gmail.com", 338 | "role": "Maintainer" 339 | }, 340 | { 341 | "name": "Raúl Araya", 342 | "email": "nubeiro@gmail.com", 343 | "role": "Maintainer" 344 | }, 345 | { 346 | "name": "Luke Bakken", 347 | "email": "luke@bakken.io", 348 | "role": "Maintainer" 349 | } 350 | ], 351 | "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", 352 | "homepage": "https://github.com/php-amqplib/php-amqplib/", 353 | "keywords": [ 354 | "message", 355 | "queue", 356 | "rabbitmq" 357 | ], 358 | "time": "2019-08-08T18:28:18+00:00" 359 | }, 360 | { 361 | "name": "psr/http-message", 362 | "version": "1.0.1", 363 | "source": { 364 | "type": "git", 365 | "url": "https://github.com/php-fig/http-message.git", 366 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 367 | }, 368 | "dist": { 369 | "type": "zip", 370 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 371 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 372 | "shasum": "" 373 | }, 374 | "require": { 375 | "php": ">=5.3.0" 376 | }, 377 | "type": "library", 378 | "extra": { 379 | "branch-alias": { 380 | "dev-master": "1.0.x-dev" 381 | } 382 | }, 383 | "autoload": { 384 | "psr-4": { 385 | "Psr\\Http\\Message\\": "src/" 386 | } 387 | }, 388 | "notification-url": "https://packagist.org/downloads/", 389 | "license": [ 390 | "MIT" 391 | ], 392 | "authors": [ 393 | { 394 | "name": "PHP-FIG", 395 | "homepage": "http://www.php-fig.org/" 396 | } 397 | ], 398 | "description": "Common interface for HTTP messages", 399 | "homepage": "https://github.com/php-fig/http-message", 400 | "keywords": [ 401 | "http", 402 | "http-message", 403 | "psr", 404 | "psr-7", 405 | "request", 406 | "response" 407 | ], 408 | "time": "2016-08-06T14:39:51+00:00" 409 | }, 410 | { 411 | "name": "ralouphie/getallheaders", 412 | "version": "3.0.3", 413 | "source": { 414 | "type": "git", 415 | "url": "https://github.com/ralouphie/getallheaders.git", 416 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 417 | }, 418 | "dist": { 419 | "type": "zip", 420 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 421 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 422 | "shasum": "" 423 | }, 424 | "require": { 425 | "php": ">=5.6" 426 | }, 427 | "require-dev": { 428 | "php-coveralls/php-coveralls": "^2.1", 429 | "phpunit/phpunit": "^5 || ^6.5" 430 | }, 431 | "type": "library", 432 | "autoload": { 433 | "files": [ 434 | "src/getallheaders.php" 435 | ] 436 | }, 437 | "notification-url": "https://packagist.org/downloads/", 438 | "license": [ 439 | "MIT" 440 | ], 441 | "authors": [ 442 | { 443 | "name": "Ralph Khattar", 444 | "email": "ralph.khattar@gmail.com" 445 | } 446 | ], 447 | "description": "A polyfill for getallheaders.", 448 | "time": "2019-03-08T08:55:37+00:00" 449 | } 450 | ], 451 | "packages-dev": [], 452 | "aliases": [], 453 | "minimum-stability": "stable", 454 | "stability-flags": [], 455 | "prefer-stable": false, 456 | "prefer-lowest": false, 457 | "platform": { 458 | "php": ">=7.0", 459 | "ext-mysqli": "*", 460 | "ext-json": "*", 461 | "ext-redis": "*", 462 | "ext-posix": "*" 463 | }, 464 | "platform-dev": [] 465 | } 466 | --------------------------------------------------------------------------------