原创的amqp原生封装rabbitmq框架代码示例,一对多发送任务消息,开箱即用、也可以在php框架里面使用
可以兼容thinkphp、laravel、ci等任何框架
代码使用与学习跳转github地址、打开链接有使用说明!!
https://github.com/linsonggao/php-rabbitmq-bus
1.核心代码、监听消息、发送消息
<?php
namespace PhpRabbitMq\Lib\MessageBus\base;
use PhpRabbitMq\Lib\Instance;
use PhpRabbitMq\Lib\MessageBus\BusPassenger;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Connection\AMQPStreamConnection;
class AmqpBus extends AbstractBus implements BusInterface
{
use Instance;
/**
* 启动
* @return \think\Response
*/
public function start($bus_name)
{
//dump("ensureBus");
$param = config('rabbitmq.AMQP');
//$amqpDetail = config('rabbitmq.direct_queue');
$connection = new AMQPStreamConnection(
$param['host'],
$param['port'],
$param['login'],
$param['password'],
$param['vhost'],
);
//dump("ensureBus");
/*
* 创建通道
*/
$channel = $connection->channel();
/*
* 设置消费者(Consumer)客户端同时只处理一条队列
* 这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个消费者(Consumer),
* 直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的消费者(Consumer)。
*/
//dump("ensureBus");
$channel->basic_qos(0, 1, false);
/*
* 同样是创建路由和队列,以及绑定路由队列,注意要跟publisher的一致
* 这里其实可以不用,但是为了防止队列没有被创建所以做的容错处理
*/
var_dump("ensureBus");
$this->ensureBus($channel, $bus_name);
//$channel->queue_bind($amqpDetail['queue_name'], $amqpDetail['exchange_name'],$amqpDetail['route_key']);
/*
queue: 从哪里获取消息的队列
consumer_tag: 消费者标识符,用于区分多个客户端
no_local: 不接收此使用者发布的消息
no_ack: 设置为true,则使用者将使用自动确认模式。详情请参见.
自动ACK:消息一旦被接收,消费者自动发送ACK
手动ACK:消息接收后,不会发送ACK,需要手动调用
exclusive:是否排他,即这个队列只能由一个消费者消费。适用于任务不允许进行并发处理的情况下
nowait: 不返回执行结果,但是如果排他开启的话,则必须需要等待结果的,如果两个一起开就会报错
callback: :回调逻辑处理函数,PHP回调 array($this, 'process_message') 调用本对象的process_message方法
*/
$noAck = $options['no_ack'] ?? true;
//消费队列~
$busInfo = BusPassenger::instance()->getBusInfo($bus_name);
//var_dump($busInfo);
foreach ($busInfo['passengers'] as $queue_name) {
//$this->queue_name = $queue_name;
//var_dump($queue_name);
$channel->basic_consume($queue_name, '', false, $noAck, false, false, function (AMQPMessage $msg) use ($queue_name) {
$messageData = json_decode($msg->body, true);
//执行对应的class即可
//todo.
//var_dump($queue_name);
$handler = BusPassenger::Instance()->getWork()[$queue_name]['class'];
try {
/**@var AbstractWork $handler**/
$handler::Instance($handler)->handler($messageData);
} catch (\Throwable $e) {
var_dump("消息消费错误信息:".$e->getMessage());
};
});
}
register_shutdown_function(array($this, 'shutdown'), $channel, $connection);
// AMQP 队列的轮询时长
$waitSeconds = $options['wait_seconds'] ?? 15;
try {
$channel->consume($waitSeconds);
} catch (AMQPConnectionClosedException $e) {
}
}
public function publish($data)
{
$param = config('rabbitmq.AMQP');
$connection = new AMQPStreamConnection(
$param['host'],
$param['port'],
$param['login'],
$param['password'],
$param['vhost']
);
$channel = $connection->channel();
/*
* 创建交换机(Exchange)
* name: vckai_exchange// 交换机名称
* type: direct // 交换机类型,分别为direct/fanout/topic,参考另外文章的Exchange Type说明。
* passive: false // 如果设置true存在则返回OK,否则就报错。设置false存在返回OK,不存在则自动创建
* durable: false // 是否持久化,设置false是存放到内存中的,RabbitMQ重启后会丢失
* auto_delete: false // 是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除
*/
$channel->exchange_declare($data['bus_name'], "fanout", false, true, false);
/*
$messageBody:消息体
content_type:消息的类型 可以不指定
delivery_mode:消息持久化最关键的参数
AMQPMessage::DELIVERY_MODE_NON_PERSISTENT = 1; 不持久化
AMQPMessage::DELIVERY_MODE_PERSISTENT = 2; 持久化
*/
//将要发送数据变为json字符串
$messageBody = json_encode($data);
/*
* 创建AMQP消息类型
* $messageBody:消息体
* delivery_mode 消息是否持久化
* AMQPMessage::DELIVERY_MODE_NON_PERSISTENT = 1; 不持久化
* AMQPMessage::DELIVERY_MODE_PERSISTENT = 2; 持久化
*/
$message = new AMQPMessage($messageBody, array('content_type' => 'text/plain', 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT));
/*
* 发送消息
* msg // AMQP消息内容
* exchange // 交换机名称
* routing key // 路由键名称
*/
$channel->basic_publish($message, $data['bus_name'], $data['bus_name']);
$channel->close();
$connection->close();
//echo "ok";
}
}
2.交换机绑定关系相关代码
<?php
namespace PhpRabbitMq\Lib\MessageBus\base;
use PhpRabbitMq\Lib\MessageBus\BusPassenger;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Wire\AMQPTable;
abstract class AbstractBus
{
private const DEFAULT_EXCHANGE = 'default.direct';
private const DEFAULT_DELAYED_MESSAGE_EXCHANGE = 'default.dmx.direct';
private const DEAD_LETTER_EXCHANGE = 'dlx.direct';
/**
* 消费端 消费端需要保持运行状态实现方式
**/
function shutdown(AMQPChannel $channel, $connection)
{
$channel->close();
$connection->close();
}
/**
* @param $channel
* @param $name bus_name,交换机
* @return void
*/
function bindBusPassengers(AMQPChannel $channel, $name, callable $bindDirectBusQueue, callable $bindDelayBusQueue)
{
$businfo = BusPassenger::instance()->getBusInfo($name);
$channel->queue_declare($name, false, true, false, false);
if (is_array($businfo['delay_seconds_arr']) && count($businfo['delay_seconds_arr']) > 0 ) {
foreach ($businfo['passengers'] as $passenger) {
if (isset($businfo['delay_arr'][$passenger]) && $businfo['delay_arr'][$passenger]) {
$bindDelayBusQueue($businfo,$channel, $passenger, $name);
} else {
$bindDirectBusQueue($businfo,$channel, $name);
}
}
} else {
$bindDirectBusQueue($businfo,$channel, $name);
}
}
protected static function array2Table(array $array): AMQPTable
{
$table = new AMQPTable();
foreach ($array as $key => $value) {
$table->set($key, $value);
}
return $table;
}
protected function ensureBus(AMQPChannel $channel, $bus_name)
{
// 默认交换机
$channel->exchange_declare(self::DEFAULT_EXCHANGE, "direct", false, true, false);
// 默认延迟消息交换机
// 如果 send 指定了延迟时间,使用此交换机
$channel->exchange_declare(self::DEFAULT_DELAYED_MESSAGE_EXCHANGE, "direct", false, true, false);
// 死信交换机
$channel->exchange_declare(self::DEAD_LETTER_EXCHANGE, "direct", false, true, false);
// 事件交换机
// 事件交换机都是 fanout 类型的,消息被分发复制到每个绑定的队列
$channel->exchange_declare($bus_name, 'fanout', false, true, false);
$bindDirectBusQueue = function ($businfo,AMQPChannel $channel, $bus_name) {
// 不需要延迟(还包含仅动态延迟的队列)
// 直接把队列绑定到默认交换机和事件交换机
// routing key 等于原始队列名称
$channel->queue_bind($bus_name, self::DEFAULT_EXCHANGE, $bus_name);
//dump(" bound to " . self::DEFAULT_EXCHANGE);
foreach ($businfo['passengers'] as $passenger) {
$channel->queue_declare($passenger, false, true, false, false);
$channel->queue_bind($passenger, $bus_name, $passenger);
}
};
$bindDelayBusQueue = function ($businfo,AMQPChannel $channel, $passenger, $bus_name) {
//Message->Exchange--> Delayed Queue ->默认交换机(dlx.direct)->Queue->consumer hander
$delayedName = $passenger . '.delayed.' . $businfo['delay_seconds_arr'][$passenger];
// 因为有动态绑定的问题,需要删除之前的延迟队列
$channel->queue_declare($delayedName, false, true, false, false, false, self::array2Table([
'x-message-ttl' => 1000 * $businfo['delay_seconds_arr'][$passenger],
'x-dead-letter-exchange' => self::DEAD_LETTER_EXCHANGE,
'x-dead-letter-routing-key' => $bus_name,
]));
// 把延迟队列绑定到事件交换机
// routing key 等于原始队列名称
$channel->queue_bind($delayedName, $bus_name, $bus_name);
// 实际消费队列绑定到死信交换机
$channel->queue_declare($passenger, false, true, false, false);
$channel->queue_bind($passenger, self::DEAD_LETTER_EXCHANGE, $bus_name);
};
$this->bindBusPassengers($channel, $bus_name,$bindDirectBusQueue,$bindDelayBusQueue);
}
}
3.rabbit交换机规则class
<?php
namespace PhpRabbitMq\Lib\MessageBus;
use PhpRabbitMq\Lib\Instance;
use PhpRabbitMq\Lib\MessageBus\works\OrderCancel;
/**
* rabbitMq的交换机队列规则class
*/
class BusPassenger
{
use Instance;
//交换机
private array $bus;
//交换机的配置信息以及绑定的队列
private array $busInfo;
//队列对应的class对用works目录下的class
private array $works = [];
public function __construct()
{
$this->registerBus();
$this->registerWork();
//不存在的handler会自动绑定
//自动模式会遵循大驼峰命名
//比如member-login-cpa-callback对应MemberLoginCpaCallback
$this->autoRegisterHanders();
}
/**
* 注册交换机跟队列
* 1对多绑定,1个事件发送到多个队列
* @return void
*/
private function registerBus()
{
//用户注册
$this->defineBus(BusEnum::MEMBER_LOGIN);
$this->defineBusInfo(BusEnum::MEMBER_LOGIN,BusEnum::MEMBER_LOGIN_CPA_CALL_BACK);
$this->defineBusInfo(BusEnum::MEMBER_LOGIN,BusEnum::MEMBER_LOGIN_CPA_CALL_BACK_DATA);
}
/**
* 手动绑定事件绑定类
* @return void
*/
private function registerWork()
{
// Core
$this->registeHandler(BusEnum::ORDER_CANCEL, OrderCancel::class);
}
/**
* 自动注册器,默认处理规则
* @return void
*/
private function autoRegisterHanders()
{
foreach ($this->getBus() as $bus_name){
$busInfo = $this->getBusInfo($bus_name);
foreach ($busInfo['passengers'] as $passenger) {
if(!isset($this->works[$passenger])) {
$psArr = explode("-",$passenger);
$class = '';
foreach ($psArr as $psStr)
{
$class .= ucfirst($psStr);
}
$this->registeHandler($passenger,'PhpRabbitMq\\Lib\\MessageBus\\works\\'.$class);
}
}
}
}
/**
* 注册处理器
* @param $command
* @param $handlerClass
* @return void
*/
public function registeHandler($command, $handlerClass)
{
$this->works[$command] = [
'class' => $handlerClass,
];
}
/**
* 获取所有works
* @return array
*/
public function getWork()
{
return $this->works;
}
/**
* 获取单个bus详情
* @param $bus_name
* @return mixed
*/
public function getBusInfo($bus_name)
{
if(!isset($this->busInfo[$bus_name])) {
dd("不存在的bus:".$bus_name);
}
return $this->busInfo[$bus_name];
}
/**
* 获取所有bus
* @return \Generator
*/
public function getBus(): \Generator
{
foreach ($this->bus as $bus => $entry) {
yield $bus;
}
}
/**
* 注册一个命令-
*
* @param string $command
*
* @return void
*/
public function defineBus(string $command)
{
$this->bus[$command] = [
'delay' => false,
'delay_dynamic' => false,
'delay_seconds' => 0,
];
}
/**
* 注册命令和事件绑定关系-
*
* @param string $command
* @param string $queue
*
* @return void
*/
public function defineBusInfo(string $command, string $queue)
{
$queues = $this->busInfo[$command]["passengers"] ??[];
$queues[]= $queue;
$this->busInfo[$command] = [
'delay' => false,
'delay_dynamic' => false,
'delay_seconds' => 0,
'delay_seconds_arr' => [],
'passengers' => $queues
];
}
/**
* 注册一个有延迟需求的命令,参数 $delay_second 表示默认延迟时间。
*
* @param string $command
* @param int $delay_seconds
*
* @return void
*/
public function defineDelayedBusInfo(string $command, string $queue,int $delay_seconds = 0)
{
$queues = $this->busInfo[$command]["passenger"] ??[];
$queues[]= $queue;
$delay_seconds_arr = $this->busInfo[$command]["delay_seconds_arr"] ??[];
$delay_seconds_arr[$queue]= $delay_seconds;
$delay_arr = $this->busInfo[$command]["delay_arr"] ??[];
$delay_arr[$queue]= true;
$this->busInfo[$command] = [
'delay' => true,
'delay_dynamic' => false,
'delay_arr' => $delay_arr,
'delay_seconds_arr' => $delay_seconds_arr,
'passengers' => $queues
];
}
}
4.交换机队列枚举类
<?php
namespace PhpRabbitMq\Lib\MessageBus;
final class BusEnum
{
public const MEMBER_LOGIN="member-login"; //用户登录bus
public const MEMBER_LOGIN_CPA_CALL_BACK="member-login-cpa-call-back"; //用户登录bus:用户登录
public const MEMBER_LOGIN_CPA_CALL_BACK_DATA="member-login-call-back-data"; //用户登录bus:用户登录记录
}
5.监听代码
<?php
require "./vendor/autoload.php";
use PhpRabbitMq\Lib\MessageBus\base\AmqpBus;
ini_set ("memory_limit","-1");
//* 1.用户登录事件监听 php think message_bus --bus member-login
//*
//* 2.订单支付事件监听 php think message_bus --bus order-paid
//*
//* 3.订单创建事件监听 php think message_bus --bus order-create
AmqpBus::instance()->start("member-login");
6.测试发送消息demo代码
<?php
require "./vendor/autoload.php";
use PhpRabbitMq\Lib\MessageBus\base\AmqpBus;
$data = [
"bus_name"=>"member-login",
"test"=>"测试消息发送成功!!!"
];
var_dump("生产消息");
AmqpBus::instance()->publish($data);
测试成功发送消息、监听消息