Spring Boot整合RabbitMQ-RabbitMQ

26 篇文章 1 订阅
9 篇文章 0 订阅

概述

消息中间件的应用场景主要有:异步处理、应用解耦、流量削峰等。
生产者发送消息通过不同类型的交互机发送到不同的消息队列中。
消费者只关心消息队列,与交换机无关,至于生产者如何发送的(直接发送到队列还是通过交换机的方式发送)毫不关心。

导入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.practice.springboot</groupId>
    <artifactId>rabbitmqtest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmqtest</name>
    <description>rabbitmqTest</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
</project>

在application.properties添加RabbitMQ基本配置

# ip
spring.rabbitmq.host=127.0.0.1
# 端口
spring.rabbitmq.port=5672
# 用户名
spring.rabbitmq.username=guest
# 密码
spring.rabbitmq.password=guest
# 配置虚拟机
spring.rabbitmq.virtual-host=/

交换机、队列、路由初始化流程

1、建议方式,无论是生产者还是消费者,都应该声明交换机。
2、队列声明、绑定交换机、路由可以由生产者或者消费者来做,或者都两者都声明。
3、建议方式,生产者只声明交换机,不声明队列,直接发送消息到指定的交换机、路由Key即可。
4、建议方式,消费者声明交换机,声明队列,队列绑定交换机和路由Key,等待接收生产者发送的消息。
PS:路由Key无需声明,只需要在队列绑定时指定即可。

Routing工作模式(Direct Exchange类型)示例

编写生产者

package com.practice.springboot.rabbitmqtest.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * @Description : 直接模式交换机 RabbitMQ配置
 * @Version : V1.0.0
 * @Date : 2021/11/9 17:14
 */
@Configuration
public class DirectRabbitConfig {
    /**
     Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
     Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
     Queue:消息的载体,每个消息都会被投到一个或多个队列。
     Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
     Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
     vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。
     Producer:消息生产者,就是投递消息的程序.
     Consumer:消息消费者,就是接受消息的程序.
     Channel:消息通道,在客户端的每个连接里,可建立多个channel.
     */

    @Value("${spring.rabbitmq.host}")
    private String host;

    @Value("${spring.rabbitmq.port}")
    private int port;

    @Value("${spring.rabbitmq.username}")
    private String username;

    @Value("${spring.rabbitmq.password}")
    private String password;

    @Value("${spring.rabbitmq.virtual-host}")
    private String vHost;

    // 交换机
    public static final String TestDirectExchange = "TestDirectExchange";

    // 消息队列
    public static final String TestDirectQueue = "TestDirectQueue";

    // RoutingKey
    public static final String TestDirectRouting = "TestDirectRouting";

    // 可以省略,使用默认的连接工厂
    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setHost(host);
        cachingConnectionFactory.setPort(port);
        cachingConnectionFactory.setUsername(username);
        cachingConnectionFactory.setPassword(password);
        cachingConnectionFactory.setVirtualHost(vHost);
        // 可以不配置,默认生产者不确认发送应答
        cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        return cachingConnectionFactory;
    }

    // 可以省略,使用默认的RabbitTemplate
    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate(connectionFactory());
    }

	// 用于在RabbitMq服务器上生成交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(TestDirectExchange, true, false);
    }
	
	// 用于在RabbitMq服务器上生成消息队列
    @Bean
    public Queue testDirectQueue() {
        // durable:是否持久化,默认是false; 持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在; 暂存队列:当前连接有效。
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:默认false,是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        // 一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue(TestDirectQueue, true);
    }

    /**
     * 将队列和交换机绑定,并设置路由key
     * @return bind实例
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(testDirectQueue()).to(directExchange()).with(TestDirectRouting);
    }
}

生产者发送消息测试示例

package com.practice.springboot.rabbitmqtest.test;

import com.practice.springboot.rabbitmqtest.config.DirectRabbitConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestDirectModel {


    //注入rabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testDirect() {
        rabbitTemplate.convertAndSend(DirectRabbitConfig.TestDirectExchange,
                DirectRabbitConfig.TestDirectRouting, "test direct exchange");
    }
}

执行两次测试用例;
因为目前还没有消费者 ,消息还没有被消费,结果如下:
在这里插入图片描述
可见消息已经发送到RabbitMq服务器上了。

编写消费者

如果消费者和生产者在同一个模块,可以不需要下面的配置,直接写一个监听类即可。

导入maven依赖:

<!--rabbitmq-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>

application.properties配置

# ip
spring.rabbitmq.host=127.0.0.1
# 端口
spring.rabbitmq.port=5672
# 用户名
spring.rabbitmq.username=guest
# 密码
spring.rabbitmq.password=guest
# 配置虚拟机
spring.rabbitmq.virtual-host=/

创建DirectRabbitMqConfig配置类

如果消费者单纯的接收消息,可以不用添加这个配置类,直接新建后面的监听器就好,使用注解来让监听器监听对应队列即可。配置上的话,消费者也可以作为生产者,也能推送消息。

@Configuration
public class DirectRabbitMqConfig {
 
    /**
     * 队列
     */
    @Bean
    public Queue testDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("TestDirectQueue", true);
    }
 
    /**
     * Direct交换机
     */
    @Bean
    public DirectExchange testDirectExchange() {
        return new DirectExchange("TestDirectExchange", true, false);
    }
 
    /**
     * 绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
     */
    @Bean
    public Binding bindingDirect() {
        return BindingBuilder.bind(testDirectQueue()).to(testDirectExchange()).with("TestDirectRouting");
    }

}

添加消息监听类

package com.practice.springboot.rabbitmqtest.controller;

import com.practice.springboot.rabbitmqtest.config.DirectRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Component
public class RabbitMqConsumer {
    @RabbitListener(queues = DirectRabbitConfig.TestDirectQueue)
    public void receive(Message<String> message) {
        System.out.println("message = " + message);
    }
}

测试结果

项目启动,可以看到之前的两条消息被消费了

2021-11-10 17:40:06.795  INFO 8708 --- [           main] c.p.s.r.RabbitmqtestApplication          : Started RabbitmqtestApplication in 2.638 seconds (JVM running for 3.623)
message = GenericMessage [payload=test direct exchange, headers={amqp_receivedDeliveryMode=PERSISTENT, amqp_receivedExchange=TestDirectExchange, amqp_deliveryTag=1, amqp_consumerQueue=TestDirectQueue, amqp_redelivered=true, amqp_receivedRoutingKey=TestDirectRouting, timestamp_in_ms=1636470108552, amqp_contentEncoding=UTF-8, amqp_timestamp=Tue Nov 09 23:01:48 CST 2021, id=039d4adf-25cf-a278-701a-9923d49abf8c, amqp_consumerTag=amq.ctag-OTugfCIczA7blzOcnooPcA, amqp_lastInBatch=false, contentType=text/plain, timestamp=1636537206802}]
message = GenericMessage [payload=test direct exchange, headers={amqp_receivedDeliveryMode=PERSISTENT, amqp_receivedExchange=TestDirectExchange, amqp_deliveryTag=2, amqp_consumerQueue=TestDirectQueue, amqp_redelivered=false, amqp_receivedRoutingKey=TestDirectRouting, timestamp_in_ms=1636470185702, amqp_contentEncoding=UTF-8, amqp_timestamp=Tue Nov 09 23:03:05 CST 2021, id=dbc839cd-2691-d123-22c4-27baf957ef35, amqp_consumerTag=amq.ctag-OTugfCIczA7blzOcnooPcA, amqp_lastInBatch=false, contentType=text/plain, timestamp=1636537235602}]

Topic工作模式(Topic Exchange类型交换机)

生产者

配置Topic 交换机

package com.practice.springboot.rabbitmqtest.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description : 直接模式交换机 RabbitMQ配置
 * @Version : V1.0.0
 * @Date : 2021/11/9 17:14
 */
@Configuration
public class TopicModelRabbitConfig {

    @Value("${spring.rabbitmq.host}")
    private String host;

    @Value("${spring.rabbitmq.port}")
    private int port;

    @Value("${spring.rabbitmq.username}")
    private String username;

    @Value("${spring.rabbitmq.password}")
    private String password;

    @Value("${spring.rabbitmq.virtual-host}")
    private String vHost;

    // 交换机
    public static final String TestTopicExchange = "TestTopicExchange";

    public static final String TestTopicQueue1 = "TestTopicQueue1";

    public static final String TestTopicQueue2 = "TestTopicQueue2";

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TestTopicExchange, true, false);
    }

    @Bean
    public Queue firstQueue() {
        return new Queue(TestTopicQueue1, true);
    }

    @Bean
    public Queue secondQueue() {
        return new Queue(TestTopicQueue2, true);
    }

    /**
     * 将firstQueue和topicExchange绑定,而且绑定的键值为topic.man
     * 这样只要是消息携带的路由键是topic.man,才会分发到该队列
     */
    @Bean
    public Binding bindingFirst() {
        return BindingBuilder.bind(firstQueue()).to(topicExchange()).with("topic.man");
    }

    /**
     * 将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
     * 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列;
     * binding key 中可以存在两种特殊字符 *与 #,用于做模糊匹配,其中 * 用于匹配一个单词,# 用于匹配0个或多个单词,单词以符号“.”为分隔符。
     */
    @Bean
    public Binding bindingSecond() {
        return BindingBuilder.bind(secondQueue()).to(topicExchange()).with("topic.#");
    }
}

生产者发送消息

package com.practice.springboot.rabbitmqtest.test;

import com.practice.springboot.rabbitmqtest.config.TopicModelRabbitConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestTopicModel {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testTopic1() {
        rabbitTemplate.convertAndSend(TopicModelRabbitConfig.TestTopicExchange, "topic.man",
                "test topic exchange 1");
    }

    @Test
    public void testTopic2() {
        rabbitTemplate.convertAndSend(TopicModelRabbitConfig.TestTopicExchange, "topic.woman", "test topic exchange 2");
    }
}

testTopic1方法发送消息,TestTopicQueue1 、TestTopicQueue2都收到消息;
testTopic2方法发送消息,只有TestTopicQueue2队列收到消息。

消费者

    @RabbitListener(queues = TopicModelRabbitConfig.TestTopicQueue1)
    public void receiveTopic1(Message<String> message) {
        System.out.println(message.getPayload());
    }

    @RabbitListener(queues = TopicModelRabbitConfig.TestTopicQueue2)
    public void receiveTopic2(Message<String> message) {
        System.out.println(message.getPayload());
    }

测试结果

可以看到,TestTopicQueue1 的一条消息、TestTopicQueue2的两条消息都被消费了

test topic exchange 1
test topic exchange 1
test topic exchange 2

发布订阅工作模式(fanout exchange扇形交换机)

发布定义模式使用的是扇形交换机,只要绑定到交换机上的队列都会接收到消息,路由无效。

生产者

配置扇形交换机

package com.practice.springboot.rabbitmqtest.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description : fanout 交换机配置
 * @Version : V1.0.0
 * @Date : 2021/11/10 19:19
 */
@Configuration
public class FanoutRabbitMqConfig {
    /**
     * 创建三个队列 :TestFanoutQueueA   TestFanoutQueueB  TestFanoutQueueC
     * 将三个队列都绑定在交换机 TestFanoutExchange 上
     * 因为是扇型交换机, 路由键无需配置,配置也不起作用
     */

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("TestFanoutExchange", true, false);
    }

    @Bean
    public Queue queueA() {
        return new Queue("TestQueueA", true);
    }

    @Bean
    public Queue queueB() {
        return new Queue("TestQueueB", true);
    }

    @Bean
    public Queue queueC() {
        return new Queue("TestQueueC", true);
    }

    // bind queue and exchange
    @Bean
    public Binding bindingA() {
        return BindingBuilder.bind(queueA()).to(fanoutExchange());
    }

    @Bean
    public Binding bindingB() {
        return BindingBuilder.bind(queueB()).to(fanoutExchange());
    }

    @Bean
    public Binding bindingC() {
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }
}

生产者发送消息

    @Test
    public void testTopic1() {
        rabbitTemplate.convertAndSend("TestFanoutExchange", null, "test fanout exchange");
    }

消息被发送到3个队列中。

消费者

    @RabbitListener(queues = "TestQueueA")
    public void receiveTopicA(Message<String> message) {
        System.out.println(message.getPayload());
    }

    @RabbitListener(queues = "TestQueueB")
    public void receiveTopicB(Message<String> message) {
        System.out.println(message.getPayload());
    }

    @RabbitListener(queues = "TestQueueC")
    public void receiveTopicC(Message<String> message) {
        System.out.println(message.getPayload());
    }

测试结果

test fanout exchange
test fanout exchange
test fanout exchange

生产者发送事务消息

事务消息,指生产者发送一组消息,消费者要么全部收到,要么都收不到。
使用上面扇形交换机的例子来展示事务收发。

配置事务管理器

在fanout exchange 代码的基础上添加了rabbitMq的事务管理器:RabbitTransactionManager

package com.practice.springboot.rabbitmqtest.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.transaction.RabbitTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description : fanout 交换机配置
 * @Version : V1.0.0
 * @Date : 2021/11/10 19:19
 */
@Configuration
public class FanoutRabbitMqConfig {
    /**
     * 创建三个队列 :TestFanoutQueueA   TestFanoutQueueB  TestFanoutQueueC
     * 将三个队列都绑定在交换机 TestFanoutExchange 上
     * 因为是扇型交换机, 路由键无需配置,配置也不起作用
     */

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("TestFanoutExchange", true, false);
    }

    // 配置启用rabbitmq事务
    @Bean
    public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory) {
        return new RabbitTransactionManager(connectionFactory);
    }

    @Bean
    public Queue queueA() {
        return new Queue("TestQueueA", true);
    }

    @Bean
    public Queue queueB() {
        return new Queue("TestQueueB", true);
    }

    @Bean
    public Queue queueC() {
        return new Queue("TestQueueC", true);
    }

    // bind queue and exchange
    @Bean
    public Binding bindingA() {
        return BindingBuilder.bind(queueA()).to(fanoutExchange());
    }

    @Bean
    public Binding bindingB() {
        return BindingBuilder.bind(queueB()).to(fanoutExchange());
    }

    @Bean
    public Binding bindingC() {
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }
}

生产者发送消息

不能使用之前的测试用例的方式来测试,否则会导致事务管理器多次初始化,无法正常测试。
下面写一个Rest API接口用于测试。

package com.practice.springboot.rabbitmqtest.controller;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;

/**
 * @Description : rabbitmq provider
 * @Author : guxuemin
 * @Version : V1.0.0
 * @Date : 2021/11/10 20:24
 */
@RestController
public class RabbitMqProvider {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        rabbitTemplate.setChannelTransacted(true);
    }

    @GetMapping("/sendMsg")
    @Transactional(rollbackFor = Exception.class, transactionManager = "rabbitTransactionManager")
    public void testTransactionMessage() throws InterruptedException {
        rabbitTemplate.convertAndSend("TestFanoutExchange", null, "test fanout transaction exchange1");
        rabbitTemplate.convertAndSend("TestFanoutExchange", null, "test fanout transaction exchange2");
        // 模拟异常
        System.out.println(1/0);
    }
}
  • 在发送消息的testTransactionMessage()方法上,加了@Transactional注解,表示这个方法将启用事务(此时的事务即是RabbitMQ事务,因为使用了RabbitTransactionManager )。
  • 启用事务,需要在系统初始化时,调用rabbitTemplate.setChannelTransacted(true),以激活rabbitTemplate对象事务处理功能。

测试

异常,消息都没有发出

调用接口,发现异常,同时消费者并没有收到消息。
在这里插入图片描述

2021-11-10 20:45:06.637 ERROR 4560 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero
	at com.practice.springboot.rabbitmqtest.controller.RabbitMqProvider.testTransactionMessage(RabbitMqProvider.java:34) ~[classes/:na]

关闭事务,消息正常发送

不启用通道事务功能
    @PostConstruct
    public void init() {
//        rabbitTemplate.setChannelTransacted(true);
    }

可以看到消息正常发出了。

2021-11-10 20:39:47.072  INFO 3480 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
test fanout transaction exchange1
test fanout transaction exchange1
test fanout transaction exchange1
test fanout transaction exchange2
test fanout transaction exchange2
test fanout transaction exchange2
2021-11-10 20:39:47.115 ERROR 3480 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
不使用事务注解
    @GetMapping("/sendMsg")
//    @Transactional(rollbackFor = Exception.class, transactionManager = "rabbitTransactionManager")
    public void testTransactionMessage() throws InterruptedException {
        rabbitTemplate.convertAndSend("TestFanoutExchange", null, "test fanout transaction exchange1");
        rabbitTemplate.convertAndSend("TestFanoutExchange", null, "test fanout transaction exchange2");
        // generate exception
        System.out.println(1/0);
    }

可以看到消息也正常发出了。

2021-11-10 20:42:49.950  INFO 13532 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
test fanout transaction exchange1
test fanout transaction exchange1
test fanout transaction exchange1
test fanout transaction exchange2
test fanout transaction exchange2
test fanout transaction exchange2
2021-11-10 20:42:49.997 ERROR 13532 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

参考

Spring Boot整合RabbitMQ详细教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融极

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值