RabbitMq 使用详解

RabbitMQ 是实现 AMQP(高级消息队列协议)的消息中间件的一种,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。

核心概念:Connection(连接)、Channel(信道)、Exchange(交换机)、Queue(队列)、Virtual host(虚拟主机)。
Connection(连接):
每个producer(生产者)或者consumer(消费者)要通过RabbitMQ发送与消费消息,就要先与RabbitMQ建立连接。Connection是一个TCP长连接。

Channel(信道):
Channel是在Connection的基础上建立的虚拟连接,RabbitMQ中大部分的操作都是使用Channel完成的,比如:声明Queue、声明Exchange、发布消息、消费消息等。。

Virtual host(虚拟主机):
Virtual host是一个虚拟主机的概念,一个Broker中可以有多个Virtual host,每个Virtual host都有一套自己的Exchange和Queue,同一个Virtual host中的Exchange和Queue不能重名,不同的Virtual host中的Exchange和Queue名字可以一样。做到了不同用户之间相互隔离的效果。

Queue(队列):
Queue存放消息的队列,生产者发送消息到Queue中,消费者从Queue中取走消息。

Exchange(交换机):
Exchange负责根据不同的分发规则将消息分发到不同的Queue。

交换机四种分发消息策略:direct、fanout、topic 、header

消息发送核心思路: 消息到达交换机exchange ,exchange 通过routing_key 找到具体的队列 然后去消费, 不过其中fanout 和 header 不需要exchange和 queue 通过routing_key 绑定。

demo 如下:

pom文件中引入jar:

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

 

1. direct 策略:当一条消息到达 DirectExchange 时会被转发到与该条消息 routing key 相同的 Queue 上

1.1 direct 配置文件

/**
 * @author wanglong
 * @since 2022/6/9 19:50
 */
@Component
public class DirectRabbitConfig {

    public static final String DIRECT_EXCHANGE = "direct_exchange";
    public static final String DIRECT_QUEUE_ONE = "queue1";
    public static final String DIRECT_QUEUE_TWO = "queue2";
    public static final String DIRECT_ROUTING_KEY_ONE = "routing_key1";
    public static final String DIRECT_ROUTING_KEY_TWO = "routing_key2";

    @Bean
    public DirectExchange exchange() {
        return new DirectExchange(DIRECT_EXCHANGE);
    }

    @Bean
    public Queue direct_queue1() {
        return new Queue(DIRECT_QUEUE_ONE);
    }

    @Bean
    public Queue direct_queue2() {
        return new Queue(DIRECT_QUEUE_TWO);
    }

    @Bean
    Binding binding1(DirectExchange exchange, Queue direct_queue1) {
        return BindingBuilder.bind(direct_queue1).to(exchange).with(DIRECT_ROUTING_KEY_ONE);
    }

    @Bean
    Binding binding2(DirectExchange exchange, Queue direct_queue2) {
        return BindingBuilder.bind(direct_queue2).to(exchange).with(DIRECT_ROUTING_KEY_TWO);
    }


}

1.2 direct 生产者

@Component
public class DirectSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send() {
        String context = "hello" + new Date();
        System.out.println("发送消息:" + context + new Date());
        this.rabbitTemplate.convertAndSend(DirectRabbitConfig.DIRECT_EXCHANGE, DirectRabbitConfig.DIRECT_ROUTING_KEY_ONE, context);
    }
}

1.3 direct 消费者

@Component
public class DirectReceiver {

    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE_ONE)
    public void receive1(String str) {
        System.out.println("接受消息DIRECT_QUEUE_ONE :" + str);
    }

    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE_TWO)
    public void receive2 (String str) {
        System.out.println("接受消息DIRECT_QUEUE_TWO :" + str);
    }

}

1.4 测试类

@SpringBootTest(classes = SystemApplication.class)
public class MqTest {

    @Autowired
    private DirectSender directSender;

    @Test
    public void directTest() {
        directSender.send();
    }

}

测试结果:

 

 

 

 

 

 

 2. fanout 策略:到达 FanoutExchange 的消息转发给所有与它绑定的 Queue,routingkey 不起作用

2.1 fanout 配置文件

@Component
public class FanoutRabbitConfig {

    public static final String FANOUT_EXCHANGE = "fanout_exchange";
    public static final String FANOUT_QUEUE_ONE = "fanout_queue1";
    public static final String FANOUT_QUEUE_TWO = "fanout_queue2";


    @Bean
    public FanoutExchange exchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }

    @Bean
    public Queue fanoutQueue1() {
        return new Queue(FANOUT_QUEUE_ONE);
    }

    @Bean
    public Queue fanoutQueue2() {
        return new Queue(FANOUT_QUEUE_TWO);
    }

    @Bean
    Binding binding1(FanoutExchange exchange, Queue fanoutQueue1) {
        return BindingBuilder.bind(fanoutQueue1).to(exchange);
    }

    @Bean
    Binding binding2(FanoutExchange exchange, Queue fanoutQueue2) {
        return BindingBuilder.bind(fanoutQueue2).to(exchange);
    }

}

2.2 fanout 生产者

@Component
public class FanoutSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send() {
        String context = "fanout mq信息" + new Date();
        System.out.println("发送消息:" + context + new Date());
        this.rabbitTemplate.convertAndSend(FanoutRabbitConfig.FANOUT_EXCHANGE, "", context);
    }
}

2.3  fanout 消费者

@Component
public class FanoutReceiver {

    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_ONE)
    public void receive1(String str) {
        System.out.println("接受消息FANOUT_QUEUE_ONE :" + str);
    }

    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_TWO)
    public void receive2 (String str) {
        System.out.println("接受消息FANOUT_QUEUE_TWO :" + str);
    }

}

2.4 fanout 测试类

@SpringBootTest(classes = SystemApplication.class)
public class MqTest {

    @Autowired
    private FanoutSender fanoutSender;

    @Test
    public void fanoutTest() {
        fanoutSender.send();
    }

}

测试结果:

 

 

 

 

 

 3. topic 策略:当消息到达 TopicExchange 后,TopicExchange 根据消息的 rountingkey 将消息路由到一个或者多个 Queue 上(可以使用符号 # 匹配一个或多个词,符号 * 匹配正好一个词)

 3.1 topic 配置文件

@Component
public class TopicRabbitConfig {

    public static final String EXCHANGE = "topic_exchange";
    public static final String TOPIC_QUEUE_ONE = "topic_queue1";
    public static final String TOPIC_QUEUE_TWO = "topic_queue2";
    public static final String TOPIC_ROUTING_KEY_ONE = "topic_routing_key.one";
    public static final String TOPIC_ROUTING_KEY_TWO = "topic_routing_key.#";
    public static final String TOPIC_ROUTING_KEY_THREE = "topic_routing_key.three";

    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE);
    }

    @Bean
    public Queue queue1() {
        return new Queue(TOPIC_QUEUE_ONE);
    }

    @Bean
    public Queue queue2() {
        return new Queue(TOPIC_QUEUE_TWO);
    }

    @Bean
    Binding binding1(TopicExchange exchange, Queue queue1) {
        return BindingBuilder.bind(queue1).to(exchange).with(TOPIC_ROUTING_KEY_ONE);
    }

    @Bean
    Binding binding2(TopicExchange exchange, Queue queue2) {
        return BindingBuilder.bind(queue2).to(exchange).with(TOPIC_ROUTING_KEY_TWO);
    }

}

3.2. topic 生产者

@Component
public class TopicSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send() {
        String context = "topic消息" + new Date();
        System.out.println("发送消息:" + context + new Date());
        this.rabbitTemplate.convertAndSend(TopicRabbitConfig.EXCHANGE, TopicRabbitConfig.TOPIC_ROUTING_KEY_ONE, context);
    }
    public void send2() {
        String context = "topic2消息" + new Date();
        System.out.println("发送消息:" + context + new Date());
        this.rabbitTemplate.convertAndSend(TopicRabbitConfig.EXCHANGE, TopicRabbitConfig.TOPIC_ROUTING_KEY_THREE, context);
    }
}

3.3 topic 消费者

@Component
public class TopicReceiver {

    @RabbitListener(queues = TopicRabbitConfig.TOPIC_QUEUE_ONE)
    public void receive1(String str) {
        System.out.println("接收消息TOPIC_QUEUE_ONE :" + str);
    }

    @RabbitListener(queues = TopicRabbitConfig.TOPIC_QUEUE_TWO)
    public void receive2(String str) {
        System.out.println("接收消息TOPIC_QUEUE_TWO :" + str);
    }

}

3.4 topic 测试类

@SpringBootTest(classes = SystemApplication.class)
public class MqTest {

    @Autowired
    private TopicSender topicSender;

    @Test
    public void topicTest1() {
        topicSender.send();
    }

    @Test
    public void topicTest2() {
        topicSender.send2();
    }

}

测试结果:send方法中routing_key 是 topic_routing_key.one, 理论上会匹配到  队列topic_queue1 ,因为存在topic_routing_key.# 所以和 topic_routing_key.# 绑定的队列也会接收到消息


 

 

 

 

 

 send2方法 中 routing_key 为 topic_routing_key.three, 只会通过topic_routing_key.# 被队列topic_queue2 接收

 

 

 

 4.headers 策略:HeadersExchange 会根据消息的 Header 将消息路由到不同的 Queue 上,和 routingkey 无关。

4.1 headers配置文件:

@Component
public class HeadersRabbitConfig {

    public static final String HEADER_EXCHANGE = "headers_exchange";
    public static final String HEADERS_QUEUE_ONE = "headers_queue1";
    public static final String HEADERS_QUEUE_TWO = "headers_queue2";

    @Bean
    public HeadersExchange headersExchange() {
        return new HeadersExchange(HEADER_EXCHANGE);
    }

    @Bean
    public Queue headerQueue1() {
        return new Queue(HEADERS_QUEUE_ONE);
    }

    @Bean
    public Queue headerQueue2() {
        return new Queue(HEADERS_QUEUE_TWO);
    }

    @Bean
    Binding binding1(HeadersExchange headersExchange, Queue headerQueue1) {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "zhangsan");
        //消息只要有一个Header匹配上map中的key/value,便路由到这个Queue上
        return BindingBuilder.bind(headerQueue1).to(headersExchange).whereAny(map).match();
    }

    @Bean
    Binding binding2(HeadersExchange headersExchange, Queue headerQueue2) {
        //只要消息Header中包含age,便路由到这个Queue上
        return BindingBuilder.bind(headerQueue2).to(headersExchange).where("age").exists();
    }

}

4.2 headers 生产者

@Component
public class HeaderSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send1() {
        Message nameMsg = MessageBuilder.withBody("map name test".getBytes()).setHeader("name", "zhangsan").build();
        System.out.println(nameMsg.toString());
        rabbitTemplate.send(HeadersRabbitConfig.HEADER_EXCHANGE, null, nameMsg);
    }

    public void send2() {
        Message ageMsg = MessageBuilder.withBody("String age test".getBytes()).setHeader("age", "18").build();
        System.out.println(ageMsg.toString());
        rabbitTemplate.send(HeadersRabbitConfig.HEADER_EXCHANGE, null, ageMsg);
    }
}

4.3 headers 消费者

@Component
public class HeaderReceiver {

    @RabbitListener(queues = HeadersRabbitConfig.HEADERS_QUEUE_ONE)
    public void receive1(byte[] msg) {
        System.out.println("接受消息QUEUE_ONE :" + new String(msg, 0, msg.length));
    }

    @RabbitListener(queues = HeadersRabbitConfig.HEADERS_QUEUE_TWO)
    public void receive2(byte[] msg) {
        System.out.println("接受消息QUEUE_TWO :" + new String(msg, 0, msg.length));
    }

}

4.4 headers 测试

@SpringBootTest(classes = SystemApplication.class)
public class MqTest {

    @Autowired
    private HeaderSender headerSender;

    @Test
    public void headerTest1(){
        headerSender.send1();
    }

    @Test
    public void headerTest2(){
        headerSender.send2();
    }

}

测试结果:

 

 

 

 

 


新建队列时调用:

 public Queue(String name) {
        this(name, true, false, false);
    }

public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete) {
    this(name, durable, exclusive, autoDelete, (Map)null);
}

name(String)队列的名称,不可为空。
durable(boolean)队列是否可持久化,默认为true。
true:持久化队列,队列的声明会存放到Erlang自带的数据库中,所以该队列在服务器重新启动后继续存在。
false:非持久化队列,队列的声明会存放到内存中,当服务器关闭时内存会被清除,所以该队列在服务器重新启动后不存在。
exclusive(boolean)队列是否具有排它性,默认为false。
true:当连接关闭时connection.close()该队列会自动删除; 会对该队列加锁,其他通道channel不能访问该队列。
false: 当连接关闭时connection.close()该队列不会自动删除;不会对该队列加锁,其他通道channel能访问该队列,即一个队列可以有多个消费者消费。
autoDelete(boolean):队列没有任何订阅的消费者时是否自动删除,默认为false。如果为true,当consumers = 0时队列就会自动删除。

posted @ 2022-06-10 17:09  山阴路的秋天  阅读(620)  评论(0编辑  收藏  举报