thinkphp6 && RabbitMq

300 阅读6分钟

一、RabbitMq安装

RabbitMQ 是 Erlang 语言写的,首先我们需要安装Erlang 环境
RabbitMQ 不同版本也对应着不同的Erlang 版本,进入官网:RabbitMQ

查看对照表:

一、安装erlang

1、进入package Cloud,这里会很详细的解释,安装过程中需要哪些和生成一些文件。


2、在命令行中执行脚本

curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash

3、安装

sudo yum install erlang-21.3.8.16-1.el7.x86_64

4、测试

二、安装rabbitmq

1、进入package Cloud,这里会很详细的解释,安装过程中需要哪些和生成一些文件。

2、在命令行中执行脚本

curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash

3、安装

sudo yum install rabbitmq-server-3.8.4-1.el7.noarch

4、启动

#启动服务
service rabbitmq-server start
#查看状态
service rabbitmq-server status
#设置开机自启动
chkconfig rabbitmq-server on
#查找安装目录
whereis rabbitmq

4、开启web管理界面,增加用户

#开启web管理界面
rabbitmq-plugins enable rabbitmq_management
#切换目录
cd /usr/lib/rabbitmq/bin
#添加用户名和密码
rabbitmqctl add_user zq 123456
#将用户admin设置为管理员
rabbitmqctl set_user_tags zq administrator

5、测试
在浏览器访问http://212.64.29.192:15672,登录 Web 管理界面
输入上面添加的用户名/密码:zq/123456

二、PHP扩展

1、安装依赖库rabbitmq-c

.安装amqp-c


wget https://github.com/alanxz/rabbitmq-c/releases/download/v0.8.0/rabbitmq-c-0.8.0.tar.gz

tar -zxvf rabbitmq-c-0.8.0.tar.gz

cd rabbitmq-c-0.8.0

./configure --prefix=/usr/local/rabbitmq-c

make && make install

4.安装amqp拓展

安装依赖


yum install epel-release

yum install autoconf gcc gcc-c++ librabbitmq librabbitmq-devel

wget https://pecl.php.net/get/amqp-1.9.4.tgz

tar xf amqp-1.9.4.tgz

cd amqp-1.9.4

/www/server/php/73/bin/phpize

./configure --with-php-config=/www/server/php/73/bin/php-config --with-amqp --with-librabbitmq-dir=/usr/local/rabbitmq-c

make && make install

修改php.ini配置增加拓展amqp.so 并重启php-fpm

5.一些命令

增加用户 rabbitmqctl add_user 'test' 'test'

列出用户 rabbitmqctl list_users

授权用户 rabbitmqctl  set_permissions -p / test '.' '.' '.*'

2、重启php-fpm

#重启php-fpm
service php-fpm restart
#查看是否安装了amqp扩展
php -m

3、安装php-amqplib扩展

进入tp6项目的根目录,安装扩展

 composer require php-amqplib/php-amqplib

安装完成后会在vendor增加一个php-amqplib目录

2、在config文件夹下添加rabbitmq.php配置文件

<?php
// 示例配置文件
return [
 
 # 连接信息
 'AMQP' => [
 'host' => '192.168.1.130',   //连接rabbitmq,此为安装rabbitmq服务器
 'port'=>'5672',
 'login'=>'guest',
 'password'=>'guest',
 'vhost'=>'/'
 ],
 # 邮件队列
 'direct_queue' => [
 'exchange_name' => 'direct_exchange',
 'exchange_type'=>'direct',#直连模式
 'queue_name' => 'direct_queue',
 'route_key' => 'direct_roteking',
 'consumer_tag' => 'direct'
 ]
];

3、编写生产者代码

<?php
namespace app\controller;

use app\BaseController;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use think\facade\Log;

class MqProducer
{
    public static function pushMessage($data)
    {
        $param = config('rabbitmq.AMQP');
        $amqpDetail = config('rabbitmq.direct_queue');
        $connection = new AMQPStreamConnection(
            $param['host'],
            $param['port'],
            $param['login'],
            $param['password'],
            $param['vhost']
        );
        $channel = $connection->channel();
        /*
         * 创建队列(Queue)
         * name: hello         // 队列名称
         * passive: false      // 如果设置true存在则返回OK,否则就报错。设置false存在返回OK,不存在则自动创建
         * durable: true       // 是否持久化,设置false是存放到内存中RabbitMQ重启后会丢失,
         *                        设置true则代表是一个持久的队列,服务重启之后也会存在,因为服务会把持久化的Queue存放在硬盘上,当服务重启的时候,会重新加载之前被持久化的Queue
         * exclusive: false    // 是否排他,指定该选项为true则队列只对当前连接有效,连接断开后自动删除
         *  auto_delete: false // 是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除
         */
        $channel->queue_declare($amqpDetail['queue_name'], false, true, false, false);

        /*
         * 创建交换机(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($amqpDetail['exchange_name'], $amqpDetail['exchange_type'], false, true, false);

        /*
         * 绑定队列和交换机
         * @param string $queue 队列名称
         * @param string $exchange  交换器名称
         * @param string $routing_key   路由key
         * @param bool $nowait
         * @param array $arguments
         * @param int|null $ticket
         * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
         * @return mixed|null
         */
        $channel->queue_bind($amqpDetail['queue_name'], $amqpDetail['exchange_name'], $amqpDetail['route_key']);

        /*
             $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, $amqpDetail['exchange_name'],$amqpDetail['route_key']);
        $channel->close();
        $connection->close();
        echo  "ok";
    }
}

4、消费者代码

<?php
namespace app\controller;

use app\BaseController;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use think\Controller;
use think\facade\Log;

class MqConsumer
{
    /**
     * 消费端 消费端需要保持运行状态实现方式
     * 1 linux上写定时任务每隔5分钟运行下该脚本,保证访问服务器的ip比较平缓,不至于崩溃
     * 2 nohup php index.php index/Message_Consume/start &  用nohup命令后台运行该脚本
     * 3
     **/
    function shutdown($channel, $connection)
    {
        $channel->close();
        $connection->close();
        Log::write("closed",3);
    }

    //消息处理
    function process_message($message)
    {

        //休眠两秒
        //sleep(2);
        echo  $message->body."\n";
        //自定义日志为rabbitmq-consumer
        Log::write($message->body,'rabbitmq-consumer');
        //[2021-01-14T16:14:17+08:00][rabbitmq-consumer] {"time":1610612057,"order":85}

        //手动发送ack
        $message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
        // 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']);
        }
    }

    /**
     * 启动
     * @return \think\Response
     */
    public function start()
    {
        $param = config('rabbitmq.AMQP');
        $amqpDetail = config('rabbitmq.email_queue');
        $connection = new AMQPStreamConnection(
            $param['host'],
            $param['port'],
            $param['login'],
            $param['password'],
            $param['vhost']
        );

        /*
         * 创建通道
         */
        $channel = $connection->channel();
        /*
         * 设置消费者(Consumer)客户端同时只处理一条队列
         * 这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个消费者(Consumer),
         * 直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的消费者(Consumer)。
         */
        $channel->basic_qos(0, 1, false);
        /*
         * 同样是创建路由和队列,以及绑定路由队列,注意要跟publisher的一致
         * 这里其实可以不用,但是为了防止队列没有被创建所以做的容错处理
         */
        $channel->queue_declare($amqpDetail['queue_name'], false, true, false, false);

        $channel->exchange_declare($amqpDetail['exchange_name'], $amqpDetail['exchange_type'], false, true, false);

        $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方法
        */
        $channel->basic_consume($amqpDetail['queue_name'], $amqpDetail['consumer_tag'], false, false, false, false, array($this, 'process_message'));

        register_shutdown_function(array($this, 'shutdown'), $channel, $connection);
        // 阻塞队列监听事件
        while (count($channel->callbacks)) {
            $channel->wait();
        }
    }
}

5、编写测试代码Index.php

<?php
namespace app\controller;

use app\BaseController;
use app\controller\MqProducer;

class Index
{
    public function index()
    {
        echo "tp6";die;
    }

    public function hello($name = 'ThinkPHP6')
    {
        return 'hello,' . $name;
    }

    public function send()
    {

//        for($i=0; $i<5; $i++){
            $consumer = new MqProducer();//生产者
            $data = [
              'time'=>time(),
              'order'=> rand(1, 100),
            ];
            $consumer->pushMessage($data);

//            sleep(1);
//        }
    }
}

 

6、项目更目录使用php think生成指令

1)进入到项目目录:php -v 查看是否配置环境,否则配置php系统环境

Windows7下的php环境配置教程_php技巧_脚本之家​www.jb51.net/article/61507.htm

2)执行:php think make:command Comer Comer 会在command目录下生成Comer .php文件

3)在config/console.php添加指令

<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
    // 指令定义
    'commands' => [
        // consumer是app\Command\Consumer文件中自定义命令行的名字不能使用
        'comer' => 'app\command\Comer',
    ],
];

4.修改指令文件

<?php
declare (strict_types = 1);

namespace app\command;

use app\controller\MqConsumer;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class Comer extends Command
{
    protected function configure()
    {
        // 指令配置
        $this->setName('Comer')
            ->setDescription('the Comer command');
    }

    protected function execute(Input $input, Output $output)
    {
        // 指令输出
//        $output->writeln('Comer');
        $consumer = new MqConsumer(); //消费者
        $consumer->start();  //启动
    }
}

7、测试

注释:我的域名为:www.tp6.net

步骤:

(1)启动监听指令(消费者消费)
(2)生产者发送消息
(3)查看rabbitmq管理界面是否有消息
(4)查看消费者已消费消息

1)在项目根目录指令监听 php think comer

2)发送消息

http://www.tp6.net/index.php/index/send

3)rabbitmq管理界面

4)查看消费者已消费消息

8、使用supervisor监控消费者实时在线

安装supervisor:

CentOS7安装Supervisor3.1.4 - 此生不换Yang - 博客园​www.cnblogs.com/yjlch1016/p/10162918.html

监听守护【rabbitmq消费者】配置文件如下,然后保存退出

[user@localhost tp6.com]cd/etc/supervisord.d/[user@localhostsupervisord.d] cd /etc/supervisord.d/ [user@localhost supervisord.d] sudo vim tp_amqp.ini

[program:tp_amqp]
directory = /www/wwwroot/www.tp6.com                           ;启动目录
command = /www/server/php/72/bin/php think comer       ;启动命令
autostart = true                ;在supervisord启动的时候也启动
startsecs = 5                   ;启动5秒后没有异常退出,就当作已经正常启动了
autorestart = true              ;程序异常退出后自动重启
startretries = 3                ;启动失败自动重试次数,默认是3
user = root                     ;哪个用户启动
redirect_stderr = true          ;把stderr重定向到stdout,默认false
stdout_logfile_maxbytes = 20MB  ;stdout日志文件大小,默认50MB
stdout_logfile_backups = 20     ;stdout日志文件备份数
stdout_logfile = /usr/log/tp_amqp.log
;stdout日志文件,需要手动创建/usr/log/tp_amqp.log

4、启动守护进程

更新配置文件:supervisorctl update
启动进程:sudo supervisorctl start tp_amqp

通过ps查看守护的命令:

[user@localhost www.tp6.com]$ sudo supervisorctl status tp_amqp
tp_amqp                          RUNNING   pid 16595, uptime 0:01:27
[user@localhost www.tp6.com]$ ps afx | grep php
 2364 ?        Ss     0:00 php-fpm: master process (/www/server/php/72/etc/php-fpm.conf)
 2365 ?        S      0:00  _ php-fpm: pool www
 2366 ?        S      0:00  _ php-fpm: pool www
 2367 ?        S      0:00  _ php-fpm: pool www
 2368 ?        S      0:00  _ php-fpm: pool www
 2369 ?        S      0:00  _ php-fpm: pool www
14478 ?        S      0:00  _ php-fpm: pool www
16746 pts/1    S+     0:00      _ grep --color=auto php
16595 ?        S      0:00  _ /www/server/php/72/bin/php think comer
[user@localhost www.tp6.com]$ sudo kill 16595
[user@localhost www.tp6.com]$ ps afx | grep php
 2364 ?        Ss     0:00 php-fpm: master process (/www/server/php/72/etc/php-fpm.conf)
 2365 ?        S      0:00  _ php-fpm: pool www
 2366 ?        S      0:00  _ php-fpm: pool www
 2367 ?        S      0:00  _ php-fpm: pool www
 2368 ?        S      0:00  _ php-fpm: pool www
 2369 ?        S      0:00  _ php-fpm: pool www
14478 ?        S      0:00  _ php-fpm: pool www
16823 pts/1    S+     0:00      _ grep --color=auto php
16821 ?        S      0:00  _ /www/server/php/72/bin/php think comer
[user@localhost www.tp6.com]$ 

简单描述一下RabbitMQ中的几个关键的概念:

Broker:可以简单的理解为安装了RabbitMQ服务的这台机器就可以称为中间人

Exchange:交换机,消息经由它,通过路由键来判断并决定把消息投递给哪个队列,它类似于一个路由器的角色

Queue:队列,最终将消息投递到队列中,由消费端监听队列进行消费

Binding:绑定关系,需要给交换机绑定队列,绑定时需要给一个路由键

Routingkey:路由键,交换机和队列进行绑定时,需要指定路由键或通配符路由键。

交换机根据路由键来决定消息投递到哪个或哪些队列

大致流程:使用RabbitMQ前,首先需要根据业务来创建交换机和队列,创建完成后需要给交换机绑定队列(交换机和队列可以是多对多的关系),绑定队列时要指定具体的路由键或者通配符路由键当生产者发送一条消息的时候,需要指定交换机和路由键,消息到达Broker后先转给刚才指定的交换机,交换机再根据路由键来决定把消息投递给与自己绑定的哪一个或哪一些队列,最后再由消费端来监听这些队列,消费处理对应的消息

最新版本的RabbitMQ有四种交换机类型,分别是:Direct exchange、Fanout exchange、Topic exchange、Headers exchange

1、Direct exchange---直接类型交换机

要求消息带的路由键和绑定的路由键完全匹配,这是一个完整的匹配。

2、Fanout Exchange---扇出类型交换机

只需要简单的将队列绑定到该类型交换机上,该类型的交换机绑定队列时可以不指定路由键(Routingkey)

当消息发送给该交换机后,它会将消息投递给与该交换机绑定的所有队列

很像广播,每台子网内的机器都会获得一份消息,Fanout交换机转发消息是最快的

3、Topic Exchange---主题类型交换机

将路由键和某模式进行匹配。此时队列需要绑定某一个模式上。符号#匹配0个或多个单词,符号 *匹配一个单词。

4、Headers Exchanges

不知道