SpringAMQP-Study-Demo

消息队列-SpringAMQP

项目包括消息发送微服务publisher,消息接收微服务consumer.

一、项目代码编写步骤

1.创建工程

创建一个springboot项目,添加spring-for-rabbitmq功能

image

在创建项目是勾选sprinng-for-rabbitmq功能,会自动添加springamqp的依赖

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

2.simple.queue

publisher微服务发送消息

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //基本队列-simple.queue
    @Test
    public void sendToSimpleQueue() {
        String queueName = "simple.queue";
        String message = "hello rabbitmq, simple queue";
        rabbitTemplate.convertAndSend(queueName, message);
        System.out.println(LocalTime.now() + "成功发送消息【" + message + "】到 simple.queue");
    }

consumer微服务接收消息

    //1.接收 simple.queue消息
    @RabbitListener(queues = "simple.queue")
    public void listenToSimpleQueue(String msg){
        System.out.println(LocalTime.now()+" 消费者接收到来自simple.queue的消息【"+msg+"】");
    }

3.work.queue

publisher微服务发送消息

    //工作队列-work.queue
    @Test
    public void sendToWorkQueue() throws InterruptedException {
        String queueName = "work.queue";
        String message = "hello rabbitmq, work queue";
        for (int i = 1; i <= 20; i++) {
            rabbitTemplate.convertAndSend(queueName, message);
            System.out.println(LocalTime.now() + "成功发送消息"+i+"【" + message + "】到 work.queue");
            Thread.sleep(20);
        }
    }

consumer微服务接收消息

    //2.接收 work.queue消息
    @RabbitListener(queues = "work.queue")
    public void listenToWorkQueue1(String msg) throws InterruptedException {
        System.out.println(LocalTime.now()+" 消费者1接收到来自work.queue的消息【"+msg+"】");
        Thread.sleep(20);
    }

    @RabbitListener(queues = "work.queue")
    public void listenToWorkQueue2(String msg) throws InterruptedException {
        System.out.println(LocalTime.now()+" 消费者2接收到来自work.queue的消息【"+msg+"】");
        Thread.sleep(200);
    }

4.FanoutExchange

将消息路由到与之绑定的所有队列

在consumer微服务中创建一个类,进行交换机、队列的声明和绑定

package com.lqy.mqconsumer.exchange;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class MyFanoutExchange {

    //交换机
    @Bean
    public  FanoutExchange fanoutExchange(){
        return new FanoutExchange("myFanout.exchange");
    }

    //队列1
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    //绑定1
    @Bean
    public Binding binding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    //队列2
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    //绑定2
    @Bean
    public Binding binding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }

}

启动consumer服务,查看rabbitmq客户端,两个队列成功绑定到交换机
image

在publisher微服务中编写消息发送代码,将消息发送到交换机myFanout.exchange

    @Test
    public void sendToFanoutQueue1(){
        String exchangeName="myFanout.exchange";
        String message ="hello myFanout.exchange";
        rabbitTemplate.convertAndSend(exchangeName,"",message);
        System.out.println(LocalTime.now()+"成功发送消息【"+message+"】myFanout.exchange");
    }

小结:
FanoutExchange交换机会将消息路由到与之绑定的所有队列。
不能缓存消息,路由失败,消息会丢失。

5.DirectExchange

与FanoutExchange类似,但是DirectExchange不是将消息路由到绑定的队列,而是根据RutingKey来路由到与之绑定的队列。(增加了RutingKey的条件)

在consumer微服务中,基于注解@QueueBinding进行交换机、队列、Rutingkey的声明和绑定

//接收 direct.queue1消息,基于注解@QueueBinding进行队列和交换机的绑定
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "direct.queue1"),exchange=@Exchange(name = "myDirect.exchange",type = ExchangeTypes.DIRECT),key = {"blue","red"}))
public void listenToDirectQueue1(String msg){
	System.out.println(LocalTime.now()+"接收到来自direct.queue1的消息【"+msg+"】");
}

//接收 direct.queue2消息,基于注解@QueueBinding进行队列和交换机的绑定
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "direct.queue2"),exchange=@Exchange(name = "myDirect.exchange",type = ExchangeTypes.DIRECT),key = {"green","red"}))
public void listenToDirectQueue2(String msg){
	System.out.println(LocalTime.now()+"接收到来自direct.queue2的消息【"+msg+"】");
}

启动consumer服务,查看rabbitmq客户端,两个队列成功绑定到交换机
image

在publisher微服务中编写消息发送代码,将消息发送到交换机myDirecct.exchange

@Test
public void sendToDirectExchange(){
	String exchangeName="myDirect.exchange";
	String message ="hello myDirect.exchange";
	rabbitTemplate.convertAndSend(exchangeName,"blue",message);
	System.out.println(LocalTime.now()+"成功发送消息【"+message+"】myDirect.exchange");
}

测试:
发消息:
image

接收消息:
同一个RutingKey
image

不同RutingKey
image

image

小结:
DirectExchange交换机会将消息根据RutingKey路由到与之绑定的对应的队列上。如果队列的Rutingkey都相同,则相当于FanoutExchange,即消息发送到所有队列。

6.TopicExchange

与DirectExchange类似,但是其RutingKey是由多个单词组成的,可以使用通配符

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

在consumer微服务中,基于注解@QueueBinding进行交换机、队列、Rutingkey的声明和绑定

    //5.接收 topic.queue1消息,基于注解@QueueBinding进行队列和交换机的绑定
    @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"), exchange = @Exchange(name = "myTopic.exchange", type = ExchangeTypes.TOPIC), key = "china.#"))
    public void listenToTopicQueue1(String msg) {
        System.out.println(LocalTime.now() + "接收到来自topic.queue1的消息【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"), exchange = @Exchange(name = "myTopic.exchange", type = ExchangeTypes.TOPIC), key = "*.news"))
    public void listenToTopicQueue2(String msg) {
        System.out.println(LocalTime.now() + "接收到来自topic.queue2的消息【" + msg + "】");
    }

启动consumer服务,查看rabbitmq客户端,两个队列成功绑定到交换机
image

在publisher微服务中编写消息发送代码,将消息发送到交换机myTopic.exchange

    //5.消息订阅(话题)- myTopic.exchange
    @Test
    public void sendToTopicExchange(){
        String exchangeName="myTopic.exchange";
        String message="hello myTopic.exchange,北京今天是晴天!";
        rabbitTemplate.convertAndSend(exchangeName,"china.weather",message);
        System.out.println(LocalTime.now()+"成功发送消息【"+message+"】到myTopic.exchange");
    }

测试1:RutingKey=china.news
发消息:
image

接收消息:
image

测试2:RutingKey=china.weather
发消息:
image

接收消息:
image

小结:
TopicExchange使用的Rutingkey采用多个单词组成,使用点号分隔,可以使用通配符。
TopicExchange通过RutingKey来将消息路由给队列。

6.转换器-序列化方法优化

在consumer微服务中创建一个object.queue队列

    @Bean
    public Queue objectQueue(){
        return new Queue("object.queue");
    }

在publisher微服务中编写对象类型的消息发送器,向object.queue队列发送对象类型的消息

package com.lqy.mqpublisher.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private int id;
    private String name;
    private int age;
    private String email;
}

    //6.测试发送对象类型的消息
    @Test
    public void sendUserToObjectQueue1(){
        String queueName="object.queue";
        User user = new User();
        user.setId(1001);
        user.setName("Lily");
        user.setAge(24);
        user.setEmail("lily@163.com");
        rabbitTemplate.convertAndSend(queueName,user);
        System.out.println(LocalTime.now()+"发送对象类型消息\n"+user+"\n到object.queue");
    }

    @Test
    public void sendUserToObjectQueue2(){
        String queueName="object.queue";
        Map<String,Object> map = new HashMap<>();
        map.put("name","张三");
        map.put("age",24);
        rabbitTemplate.convertAndSend(queueName,map);
        System.out.println(LocalTime.now()+"发送对象类型消息\n"+map+"\n到object.queue");
    }

启动consumer微服务,查看消息

通过打断点调试,可以发现对于对象类型的消息RabbitTemplate会使用MessageConverter来进行序列化,但是序列化后可读性很差,并且占用较大的内存空间。

image

编写消息接收代码:

    @RabbitListener(queues = "object.queue")
    public void listenToObjectQueue2(Map<String ,Object> msg){
        System.out.println(LocalTime.now()+"接收到来自object.queue的消息:\n"+msg);
    }

重启consumer微服务,接收到的对象类型消息也是序列化的代码
image

对象类型消息序列化优化:
两个微服务中都导入以下依赖,使用json方式进行序列化

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

在两个微服务的启动类中添加Bean

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

测试:
重启服务,发送对象类型的消息
发消息:
image

rabbitmq客户端:
image

接收消息:
image

小结:
RabbitTemplate默认的MessageConver方式可以使用Jackson2JsonMessageConverter进行优化,即将对象类型的消息转换(序列化)成json格式。

问题 & 解决方案

1.在创建springboot工程后可以在编写逻辑代码前先运行启动类,保证启动类能正常启动,防止后续出现启动异常等问题。
2.启动类报错找不到主类时,可以使用maven-clean和maven-install命令,之后再次尝试启动。
3.test的包名要和main/java下的包名一致,否则出现找不到配置类,RabbitTemplate注入不成功的问题
image

posted on 2023-03-14 18:05  橙子blues  阅读(19)  评论(0)    收藏  举报

导航