基于【TP5框架】开发消息队列(阿里云)

        小张这里使用的是阿里云消息队列,消息队列开通地址是:https://mns.console.aliyun.com/#/list/cn-shenzhen
     基于【TP5框架】开发消息队列(阿里云)_第1张图片
    
      1.下载aliyun-queue包,注意:这个包不能composer.json下载,只能手动引入
       基于【TP5框架】开发消息队列(阿里云)_第2张图片
 2.获取阿里云的第一条消息信息,(等下我这里会循环读取第一条)

 //初始化阿里云消息队列
         require ROOT_PATH . 'vendor/aliyun-queue/mns-autoloader.php';
         //获取配置信息
         $system_config = config('aliyun_config');
         //初始化队列client
         $mns = new \AliyunMNS\Client($system_config['mns_end_point'], $system_config['key_id'], $system_config['key_secret']);//配置密钥
      
         //获取队列     
         $queue = $mns->getQueueRef('xiaozhang');
        try {
            /*
             * 获取第一条当前消息信息
             */
            $res = $queue->receiveMessage(0);//当前第一条
            $msg = $res->getMessageBody();//输出当前数据
        
        } catch (\AliyunMNS\Exception\MnsException $e) {
            echo $e;exit;
        }

3.当读取成功了,我们就开始做个死循环读取消息队列进程,先记录进程


  //进程记录数组
    public $pid_arr = [
        'master' => 0,
        'son' => [
        ]
    ];
    //队列配置
    public $config = [
        [
            //队列名
            'queue_name' => 'xiaozhang',
        ]
    ];
    //liunx命令操作
    public function index() {
        set_time_limit(0);
        global $argv; //获取全局变量  global是设置全局变量

        switch ($argv[1]) {
            case 'start':
                $this->start();
                break;
            case 'stop':
                $this->stop();
                break;
        }
    }

4.队列开始操作

 /*
     * 队列处理启动
     * @主进程会于运行一边就销毁(php运行一边就自动销毁的特性)
     * @子进程会一直循环下去
     */

    public function start() {
        if (file_exists(ROOT_PATH . "/public/queue.pid")) {//进程已经启动
            echo "程序运行中! \n";
            exit;
        }

        //取主进程id
        $this->pid_arr['master'] = getmypid();

        //初始化阿里云消息队列
        require ROOT_PATH . 'vendor/aliyun-queue/mns-autoloader.php';
        //获取配置信息
        $system_config = config('aliyun_config');
        //初始化队列client
        $mns = new \AliyunMNS\Client($system_config['mns_end_point'], $system_config['key_id'], $system_config['key_secret']);
        //初始化分析类
        $analysis = new \app\event\controller\MsgController();

        //循环创建进程
        for ($i = 0; $i < count($this->config); $i++) {
            $pid = pcntl_fork();//操作系统创建一个新的进程(子进程),
            if ($pid == -1) {
                //错误
                echo "进程创建失败! \n";
            } elseif ($pid == 0) {
                //当前是子进程
                echo "进程创建成功 id:" . getmypid() . " \n";
                //获取队列
                $queue = $mns->getQueueRef($this->config[$i]['queue_name']);
                //执行任务
                $this->queques($queue, $this->config[$i]['queue_name'], $analysis);
                break;
            } else {
                //当前是主进程
                $this->pid_arr['son'][] = $pid;
                //判断是否为最后一次循环
                if (count($this->config) - $i == 1) {
                    //写入文件
                    file_put_contents(ROOT_PATH . "/public/queue.pid", json_encode($this->pid_arr));
                    echo "运行完毕 主进程ID:" . getmypid() . "\n";
                }
            }
            //延迟20ms
            usleep(1000 * 20);
        }
    }

5.结束进程
 /*
     * 队列停止
     */

    public function stop() {
        $proce_file = file_get_contents(ROOT_PATH . "/public/queue.pid");
        if (!$proce_file) {
            echo "进程未运行! \n";
            exit;
        }
        $arr = json_decode($proce_file, true);
        if (!$arr) {
            unlink(ROOT_PATH . "/public/queue.pid");
            echo "进程未运行! \n";
            exit;
        }
        foreach ($arr['son'] as $val) {
            //发送终止进程信号
            posix_kill($val, SIGTERM);
        }
        // posix_kill($arr['master'], SIGTERM);
        unlink(ROOT_PATH . "/public/queue.pid");
        echo "停止成功!\n";
    }

6.最关键的循环操作
  /*
     * 队列处理
     */

    public function queques($queue, $queue_name, $analysis) {
        $sleep = 1000000 * rand(1, 3);
        while (1) {
            usleep($sleep); //延迟
            /*
             * 获取消息
             */
            try {
                $res = $queue->receiveMessage(0);
                $msg = $res->getMessageBody();
                $receiptHandle = $res->getReceiptHandle();
                $sleep = 200000; //取出数据则激活队列,100毫秒执行一次循环
            } catch (\AliyunMNS\Exception\MnsException $e) {
                // 没有消息
                $sleep = 2000000; //没有取出数据则延长循环时间为2秒一次循环
                continue;
            }
            /*
             * 处理消息
             */
            $arr = json_decode($msg, TRUE);
            if ($arr) {
                $type = (string) $arr['type'];
                if ($type) {
                    $analysis->$type($arr, $queue_name);
                } else {
                    $this->log($queue_name, "收到一条没有类型的队列消息,MSG:" . $msg);
                }
            } else {
                $this->log($queue_name, "收到一条无法JSON解析的队列消息,MSG:" . $msg);
            }
            /*
             * 删除消息
             */
            try {
                $res = $queue->deleteMessage($receiptHandle);
            } catch (\AliyunMNS\Exception\MnsException $e) {
                continue;
            }
        }
    }

为了方便大家观看,一套代码就贴出来了,这个消息队列的原理就是开个死循环进程去读取消息队列。

 0,
        'son' => [
        ]
    ];
    //队列配置
    public $config = [
        [
            //队列名
            'queue_name' => 'xiaozhang',
        ]
    ];

    public function index() {
        set_time_limit(0);
        global $argv; //获取全局变量  global是设置全局变量

        switch ($argv[1]) {
            case 'start':
                $this->start();
                break;
            case 'stop':
                $this->stop();
                break;
        }
    }

    /*
     * 队列处理启动
     * @主进程会于运行一边就销毁(php运行一边就自动销毁的特性)
     * @子进程会一直循环下去
     */

    public function start() {
        if (file_exists(ROOT_PATH . "/public/queue.pid")) {//进程已经启动
            echo "程序运行中! \n";
            exit;
        }

        //取主进程id
        $this->pid_arr['master'] = getmypid();

        //初始化阿里云消息队列
        require ROOT_PATH . 'vendor/aliyun-queue/mns-autoloader.php';
        //获取配置信息
        $system_config = config('aliyun_config');
        //初始化队列client
        $mns = new \AliyunMNS\Client($system_config['mns_end_point'], $system_config['key_id'], $system_config['key_secret']);
        //初始化分析类
        $analysis = new \app\event\controller\MsgController();

        //循环创建进程
        for ($i = 0; $i < count($this->config); $i++) {
            $pid = pcntl_fork();//操作系统创建一个新的进程(子进程),
            if ($pid == -1) {
                //错误
                echo "进程创建失败! \n";
            } elseif ($pid == 0) {
                //当前是子进程
                echo "进程创建成功 id:" . getmypid() . " \n";
                //获取队列
                $queue = $mns->getQueueRef($this->config[$i]['queue_name']);
                //执行任务
                $this->queques($queue, $this->config[$i]['queue_name'], $analysis);
                break;
            } else {
                //当前是主进程
                $this->pid_arr['son'][] = $pid;
                //判断是否为最后一次循环
                if (count($this->config) - $i == 1) {
                    //写入文件
                    file_put_contents(ROOT_PATH . "/public/queue.pid", json_encode($this->pid_arr));
                    echo "运行完毕 主进程ID:" . getmypid() . "\n";
                }
            }
            //延迟20ms
            usleep(1000 * 20);
        }
    }

    /*
     * 队列停止
     */

    public function stop() {
        $proce_file = file_get_contents(ROOT_PATH . "/public/queue.pid");
        if (!$proce_file) {
            echo "进程未运行! \n";
            exit;
        }
        $arr = json_decode($proce_file, true);
        if (!$arr) {
            unlink(ROOT_PATH . "/public/queue.pid");
            echo "进程未运行! \n";
            exit;
        }
        foreach ($arr['son'] as $val) {
            //发送终止进程信号
            posix_kill($val, SIGTERM);
        }
        // posix_kill($arr['master'], SIGTERM);
        unlink(ROOT_PATH . "/public/queue.pid");
        echo "停止成功!\n";
    }

    /*
     * 队列处理
     */

    public function queques($queue, $queue_name, $analysis) {
        $sleep = 1000000 * rand(1, 3);
        while (1) {
            usleep($sleep); //延迟
            /*
             * 获取消息
             */
            try {
                $res = $queue->receiveMessage(0);
                $msg = $res->getMessageBody();
                $receiptHandle = $res->getReceiptHandle();
                $sleep = 200000; //取出数据则激活队列,100毫秒执行一次循环
            } catch (\AliyunMNS\Exception\MnsException $e) {
                // 没有消息
                $sleep = 2000000; //没有取出数据则延长循环时间为2秒一次循环
                continue;
            }
            /*
             * 处理消息
             */
            $arr = json_decode($msg, TRUE);
            if ($arr) {
                $type = (string) $arr['type'];
                if ($type) {
                    $analysis->$type($arr, $queue_name);
                } else {
                    $this->log($queue_name, "收到一条没有类型的队列消息,MSG:" . $msg);
                }
            } else {
                $this->log($queue_name, "收到一条无法JSON解析的队列消息,MSG:" . $msg);
            }
            /*
             * 删除消息
             */
            try {
                $res = $queue->deleteMessage($receiptHandle);
            } catch (\AliyunMNS\Exception\MnsException $e) {
                continue;
            }
        }
    }

}

MsgController的代码也贴出来:

sendSms($tel, $msg), TRUE);
        if ($res['result'] === 0) {
            //发送成功
            $this->log($queue_name, "手机:{$tel} 的信息发送成功");
        } else {
            //发送失败
            $err_msg = $res['errmsg'];
            $this->log($queue_name, "手机:{$tel} 的信息发送失败,ERROR:{$err_msg}");
        }
    }

    /*
     * 信鸽推送处理
     */

    public function xinge($param, $queue_name) {
        $result = $this->xinge_push($param['uid'], $param['title'], $param['content'], $param['custom'], $param['push_type'], $param['client']);
        $app_id = config('xinge')[$param['client']]['id'];
        $this->log($queue_name, '使用' . $app_id . "推送");
        if ($result['ret_code'] === 0) {
            if (is_array($param['uid'])) {
                $this->log($queue_name, "推送成功,接收者:" . json_encode($param['uid']) . " 内容:" . json_encode($param) . " 返回值:" . json_encode($result));
            } else {
                $this->log($queue_name, "推送成功,接收者:{$param['uid']} 内容:" . json_encode($param) . " 返回值:" . json_encode($result));
            }
        } else {
            if (is_array($param['uid'])) {
                $this->log($queue_name, "推送失败,接收者:" . json_encode($param['uid']) . " ERROR_CODE:" . $result['ret_code']);
            } else {
                $this->log($queue_name, "推送失败,接收者:{$param['uid']} ERROR_CODE:" . $result['ret_code']);
            }
        }
    }

    /*
     * 微信模板消息
     */

    public function wechat_tpl($param, $queue_name) {
        $result = $this->send_tpl_msg($param['tpl_id'], $param['open_id'], $param['data'], $param['url']);
        if ($result['errcode']) {
            //发送失败
            $this->log($queue_name, "模板消息推送失败,接收者:{$param['open_id']} ERROR_CODE:{$result['errcode']} TPL_ID:{$param['tpl_id']}");
        } else {
            //发送成功
            $this->log($queue_name, "模板消息推送成功,接收者:{$param['open_id']}  TPL_ID:{$param['tpl_id']}");
        }
    }
    
    /*
     *  测试
     * (如果直接用数据库保存会线程会崩掉)
     * 也可以用阿里云上的日志接口
     */
    public function record($queue_name){
        $path = ROOT_PATH . '/queue_log/' . 'win';
        if (!is_dir($path)) {
            mkdir($path);
        }
        $file = $path . '/' . str_replace('-', '_', '奖品') . '.txt';
        $msg=[
            'uid'=>$queue_name['uid'],
            'username'=>$queue_name['msg']['username'],
            'win'=>$queue_name['msg']['win']
        ];
        file_put_contents($file,json_encode($msg, JSON_UNESCAPED_UNICODE). "\n", FILE_APPEND);
    }
    
    /*
     * 空操作(对其他没有影响)
     */

    public function __call($name, $arguments) {
        $this->log('queue_error', "执行了一条错误的队列,QueueType:{$name} QueueName:{$arguments[1]}");
    }

}

需要注意的是用消息队列以后,发送成功的提示并不是100%正确,主要的还是看日志,这点非常重要!!!!!!!!
需要注意的是用消息队列以后,发送成功的提示并不是100%正确,主要的还是看日志,这点非常重要!!!!!!!!
需要注意的是用消息队列以后,发送成功的提示并不是100%正确,主要的还是看日志,这点非常重要!!!!!!!!
最后log的方法也贴出来:

  /*
     * 写日志
     */

    public function log($queue_name, $msg) {
        $path = ROOT_PATH . '/queue_log/' . date('Y_m_d');
        if (!is_dir($path)) {
            mkdir($path);
        }
        $file = $path . '/' . str_replace('-', '_', $queue_name) . '.txt';
        $msg = "[" . date('Y-m-d H:i:s') . "]:" . $msg . "\n";
        file_put_contents($file, $msg, FILE_APPEND);
    }
这里的开启方法是 基于【TP5框架】开发消息队列(阿里云)_第3张图片

public 下新建一个service.php     (这里是用liunx命令操作的,以后会写一个网页操作,已经码好了只是懒得写了)

开始线程: php service.php start

关闭线程:  php service.php stop


你可能感兴趣的:(PHP)