thinkphp5 使用 RabbitMQ代码示例,amqp扩展非php-amqplib

本文所使用的是PHP的amqp扩展,不是composer安装的php-amqplib

一、在控制器里封装了一个基类–生产者

<?php
namespace app\home\controller;

/**
 * RabbitMQ 基类
 * 直连模式
 */
class RabbitMqController
{
    private $conn;
    private $channel;
    private $ex;
    private $queue;
    private $routingKey;
    public function __construct($exchangeName,$queueName,$routingKey)
    {

        $this->exchangeName = $exchangeName;
        $this->queueName = $queueName;
        $this->routingKey = $routingKey;
        $this->conf = [
            'host' => '127.0.0.1',
            'port' => 5672,
            'login' => 'guest', //改为自己RabbitMQ账号
            'password' => 'guest', //改为自己RabbitMQ密码
        ];

        //  1、创建链接对象
        $this->conn = new \AMQPConnection($this->conf);
        if (!$this->conn->connect()) {
            echo json(["st" => 505, "errMsg" => "Cannot connect to the rabbitMQ"]);
            die();
        }

        //  2、创建通道
        $this->channel = new \AMQPChannel($this->conn);
        $this->channel->qos(0,2);
        //  3、创建交换机
        $this->ex = new \AMQPExchange($this->channel);
        //  4、设置交换机名称
        $this->ex->setName($this->exchangeName);
        //  5、设置交换机的类型
        $this->ex->setType(AMQP_EX_TYPE_DIRECT);
        //  6、设置交换机持久化
        $this->ex->setFlags(AMQP_DURABLE);
        //  7、声明交换机
        $this->ex->declareExchange();

        //  8、创建消息队列
        $this->queue = new \AMQPQueue($this->channel);
        //  9、设置消息队列的名称
        $this->queue->setName($this->queueName);
        //  10、设置消息队列持久化(消息要想持久化,交换机和消息队列必须要持久化)
        $this->queue->setFlags(AMQP_DURABLE);
        //  11、声明消息队列
        $this->queue->declareQueue();
        //  12、绑定交换机和路由键
        $this->queue->bind($this->exchangeName, $this->routingKey);

    }

    public function ping(){
        die('pone');
    }

    /**
     * @param $msg array 消息内容
     * @throws AMQPChannelException
     * @throws AMQPConnectionException
     * @throws AMQPExchangeException
     *
     * @return bool TRUE on success or FALSE on failure.
     */
    public function sendMsg($msg){
        $msgBody = is_array($msg)?json_encode($msg):$msg;
        return $this->ex->publish($msgBody,$this->routingKey,AMQP_NOPARAM,array('delivery_mode' => 2));
    }
}

二、在控制器里封装了一个基类–消费者

class RabbitMqConsumerController
{
    private $conn;
    private $channel;
    private $queue;
    private $routingKey;
    public function __construct($exchangeName,$queueName,$routingKey)
    {

        $this->exchangeName = $exchangeName;
        $this->queueName = $queueName;
        $this->routingKey = $routingKey;
        $this->conf = [
            'host' => '127.0.0.1', //部署时,修改为自己RabbitMQ服务器地址
            'port' => 5672,
            'login' => 'guest', //改为自己RabbitMQ账号
            'password' => 'guest', //改为自己RabbitMQ密码
            'heartbeat'=> 600, //心跳,主要是给消费者使用
        ];

        //  1、创建链接对象
        $this->conn = new \AMQPConnection($this->conf);
        if (!$this->conn->connect()) {
            echo json(["st" => 505, "errMsg" => "Cannot connect to the rabbitMQ"]);
            die();
        }

        //  2、创建通道
        $this->channel = new \AMQPChannel($this->conn);
        $this->channel->qos(0,2); //消息预取,此处设置为2 表示只有两条消息可先发送给消费者,后续新消息在队列里等待消费者消费完再发送,防止消息过多,消费者压力过大发生异常

        //  3、创建消息队列
        $this->queue = new \AMQPQueue($this->channel);
        $this->queue->setName($this->queueName); //  设置消息队列的名称
        $this->queue->setFlags(AMQP_DURABLE); //  设置消息队列持久化(消息要想持久化,交换机和消息队列必须要持久化)
        $this->queue->declareQueue(); //  声明消息队列

        //  4、绑定交换机和路由键
        $this->queue->bind($this->exchangeName, $this->routingKey);

    }

    public function ping(){
        die('pone');
    }

    /**
     * @param $parma array [$message,"processMessage"]
     * $message为Message的对象,processMessage为Message的方法
     * 调用该方法需要用php的cli模式
     * @throws AMQPChannelException
     * @throws AMQPConnectionException
     * @throws AMQPEnvelopeException
     */
    public function consume($parma){
        // 阻塞
        while(True){
            $this->queue->consume($parma);
        }
    }
}

三、生产者:

<?php

namespace app\home\controller;
use think\db;

/**
 * 消费者写入消息
 */
class MqController{

    public $code = "test";

    /**
     * 消息写入
     */
    public function pub() {
    	//业务需求,给每个消息创建了一个唯一key, 然后将消息+key写入mysql库。
    	$msgData['type'] = 'SendEmail';  //每种信息都有一个类型
        $msgData['unique_key'] = date('YmdHis').mt_rand (1000,9999);
		$msgData['info'] = 'test';
        if($this->insertData($msgData)){
            $mq = new RabbitMqController($this->code,$this->code,$this->code); //交换机、队列名、路由键我这里用的是一个变量
            $mq->sendMsg($msgData); //发送消息到RabbitMQ服务器
        }

        $resultData['code'] = 1;
        $resultData['unique_key'] = $msgData['unique_key'];
        $resultData['msg'] = '消息已写入';
        return json($resultData);
    }

    //消息入库
    public function insertData($data){
        if(isset($data['type']) && isset($data['unique_key'])){
            if(!empty($data['type']) && !empty($data['unique_key'])){
                $insertData['type'] = $data['type'];
                $insertData['unique_key'] = $data['unique_key'];
                $insertData['msg_data'] = json_encode($data);
                $res = Db::name('mq_info')->insert($insertData);
                if($res){
                    return true;
                }
            }
        }
        return false;
    }
}

四、消费者:

<?php

namespace app\home\controller;
use think\Db;

/**
 * 消息队列消费者
 * 部署:
 * PHP环境部署需要安装RabbitMQ扩展 http://pecl.php.net/package/amqp (选择PHP对应的版本扩展);PHP配置文件,加入 extension=php_amqp.dll
 * 配置php环境变量;切换到项目public目录cmd `php index.php zoneking_dept/sync_mq_consumer/receiveMsg`
 */
class MqConsumerController {

    function processMessage($envelope, $queue) {
        $msg = json_decode($envelope->getBody(),true);
        echo 'message:'. PHP_EOL;
        var_dump($msg); //打印接收到的消息
        echo PHP_EOL;
		try{
			if(!empty($msg['type']) && !empty($msg['unique_key'])){
            	$res = false;
            	switch ($msg['type']){
                	case 'SendEmail':
                    $res = $this->toSend($msg); //处理消息,业务操作...
                    break;
            	}
            	if($res){
                	$queue->ack($envelope->getDeliveryTag()); //手动 ack
                	echo 'message deal ok'. PHP_EOL;
            	}
           	}
		}catch (\Throwable $e){//Throwable是PHP7中可以用作任何对象抛出声明的基本接口,包括 Expection 异常和 Error 错误。
			$queue->ack($envelope->getDeliveryTag());

            $updateData['deal_data'] = 'message discarded!!!!'.$e->getMessage();
            $this->updateData($msg['type'],$msg['unique_key'],$updateData);
            echo 'message discarded!!!!'.$e->getMessage(). PHP_EOL;
		}
        
    }

    public function receiveMsg()
    {
        try{
            $mq = new RabbitMqConsumerController($this->code,$this->code,$this->code);
            echo 'Connected to the rabbitMQ'. PHP_EOL;
            $message = new SyncMqConsumerController();
            $mq->consume([$message,"processMessage"]);
        }catch (\Throwable $e){ //如果连接不到RabbitMQ服务器,则捕获异常,启动重连机制
            echo 'Cannot connect to the rabbitMQ'. PHP_EOL;
            echo $e->getMessage();
            echo PHP_EOL;
            while (true) //每60秒重试
            {
                echo 'Try reconnect...'. PHP_EOL;
                sleep(60);
                $this->receiveMsg();
                break;
            }
        }

    }

    /**
     * 发送邮箱信息
     */
    public function toSend($msg) {
        ...
        $updateData['deal_data'] = '处理完毕!';
        $this->updateData($msg['type'],$msg['unique_key'],$updateData);
        return true;

    }

	//消费者
    public function updateData($type,$unique_key,$updateData){
        if(!empty($type) && !empty($unique_key)){
            $map['type'] = $type;
            $map['unique_key'] = $unique_key;

            $updateData['deal_ok'] = 'true';
            $res = Db::name('mq_info')->where($map)->update($updateData);
            if($res){
                return true;
            }
        }
        return false;
    }

}

消费信息mysql存储

1. 如何启动消费者?
配置php环境变量;切换到项目public目录cmd php index.php zoneking_dept/sync_mq_consumer/receiveMsg

2. 消费者一段时间没有消息处理,就会出现卡死现象,再有新消息不会自动处理问题?
给消费者设置一个心跳,定时发送心跳告知服务器自己还“活着”,

$this->conf = [
   	 ...
    'heartbeat'=> 10, //心跳,主要是给消费者使用,这里重点
];

$this->conn = new \AMQPConnection($this->conf);

3. 服务器关闭后,消费者连接抛异常,等服务器再打开,消费者不会重新进入工作?
给消费者加上重试机制,当连接不到服务器就每隔一段时间进行重新连接,直到连接上为止。重点代码:

try{
            $mq = new RabbitMqController($this->center_code,$this->center_code,$this->center_code);
            echo 'Connected to the rabbitMQ'. PHP_EOL;
            $message = new SyncMqConsumerController();
            $mq->consume([$message,"processMessage"]);
        }catch (\Exception $e){ //如果连接不到RabbitMQ服务器,则捕获异常,启动重连机制
            echo 'Cannot connect to the rabbitMQ'. PHP_EOL;
            echo $e->getMessage();
            echo PHP_EOL;
            while (true) //每60秒重试
            {
                echo 'Try reconnect...'. PHP_EOL;
                sleep(60);
                $this->receiveMsg();
                break;
            }
        }

4. 消费者加上了心跳机制,但是不管用?
我们可以登录管理面板查看是否启用了心跳机制
在这里插入图片描述
如果 Heartbeat 为空则说明没有启动心跳。但是代码里明明配置上了,为什么不起作用呢?这个问题一直困扰了我好久。
主要问题在于你安装的 amqp扩展 版本支不支持心跳,你在 phpinfo() 里可以查看到
在这里插入图片描述
有的低版本的扩展不支持,当你使用php7 时,对应的windows扩展,(注意此处是windows)才支持心跳,于是我升级了PHP版本,下载了对应的扩展安装,问题解决。
扩展下载地址 http://pecl.php.net/package/amqp (选择PHP对应的版本扩展);PHP配置文件,加入 extension=php_amqp.dll

5. 工作模式?
如上代码的消费者可以开启工作模式,即可以有多个消费者同时分摊队列中的消息,我们只需开启多个cmd窗口:
切换到项目public目录cmd php index.php zoneking_dept/sync_mq_consumer/receiveMsg

6. 其他模式

参考:http://www.hangdaowangluo.com/archives/1337
文章底部有相关阅读
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有码无尘

知识无价,有收获就好!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值