本文所使用的是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;
}
}
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
文章底部有相关阅读