Docker+TP5+RabbitMQ+入消息队列+自动消费队列

该博客记录了RabbitMQ的安装、使用composer安装php - amqplib以及Tp5的实现过程,同时提供了相关学习地址,如cnblogs、csdn、segmentfault等平台的文章链接,不过仅作学习记录,不能直接运行。

注意:仅仅记录学习,不能直接运行,有任何问题请留言。

1. 安装RabbitMQ

拉取镜像

docker pull rabbitmq:3.7.7-management
docker run -d --name rabbitmq3.7.7 -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq --hostname myRabbit -e RABBITMQ_DEFAULT_VHOST=my_vhost  -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin df80af9ca0c9

http://ip:15672

10306662-51f9245a643a679e.png
image.png

2. composer安装php-amqplib

composer require php-amqplib/php-amqplib

3.Tp5 实现

再次封装php-amqplib

<?php
/**
 * User: yuzhao
 * Description: RabbitMq 工具
 */
namespace app\common\tool;
use app\common\config\SelfConfig;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class RabbitMQTool {

    /**
     * User: yuzhao
     * @var
     * Description:
     */
    private $channel;

    private $mqConf;

    /**
     * RabbitMQTool constructor.
     * @param $mqName
     */
    public function __construct($mqName)
    {
        // 获取rabbitmq所有配置
        $rabbitMqConf = SelfConfig::getConfig('Source.rabbit_mq');
        if (!isset($rabbitMqConf['rabbit_mq_queue'])) {
            die('没有定义Source.rabbit_mq');
        }
        //建立生产者与mq之间的连接
        $this->conn = new AMQPStreamConnection(
            $rabbitMqConf['host'], $rabbitMqConf['port'], $rabbitMqConf['user'], $rabbitMqConf['pwd'], $rabbitMqConf['vhost']
        );
        $channal = $this->conn->channel();
        if (!isset($rabbitMqConf['rabbit_mq_queue'][$mqName])) {
            die('没有定义'.$mqName);
        }
        // 获取具体mq信息
        $mqConf = $rabbitMqConf['rabbit_mq_queue'][$mqName];
        $this->mqConf = $mqConf;
        // 声明初始化交换机
        $channal->exchange_declare($mqConf['exchange_name'], 'direct', false, true, false);
        // 声明初始化一条队列
        $channal->queue_declare($mqConf['queue_name'], false, true, false, false);
        // 交换机队列绑定
        $channal->queue_bind($mqConf['queue_name'], $mqConf['exchange_name']);
        $this->channel = $channal;
    }

    /**
     * User: yuzhao
     * @param $mqName
     * @return RabbitMQTool
     * Description: 返回当前实例
     */
    public static function instance($mqName) {
        return new RabbitMQTool($mqName);
    }

    /**
     * User: yuzhao
     * @param $data
     * Description: 写mq
     * @return bool
     */
    public function wMq($data) {
        try {
            $data = json_encode($data, JSON_UNESCAPED_UNICODE);
            $msg = new AMQPMessage($data, ['content_type' => 'text/plain', 'delivery_mode' => 2]);
            $this->channel->basic_publish($msg, $this->mqConf['exchange_name']);
        } catch (\Throwable $e) {
            $this->closeConn();
            return false;
        }
        $this->closeConn();
        return true;
    }

    /**
     * User: yuzhao
     * @param int $num
     * @return array
     * Description:
     * @throws \ErrorException
     */
    public function rMq($num=1) {
        $rData = [];
        $callBack = function ($msg) use (&$rData){
            $rData[] = json_decode($msg->body, true);
        };
        for ($i=0;$i<$num;$i++) {
            $this->channel->basic_consume($this->mqConf['queue_name'], '', false, true, false, false, $callBack);
        }
        $this->channel->wait();
        $this->closeConn();
        return $rData;
    }

    /**
     * User: yuzhao
     * Description: 关闭连接
     */
    public function closeConn() {
        $this->channel->close();
        $this->conn->close();
    }

}

入队列

<?php
/**
 * User: yuzhao
 * Description:
 */
namespace app\test\controller;

use app\common\tool\RabbitMQTool;
use think\Controller;

class TestController extends Controller {
    public function test() {
        RabbitMQTool::instance('test')->wMq(['name'=>'123']);
    }
}

启动消费队列

<?php
/**
 * User: yuzhao
 * Description: 启动MQ,php xxx/public/index.php /daemon/start_Mq/main 队列别名 进程数 -d(守护进程) | -s (杀死进程)
 */

namespace app\daemon\controller;
use app\common\config\SelfConfig;
use app\common\tool\RabbitMQTool;

class StartMqController {

    private $dealPath = null;

    private $childsPid = array();

    /**
     * StartRabbitMQ constructor.
     */
    public function __construct()
    {
        // 脚本路径
        $this->dealPath = str_replace('/','\\',"/app/daemon/deal/");
    }

    /**
     * User: yuzhao
     * Description: 返回当前实例
     */
    public static function instance() {
        return new StartMqController();
    }

    /**
     * User: yuzhao
     * Description: 主要处理流程
     * @throws \ErrorException
     */
    public function main() {
        global $argv;
        // 扩展参数
        if (isset($argv[3])) {
            switch ($argv[3]) {
                case '-d': // 守护进程启动
                    $this->daemonStart();
                break;
                case '-s': // 杀死进程
                    $this->killEasyExport($argv[2]);die();
                break;
            }
        }
        // 判断参数
        if (count($argv) < 2) {
            die('缺少参数');
        }
        // 获取配置信息
        $rabbitMqConf = SelfConfig::getConfig('Source.rabbit_mq');
        if (!isset( $rabbitMqConf['rabbit_mq_queue'][$argv[2]])) {
            die('没有配置:'.$argv[2]);
        }
        // 获取mq配置
        $mqConf = $rabbitMqConf['rabbit_mq_queue'][$argv[2]];
        // 实例化处理脚本
        $dealClass = $this->dealPath.$mqConf['consumer'];
        $dealObj = new $dealClass;
        $processNum = 1;
        if (isset($mqConf['process_num']) || !is_numeric($mqConf['process_num']) || $mqConf['process_num'] < 1 || $mqConf['process_num'] >10 ) {
            $processNum = $mqConf['process_num'];
        }
        if (!isset($mqConf['deal_num']) || !is_numeric($mqConf['deal_num'])) {
            die('处理条数设置有误');
        }
        // fork进程
        for ($i=0; $i<$processNum; $i++) {
            $pid = pcntl_fork();
            if( $pid < 0 ){
                exit();
            } else if( 0 == $pid ) {
                $this->downMqData($dealObj, $argv, $mqConf);
                exit();
            } else if( $pid > 0 ) {
                $this->childsPid[] = $pid;
            }
        }
        while( true ){
            sleep(1);
        }
    }

    /**
     * User: yuzhao
     * @param $dealObj
     * @param $argv
     * @param $mqConf
     * @throws \ErrorException
     * Description:
     */
    private function downMqData($dealObj, $argv, $mqConf) {
        while (true) {
            // 下载数据
            $mqData = RabbitMQTool::instance($argv[2])->rMq($mqConf['deal_num']);
            $dealObj->deal($mqData);
            sleep(1);
        }
    }

    private function killEasyExport($startFile) {
        exec("ps aux | grep $startFile | grep -v grep | awk '{print $2}'", $info);
        if (count($info) <= 1) {
            echo "not run\n";
        } else {
            echo "[$startFile] stop success";
            exec("ps aux | grep $startFile | grep -v grep | awk '{print $2}' |xargs kill -SIGINT", $info);
        }
    }

    /**
     * User: yuzhao
     * Description: 守护进程模式启动
     */
    private function daemonStart() {
        // 守护进程需要pcntl扩展支持
        if (!function_exists('pcntl_fork'))
        {
            exit('Daemonize needs pcntl, the pcntl extension was not found');
        }
        umask( 0 );
        $pid = pcntl_fork();
        if( $pid < 0 ){
            exit('fork error.');
        } else if( $pid > 0 ) {
            exit();
        }
        if( !posix_setsid() ){
            exit('setsid error.');
        }
        $pid = pcntl_fork();
        if( $pid  < 0 ){
            exit('fork error');
        } else if( $pid > 0 ) {
            // 主进程退出
            exit;
        }
        // 子进程继续,实现daemon化
    }

}

自定义配置文件

<?php
/**
 * User: yuzhao
 * Description:
 */

return [
   
    'rabbit_mq' => [
        'host' => ip,
        'port' => 5672,
        'user' => 'root',
        'pwd' => 'xxx',
        'vhost' => 'my_vhost',
        'rabbit_mq_queue' => [
            'test' => [
                'exchange_name' => 'ex_test', // 交换机名称
                'queue_name' => 'que_test', // 队列名称
                'process_num' => 3, // 默认单台机器的进程数量
                'deal_num' => '50', // 单次处理数量
                'consumer' => 'DealTest' // 消费地址
            ]
        ]
    ]
];

4. 学习地址

https://www.cnblogs.com/yufeng218/p/9452621.html
https://blog.youkuaiyun.com/demon3182/article/details/77335206
https://blog.youkuaiyun.com/u010472499/article/details/78366614
https://segmentfault.com/a/1190000012308675

<think>我们使用Testcontainers来创建一个IBM MQ容器,并在其中发送消息。Testcontainers是一个Java库,支持JUnit测试,可以轻松地在测试中使用Docker容器。下面是一个使用Testcontainers和IBM MQ的示例。 步骤: 1. 添加依赖 2. 编写测试类,使用Testcontainers启动IBM MQ容器 3. 配置连接参数(从容器获取) 4. 发送消息队列 注意:IBM MQ的Testcontainer镜像可以从官方获取,我们使用`icr.io/ibm-messaging/mq`。 示例代码: 首先,确保在pom.xml中添加以下依赖(以Maven为例): ```xml <dependencies> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.16.0</version> <!-- 使用最新版本 --> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.16.0</version> <scope>test</scope> </dependency> <dependency> <groupId>com.ibm.mq</groupId> <artifactId>com.ibm.mq.allclient</artifactId> <version>9.2.3.0</version> <!-- 使用与MQ服务器版本兼容的客户端版本 --> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> </dependencies> ``` 然后,编写测试类: ```java import com.ibm.mq.MQException; import com.ibm.mq.MQQueueManager; import com.ibm.mq.constants.MQConstants; import com.ibm.mq.MQMessage; import com.ibm.mq.MQPutMessageOptions; import com.ibm.mq.MQQueue; import org.junit.jupiter.api.Test; import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import java.io.IOException; @Testcontainers public class IBMMQTest { // 使用IBM MQ官方镜像 private static final DockerImageName MQ_IMAGE = DockerImageName.parse("icr.io/ibm-messaging/mq:latest"); @Container public GenericContainer<?> mqContainer = new GenericContainer<>(MQ_IMAGE) .withExposedPorts(1414) // MQ默认端口 .withEnv("LICENSE", "accept") // 接受许可 .withEnv("MQ_QMGR_NAME", "QM1") // 设置队列管理器名称 .waitingFor(new org.testcontainers.containers.wait.strategy.Wait() { // 等待日志中出现队列管理器启动成功的消息 @Override public void waitUntilReady(org.testcontainers.containers.Container.ExecResult execResult) { // 等待策略,可以自定义,但Testcontainers有默认的等待策略,这里为了简单,我们使用日志等待 } }.forLogMessage(".*Started queue manager.*", 1)); @Test public void sendMessageToMQ() throws MQException, IOException { // 从容器获取连接信息 String host = mqContainer.getHost(); int port = mqContainer.getMappedPort(1414); String channel = "DEV.ADMIN.SVRCONN"; // 默认通道,根据镜像不同可能有所变化,请查阅镜像文档 String queueManagerName = "QM1"; // 与上面环境变量中设置的队列管理器名称一致 String queueName = "DEV.QUEUE.1"; // 默认队列,在镜像中预定义 // 配置MQ连接参数 com.ibm.mq.jms.MQConnectionFactory factory = new com.ibm.mq.jms.MQConnectionFactory(); factory.setHostName(host); factory.setPort(port); factory.setChannel(channel); factory.setQueueManager(queueManagerName); factory.setTransportType(com.ibm.mq.jms.JMSC.MQJMS_TP_CLIENT_MQ_TCPIP); // 或者使用传统方式(如之前的示例)但这里我们使用JMS方式更常见,但为了与之前示例一致,我们使用MQQueueManager // 注意:Testcontainers启动的容器默认用户为admin,密码为passw0rd,但根据镜像不同可能有变化,请查阅镜像文档 // 使用传统MQ客户端(非JMS)连接 MQQueueManager queueManager = new MQQueueManager(queueManagerName, MQConstants.MQCNO_STANDARD, null, // 使用默认连接选项 host, port, channel, // 主机、端口、通道 "admin", "passw0rd"); // 用户名和密码(根据镜像设置) // 打开队列 int openOptions = MQConstants.MQOO_OUTPUT; MQQueue queue = queueManager.accessQueue(queueName, openOptions); // 创建消息 MQMessage message = new MQMessage(); message.writeUTF("Hello from Testcontainers!"); // 发送消息 MQPutMessageOptions pmo = new MQPutMessageOptions(); queue.put(message, pmo); System.out.println("Message sent successfully!"); // 关闭 queue.close(); queueManager.disconnect(); } } ``` 注意: 1. 上述代码中使用的用户名和密码(admin/passw0rd)是IBM MQ官方Testcontainer镜像的默认凭证。如果使用其他镜像,请查阅相应文档。 2. 镜像中预定义了队列管理器(QM1)和队列(DEV.QUEUE.1)以及通道(DEV.ADMIN.SVRCONN)。 3. 等待策略:我们等待容器日志中出现“Started queue manager”表示队列管理器已启动。实际使用中,Testcontainers提供了多种等待策略,也可以使用`Wait.forLogMessage`。 如果使用JMS方式发送消息,代码会有所不同,但这里为了与之前的示例保持一致,使用了传统的MQQueueManager方式。 运行测试: 确保Docker已运行,然后执行测试。Testcontainers会自动下载镜像(如果本地没有)并启动容器,然后执行测试。 参考:IBM MQ Testcontainer镜像的官方文档:https://github.com/ibm-messaging/mq-container 相关问题: 1. 如何使用Testcontainers进行IBM MQ的消息接收测试? 2. 如何配置IBM MQ容器以使用自定义队列和通道? 3. 在Testcontainers中使用IBM MQ时如何处理SSL连接? 4. 如何在使用Testcontainers的测试中模拟MQ连接失败的情况? 5. 如何管理IBM MQ容器的生命周期(例如,在多个测试类之间共享容器)?</think>### 使用 Testcontainers 在 IBM MQ 中发送消息的完整指南 下面是一个使用 Testcontainers 在 IBM MQ 容器中发送消息的完整示例,包含 Java 代码实现: #### 1. 添加依赖 在 `pom.xml` 中添加必要依赖: ```xml <dependencies> <!-- Testcontainers 核心库 --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.17.6</version> <scope>test</scope> </dependency> <!-- IBM MQ 客户端 --> <dependency> <groupId>com.ibm.mq</groupId> <artifactId>com.ibm.mq.allclient</artifactId> <version>9.2.5.0</version> </dependency> <!-- JUnit 5 支持 --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.17.6</version> <scope>test</scope> </dependency> </dependencies> ``` #### 2. 完整示例代码 ```java import com.ibm.mq.MQException; import com.ibm.mq.MQMessage; import com.ibm.mq.MQPutMessageOptions; import com.ibm.mq.MQQueue; import com.ibm.mq.MQQueueManager; import com.ibm.mq.constants.MQConstants; import org.junit.jupiter.api.Test; import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import java.io.IOException; @Testcontainers public class IBMMQTestContainerTest { // 使用 IBM 官方 MQ 容器镜像 private static final DockerImageName MQ_IMAGE = DockerImageName.parse("icr.io/ibm-messaging/mq:9.2.5.0-r1"); // 启动 MQ 容器 @Container public GenericContainer<?> mqContainer = new GenericContainer<>(MQ_IMAGE) .withExposedPorts(1414) // MQ 默认端口 .withEnv("LICENSE", "accept") // 接受许可协议 .withEnv("MQ_QMGR_NAME", "QM1") // 设置队列管理器名称 .waitingFor(new MQStartupWaitStrategy()); // 自定义等待策略 @Test public void sendMessageToMQ() throws MQException, IOException { // 获取容器连接信息 String host = mqContainer.getHost(); int port = mqContainer.getMappedPort(1414); String channel = "DEV.ADMIN.SVRCONN"; String queueManager = "QM1"; String queueName = "DEV.QUEUE.1"; // 配置 MQ 连接 MQQueueManager qMgr = new MQQueueManager( queueManager, MQConstants.MQCNO_STANDARD, null, host, port, channel, "admin", // 默认用户名 "passw0rd" // 默认密码 ); // 打开队列(写权限) int openOptions = MQConstants.MQOO_OUTPUT; MQQueue queue = qMgr.accessQueue(queueName, openOptions); try { // 创建消息 MQMessage message = new MQMessage(); message.writeUTF("Testcontainers 发送的测试消息"); // 设置消息选项 MQPutMessageOptions pmo = new MQPutMessageOptions(); pmo.options = MQConstants.MQPMO_NO_SYNCPOINT; // 发送消息 queue.put(message, pmo); System.out.println("✅ 消息发送成功!"); } finally { // 清理资源 queue.close(); qMgr.disconnect(); } } // 自定义等待策略(确保 MQ 完全启动) static class MQStartupWaitStrategy extends org.testcontainers.containers.wait.strategy.AbstractWaitStrategy { @Override protected void waitUntilReady() { // 实际实现中可添加更精确的等待逻辑 try { Thread.sleep(15000); // 等待15秒确保MQ完全启动 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } ``` #### 3. 关键配置说明 1. **容器配置**: - `icr.io/ibm-messaging/mq`:IBM 官方 MQ 容器镜像 - 默认队列管理器:`QM1` - 默认队列:`DEV.QUEUE.1` - 默认通道:`DEV.ADMIN.SVRCONN` - 默认凭证:用户 `admin` / 密码 `passw0rd` 2. **连接参数获取**: ```java String host = mqContainer.getHost(); // 容器主机 int port = mqContainer.getMappedPort(1414); // 映射端口 ``` 3. **自定义队列**: 要使用自定义队列,可在容器启动时添加环境变量: ```java .withEnv("MQ_QUEUES", "MY_QUEUE1, MY_QUEUE2") ``` #### 4. 运行流程 1. Testcontainers 自动下载 MQ 镜像(首次运行) 2. 启动容器并配置队列管理器 3. 建立 MQ 连接 4. 发送文本消息到指定队列 5. 自动清理资源 #### 5. 验证消息 要验证消息是否成功发送,可添加接收消息的测试方法: ```java @Test public void receiveMessageFromMQ() throws MQException, IOException { // ... 连接配置同上 ... // 打开队列(读权限) int openOptions = MQConstants.MQOO_INPUT_SHARED; MQQueue queue = qMgr.accessQueue(queueName, openOptions); // 接收消息 MQMessage receivedMessage = new MQMessage(); MQGetMessageOptions gmo = new MQGetMessageOptions(); queue.get(receivedMessage, gmo); // 读取消息内容 String messageContent = receivedMessage.readUTF(); System.out.println("收到的消息: " + messageContent); } ``` #### 6. 常见问题解决 1. **连接超时**: - 增加等待策略中的启动等待时间 - 检查防火墙是否允许端口访问 2. **认证失败**: - 确认使用默认凭证 `admin/passw0rd` - 或通过环境变量自定义凭证: ```java .withEnv("MQ_APP_PASSWORD", "myPassword") ``` 3. **队列不存在**: - 确保在容器启动时声明所需队列: ```java .withEnv("MQ_QUEUES", "MY_QUEUE") ``` --- ### 容器配置参考 | 环境变量 | 说明 | 默认值 | |---------|------|-------| | `MQ_QMGR_NAME` | 队列管理器名称 | `QM1` | | `MQ_QUEUES` | 预创建队列列表(逗号分隔) | `DEV.QUEUE.1` | | `MQ_CHANNELS` | 预创建通道列表 | `DEV.ADMIN.SVRCONN` | | `MQ_APP_PASSWORD` | 应用用户密码 | `passw0rd` | | `LICENSE` | 必须设置为 `accept` | - | --- ### 相关问题 1. 如何在 Testcontainers 中配置 IBM MQ 的持久化存储? 2. 如何在使用 Testcontainers 的 IBM MQ 测试中模拟消息消费失败场景? 3. Testcontainers 支持哪些其他消息中间件(如 Kafka、RabbitMQ)? 4. 如何在 CI/CD 流水线中集成 IBM MQ 的 Testcontainers 测试? 5. 如何监控 Testcontainers 中 IBM MQ 容器的运行状态?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值