配置项:
<?php
namespace app\common\library\rabbitMQ;
const HOST = '127.0.0.1';
const PORT = 5672;
const USER = 'guest';
const PASS = 'guest';
const EXCHANGE_NAME= 'router';
const QUEUE_NAME = 'msgs';
const ROUTING_KEY = 'routing_key';
const DELAY_QUEUE_NAME = 'delayed_queue';
const DELAY_EXCHANGE_NAME = 'delayed_exchange';
const DELAY_ROUTING_KEY = 'delayed_key';
定义基类服务:
<?php
namespace app\common\library\rabbitMQ;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Exchange\AMQPExchangeType;
use PhpAmqpLib\Wire\AMQPTable;
include_once(__DIR__ .'/mq_config.php');
class BaseMQService
{
/**
* MQ events must be declared before use
*/
const PLATFORM_EVENT = [
/** 平台支持的事件列表,EventPool内部定义,可自由拓展 **/
];
protected static $_instance;
protected $connection;
protected $channel;
protected $delay = false;
public function __construct($delay = 0)
{
$this->connection = new AMQPStreamConnection(HOST, PORT, USER, PASS);
$this->channel = $this->connection->channel();
$this->delay = $delay;
if($this->delay){
$args = new AMQPTable(['x-delayed-type' => AMQPExchangeType::FANOUT]);
$this->channel->exchange_declare(DELAY_EXCHANGE_NAME, 'x-delayed-message', false, true, false,false,false,$args);
$this->channel->queue_declare(DELAY_QUEUE_NAME, false, true, false, false, false,
new AMQPTable(['x-dead-letter-exchange' => 'delayed'])
);
$this->channel->queue_bind(DELAY_QUEUE_NAME, DELAY_EXCHANGE_NAME, DELAY_ROUTING_KEY);
}else{
$this->channel->exchange_declare(EXCHANGE_NAME, AMQPExchangeType::DIRECT, false, true, false);
$this->channel->queue_declare(QUEUE_NAME, false, true, false, false);
$this->channel->queue_bind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
}
}
public static function getInstance($delay = 0): BaseMQService
{
if (is_null(self::$_instance) || self::$_instance->delay != $delay){
self::$_instance = new static($delay);
}
return self::$_instance;
}
public function __destruct()
{
$this->shutdown();
}
public function shutdown() : void
{
$this->channel->close();
$this->connection->close();
if (request()->isCli()){
$this->consoleLog('closed');
}
}
//控制台打印日志,调试使用,write_queue_logs方法可去除可使用日志记录插入日志数据
protected function consoleLog($message = 'Waiting for message. To exit press CTRL+C ',$writeLog = false)
{
$message = date('Y-m-d H:i:s'). " [*]: " . $message;
echo $message.PHP_EOL;
if($writeLog) write_queue_logs($message);
}
}
事件池,统一调用业务代码,定义业务事件类型:
<?php
namespace app\common\library\rabbitMQ;
use app\api\model\AiJob;
use app\api\model\BatchProducingTask;
use app\api\model\ProducingTask;
use app\api\model\Resource;
use app\api\model\ShareTask;
use app\api\service\AccountService;
use app\api\service\AliCloudService;
use app\api\service\VideoService;
use app\common\library\rabbitMQ\publish\MessageQueue;
class EventPool
{
const EVENT_ERROR = 'error';
public static function dispatch($event,$messageData)
{
extract($messageData);
$token = $token ?? '';
/***
这里编写你的业务代码
***/
}
public static function handelErrorJob($event,$data)
{
$queue = new MessageQueue();
$queue->sendMessage($event,$data);
}
}
发布事件:
<?php
namespace app\common\library\rabbitMQ\publish;
use app\common\library\rabbitMQ\BaseMQService;
use app\common\library\rabbitMQ\MessageInterface;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
use think\Env;
use const app\common\library\rabbitMQ\DELAY_EXCHANGE_NAME;
use const app\common\library\rabbitMQ\DELAY_ROUTING_KEY;
use const app\common\library\rabbitMQ\EXCHANGE_NAME;
use const app\common\library\rabbitMQ\ROUTING_KEY;
class MessageQueue extends BaseMQService implements MessageInterface
{
/**
* @param string $event
* @param array $data
* @return bool|void
*/
public function sendMessage($event,$data = [],$delay = false)
{
if (!in_array($event,self::PLATFORM_EVENT) && !Env::get('app.debug')){
errException('event was not allowed for this server');
}
$data['counter'] = $data['counter'] ?? 1;
$messageBody = json_encode(compact('event','data'));
$message = new AMQPMessage($messageBody, array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT));
if ($this->delay){
$headers = new AMQPTable(['x-delay' => $this->delay * 1000]);
$message->set('application_headers',$headers);
$this->channel->basic_publish($message, DELAY_EXCHANGE_NAME, DELAY_ROUTING_KEY);
}else{
$this->channel->basic_publish($message, EXCHANGE_NAME, ROUTING_KEY);
}
return true;
}
}
消费者代码:
基类消费者:
<?php
namespace app\common\library\rabbitMQ\consumer;
use app\api\model\MqErrorLogs;
use app\common\library\rabbitMQ\BaseMQService;
use app\common\library\rabbitMQ\EventPool;
use PhpAmqpLib\Message\AMQPMessage;
class BaseConsumer extends BaseMQService
{
const CONSUMER_TAG = 'consumer';
const DELAYED_CONSUMER_TAG = 'delayed_consumer';
function processMessage(AMQPMessage $message)
{
$messageArr = json_decode($message->body,true);
// var_export($messageArr['data']);
// var_export(PHP_EOL);
$event = $messageArr['event'];
if (in_array($event,self::PLATFORM_EVENT)){
$data = $messageData = $messageArr['data'] ?? [];
if ($messageData['counter'] >= 5){
$data = json_encode($data);
MqErrorLogs::create(compact('event','data'));
$this->consoleLog("job was completed with error:{$message->body}",true);
$message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
}else{
$res = EventPool::dispatch($event,$messageData);
if ($res === EventPool::EVENT_ERROR){
$this->consoleLog("job error:{$message->body}",true);
$message->delivery_info['channel']->basic_reject($message->delivery_info['delivery_tag'],false);
$messageData['counter']++;
EventPool::handelErrorJob($event,$messageData);
}else{
if ($res){
$this->consoleLog("job was completed:{$message->body}",true);
$message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
}else{
$this->consoleLog("requeue the job:{$message->body}",true);
$message->nack(true);
}
}
}
}else{
$this->consoleLog("event not found:{$message->body}",true);
$message->delivery_info['channel']->basic_reject($message->delivery_info['delivery_tag'],false);
}
// Send a message with the string "quit" to cancel the consumer.
if ($message->body === 'quit') {
$message->delivery_info['channel']->basic_cancel($message->delivery_info['consumer_tag']);
}
}
}
延时消费者:
<?php
namespace app\common\library\rabbitMQ\consumer;
use const app\common\library\rabbitMQ\DELAY_QUEUE_NAME;
class DelayedConsumer extends BaseConsumer
{
public function start()
{
$this->channel->basic_qos('',1,'');
$this->channel->basic_consume(DELAY_QUEUE_NAME, self::DELAYED_CONSUMER_TAG, false, false, false, false, array($this, 'processMessage'));
echo " [*] Waiting for delayed messages. To exit press CTRL+C\n";
register_shutdown_function(array($this, 'shutdown'));
while (count($this->channel->callbacks)) {
$this->channel->wait();
}
$this->consoleLog();
}
}
实时消费者:
<?php
namespace app\common\library\rabbitMQ\consumer;
use const app\common\library\rabbitMQ\DELAY_QUEUE_NAME;
use const app\common\library\rabbitMQ\QUEUE_NAME;
class MessageConsumer extends BaseConsumer
{
public function start()
{
$processes = 5;
for ($i = 0; $i < $processes;$i++){
$this->channel->basic_qos('',1,'');
$this->channel->basic_consume(QUEUE_NAME, self::CONSUMER_TAG . $i, false, false, false, false, array($this, 'processMessage'));
}
echo " [*] Waiting for messages. To exit press CTRL+C\n";
register_shutdown_function(array($this, 'shutdown'));
while (count($this->channel->callbacks)) {
$this->channel->wait();
}
$this->consoleLog();
}
}
启动消费者:
按需分开执行,以下为使用demo
<?php
namespace app\api\controller;
use app\common\library\rabbitMQ\consumer\DelayedConsumer;
use app\common\library\rabbitMQ\consumer\MessageConsumer;
class Queue
{
public function index()
{
$consumer = new MessageConsumer();
$consumer->start();
echo "normal queue\n";
$consumer = new DelayedConsumer();
$consumer->start();
echo "delayed queue\n";
}
}
投递任务:
<?php
//投递延时任务
$q = MessageQueue::getInstance(10);
$q->sendMessage(EventPool::TEST_JOB,$data['data']);
//投递实时任务
$q = MessageQueue::getInstance();
$q->sendMessage(EventPool::TEST_JOB,$data['data']);