SpringAMQP

官方地址:Spring AMQP

特性:

异步处理消息如栈的监听容器,即监听消息
Listener container for asynchronous processing of inbound messages
使用RabbitTamplate进行消息的接收和发送
RabbitTemplate for sending and receiving messages
自动创建队列、交换机,绑定。
RabbitAdmin for automatically declaring queues, exchanges and bindings

实现流程:

1、导入依赖(依赖版本要与springboot版本对应)

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.6.13</version>
</dependency>

2、生产者

// 在生产者代码中使用RabbitTemplate来进行消息的发送,发送到hzy_queue_name队列
public class ProducerMQ {

    private RabbitTemplate rabbitTemplate;

    @Autowired
    private void setRabbitTemplate(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void testProducer(){
        String channelName = "test_change";
        String queueName = "test_queue";
        String message = "hello RabbitMQ !!!!";
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

3、消费者

// 在消费者代码中实现消费逻辑,并绑定到hzy_queue_name该队列
@Component
public class ConsumerMQ {

    @RabbitListener(queues = "hzy_queue_name")
    public void testConsumer(String message){
        System.out.println(message);
    }

}

work queue(工作队列)

工作队列中,没有交换机概念,即生产者/队列/消费者,该方式做到一条消息只能被一个消费者消费。

模拟一个队列有两个消费者时出现:不论两个消费者消费速度如何,消息总是平均分配给了两给消费者。

原因:由于RabbitMQ默认有一个“消息预取”机制,也就是当队列有消息时,消费者1和消费者2会提前将消息平均拿到,然后再处理。

解决:可以在application.yml配置文件中进行配置,配置键:preFetch,可以设置该值来限制预取消息的上限。该值默认时无上限,即有多少消息,则拿多少消息。

配置:

spring:
  rabbitmq:
    host: 192.168.81.3 # rabbitmq主机地
    port: 5672 # rabbitmq连接端口
    virtual-host: / # rabbitmq虚拟主机
    username: hzy # rabbitmq用户名
    password: 123456 # rabbitmq密码
    listener:
      simple:
        prefetch: 1 # 设置消息预取上限为1,即处理完了在去拿下一条消息

发布(Publish)、订阅(Subscribe)

发布订阅中,允许将同一消息发送给多个消费者。实现方式是新增了交换机(exchange)概念。

交换机只负责消息的转发,不负责消息的存储;即在转发过程中存在消息丢失的情况。


Fanout(广播)类型交换机:会将消息转发给与该交换机绑定的每一个队列

code实现:

// 生产者代码
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void fanoutExchange() throws Exception{

        String exchangeName = "hzy.exchange";
        String message = "hello everyone!";

        rabbitTemplate.convertAndSend(exchangeName, "", message);
    }
// 配置类代码
@Configuration
public class FanoutConfig {
    // 声明交换机: hzy.exchange
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("hzy.exchange");
    }

    // 声明队列1: hzy.queue1
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("hzy.queue1");
    }

    // 声明队列2: hzy.queue2
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("hzy.queue2");
    }

    // 将队列1绑定到交换机
    @Bean
    public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    // 将队列2绑定到交换机
    @Bean
    public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}
// 消费者代码
    @RabbitListener(queues = "hzy.queue1")
    public void fanoutQueue1(String message){
        System.out.println("广播消费者1收到消息: " + message);
    }

    @RabbitListener(queues = "hzy.queue2")
    public void fanoutQueue2(String message){
        System.out.println("广播消费者2收到消息: " + message);
    }

Direct(路由)类型交换机:会根据指定的路由规则将消息路由到指定的队列;每一个Queue(队列)都会与Exchange(路由)设置一个BindingKey。当发布者发布消息的时候,指定消息的RoutingKey。Exchange将消息路由到BindingKey与消息队列的RooutingKey一致的队列。其中一个队列可以绑定多个RoutingKey就好像一个用户可以对应多个角色一样。

code实现:

// 生产者代码
    @Test
    void directExchange() throws Exception{

        String exchangeName = "direct.exchange";
        String message = "hello blue!";

        rabbitTemplate.convertAndSend(exchangeName, "blue", message);
    }

// 消费者代码
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "direct.exchange", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void directQueue1(String message){
        System.out.println("directQueue1: " + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "direct.exchange", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void directQueue2(String message){
        System.out.println("directQueue2: " + message);
    }

Topic(话题)类型交换机:TopicExchange与DirectExchange非常相似,区别在于routingKey多个单词的列表,并且以"."进行分割。如:china.news代表中国的新闻消息,china.weather代表中国的天气消息,japan.news代表日本新闻消息,japan.weather代表日期天气消息

解决了direct中如果后续我需要在订阅美国的天气,那么就需要重新添加有关美国的routingKey。

而在Topic中Queue与Exchange指定BindingKey时可以使用通配符来进行路由。

#:代指0个或多个单词
*:代指一个单词
 code实现:
// 生产者代码
    @Test
    void topicExchange(){

        String exchangeName = "topic.exchange";
        String message = "hhhhhhhhhhhhhhhh";
        rabbitTemplate.convertAndSend(exchangeName, "paner.news", message);
        rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
        rabbitTemplate.convertAndSend(exchangeName, "china.hello", message);
    }

// 消费者代码
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),
            key = "china.#"
    ))
    public void topicQueue1(String message){
        System.out.println("topicQueue1: " + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2"),
            exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),
            key = "#.news"
    ))
    public void topicQueue2(String message){
        System.out.println("topicQueue2: " + message);
    }

消息转换器

在spring中使用RabbitMQ,rabbitTemplate默认只用的消息处理对象是由org.springframework.amqp.support.converter.MessageConverter来处理。默认的实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。该方式不仅效率比较差,而且序列化后的内容比较占用内存。不推荐。消息转换器需要自定义实现。

使用默认消息转换器测试
    // 创建Bean代码
    // 消息转换器测试队列
    @Bean
    public Queue messageQueue(){
        return new Queue("converter.queue");
    }

// 发送消息代码
    @Test
    void messageConverterExchange(){
        Map<String, Object> message = new HashMap<>();
        message.put("name", "胡真勇");
        message.put("age", 22);
        message.put("sex", "男");
        rabbitTemplate.convertAndSend("converter.queue", message);
    }

消息:rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAADdAADc2V4dAAD
55S3dAAEbmFtZXQACeiDoeecn+WLh3QAA2FnZXNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoas
lR0LlOCLAgAAeHAAAAAWeA==
使用Jackson2JsonMessageConverter消息转换器实现
<!-- 导入依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
// 注入消息转换器Bean
// 在主启动类中或配置类中编写
@SpringBootApplication
public class ProducerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class, args);
    }

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

}

重新发送消息:{"sex":"男","name":"哈哈哈","age":22}
消费者接收消息
<!-- 导入依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
// 注入消息转换器Bean
// 在主启动类中或配置类中编写
@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

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

}

// 消息接收监听器
@RabbitListener(queues = "converter.queue")
public void converterQueue(Map<String, Object> message){
  System.out.println("converterQueue: " + message);
}

输出结果:converterQueue: {sex=男, name=哈哈哈, age=22}

 

posted @ 2023-09-05 21:27  可怜小屁孩儿  阅读(43)  评论(0)    收藏  举报