RabbitMQ

目录

一、RabbitMQ入门

1、消息中间件RabbitMQ

2、RabbitMQ的安装部署配置

3、RabbitMQ的生产者消费者演示

二、交换机

1.交换机简介

2.直连交换机实操

 创建一个主模块rabbitmq02

 建立子模块rabbitmq-provider  

  建立子模块rabbitmq-consumer 

生产者生产消息

 消费者消费消息

3.主题交换机实操

生产者发送消息

 消费者订阅消息

4.扇形交换机实操

生产者发送消息

 消费者订阅消息

三、延迟队列

1.延迟队列的应用场景

2.延迟队列中的消息投递

3.延迟队列的消息消费


一、RabbitMQ入门

1、消息中间件RabbitMQ

什么是MQ
   消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已

   作用:应用程序“对”应用程序的通信方法。

应用场景
   主要解决异步处理、应用解耦、流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构
 
   1.异步处理
     用户注册后,需要发注册邮件和注册短信

   2.应用解耦
     用户下单后,订单系统需要通知库存系统

   3.流量削锋(重点)
     流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛

     应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列
     a、可以控制活动的人数
     b、可以缓解短时间内高流量压垮应用
     用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
     秒杀业务根据消息队列中的请求信息,再做后续处理

   4.日志处理
     日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题
     1.日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
     2.Kafka消息队列,负责日志数据的接收,存储和转发
     3.日志处理应用:订阅并消费kafka队列中的日志数据

 用户注册-串行方式

用户注册-并行方式

RabbitMQ_第1张图片

用户注册-消息队列

RabbitMQ_第2张图片

应用解耦(客户下单)

RabbitMQ_第3张图片

 RabbitMQ_第4张图片

 流量削锋(秒杀)

 RabbitMQ_第5张图片

 日志处理

RabbitMQ_第6张图片

 MQ选型对比

RabbitMQ_第7张图片

2、RabbitMQ的安装部署配置

停掉所有的容器

docker stop $(docker ps -aq)

 查询镜像

docker search rabbitmq:management 

RabbitMQ_第8张图片

 获取镜像

docker pull rabbitmq:management

RabbitMQ_第9张图片

 运行镜像


   ##方式一:默认guest用户,密码也是guest

   $ docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

   ##方式二:设置用户名和密码

   $ docker run -d \
     --name my-rabbitmq \
     -p 5672:5672 -p 15672:15672 \
     -v /data:/var/lib/rabbitmq \
     --hostname my-rabbitmq-host \
     -e RABBITMQ_DEFAULT_VHOST=my_vhost \
     -e RABBITMQ_DEFAULT_USER=admin \
     -e RABBITMQ_DEFAULT_PASS=admin \
     --restart=always \
     rabbitmq:management 

参数说明:
   -d:后台运行容器
   -name:指定容器名
   -p:指定服务运行的端口(5672:应用访问端口;15672:控制台Web端口号)
   -v:映射目录或文件,启动了一个数据卷容器,数据卷路径为:/var/lib/rabbitmq,再将此数据卷映射到住宿主机的/data目录
   --hostname:主机名(RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据,默认为主机名)
   -e:指定环境变量;(
RABBITMQ_DEFAULT_VHOST:默认虚拟机名;
RABBITMQ_DEFAULT_USER:默认的用户名;
RABBITMQ_DEFAULT_PASS:默认用户名的密码)
   --restart=always:当Docker重启时,容器能自动启动   
   rabbitmq:management:镜像名
   
   注1:RABBITMQ_DEFAULT_VHOST=my_vhost,my_vhost名字请记好,在之后的编程中要用到,
        如果启动时没指定,默认值为/

   #4.进入RabbitMQ管理平台进行相关操作
    

   注1:容器启动后,可以通过docker logs 窗口ID/容器名字 查看日志
        docker logs my-rabbitmq    
   注2:停止并删除所有容器
        docker stop $(docker ps -aq) && docker rm $(docker ps -aq)

 设置用户名和密码

RabbitMQ_第10张图片

 RabbitMQ管理平台

后台地址:http://[宿主机IP]:15672
 默认账号:guest/guest,用户也可以自己创建新的账号,例如:admin/admin

RabbitMQ_第11张图片

 登录之后

RabbitMQ_第12张图片

 添加新用户

RabbitMQ_第13张图片

 填好信息RabbitMQ_第14张图片

 此时没有操作my_vhost(我的虚拟机)的权限RabbitMQ_第15张图片

 授权 点击用户名

RabbitMQ_第16张图片

 授权成功RabbitMQ_第17张图片

 退出用新用户登录RabbitMQ_第18张图片

3、RabbitMQ的生产者消费者演示

用idea新建一个项目

注意的两个点

一定是maven项目

RabbitMQ_第19张图片

 RabbitMQ_第20张图片

 添加pom依赖


       org.springframework.boot
       spring-boot-starter-amqp
     



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.2
         
    
    com.cdl
    rabbitmq01
    0.0.1-SNAPSHOT
    rabbitmq01
    Demo project for Spring Boot
    
        8
    
    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            junit
            junit
            test
        


    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


 测试一下依赖

RabbitMQ_第21张图片

rabbitmq连接配置

     server.port=8080
     ## rabbitmq config
     spring.rabbitmq.host=192.168.199.144
     spring.rabbitmq.port=5672
     spring.rabbitmq.username=springboot
     spring.rabbitmq.password=123456
     
     ## 与启动容器时虚拟主机名字一致~~~与启动容器时虚拟主机名字一致~~~与启动容器时虚拟主机名字一致~~~
     spring.rabbitmq.virtual-host=my_vhost

RabbitMQ_第22张图片

 写入

创建Rabbit配置类RabbitConfig 
配置类主要用来配置队列、交换器、路由等高级信息

package com.cdl.rabbitmq01.config;

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

@Configuration
     public class RabbitConfig {
       @Bean
       public Queue firstQueue() {
         // 创建一个队列,名称为:first
         return new Queue("first");
       }
     }

 创建消息产生者类

package com.cdl.rabbitmq01.component;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
     public class Sender {
       @Autowired
       private AmqpTemplate rabbitTemplate;

       public void send() {

           rabbitTemplate.convertAndSend("first", "test rabbitmq message !!!");
       }
     }

 创建测试类 

package com.cdl.rabbitmq01;

import com.cdl.rabbitmq01.component.Sender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
 @SpringBootTest
     public class RabbitmqTest {

         @Autowired
         private Sender sender;

         @Test
         public void testRabbitmq() throws Exception {
             sender.send();
         }
     }
    

 运行RabbitMQ_第23张图片

RabbitMQ_第24张图片

 RabbitMQ_第25张图片

 有消息产生者类中内容RabbitMQ_第26张图片

 运行三次测试类

RabbitMQ_第27张图片

 创建消息消费者

package com.cdl.rabbitmq01.component;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
     @RabbitListener(queues = "first")
     public class Receiver {
         @RabbitHandler
         public void process(String msg) {
             System.out.println("receive msg : " + msg);
         }
     }
    

讲send方法注掉

RabbitMQ_第28张图片

 再运行改测试类

RabbitMQ_第29张图片

RabbitMQ_第30张图片

 这个演示对应的就是应用解耦的那张图,当很多订单被写入时服务器宕机,此时库存系统的数据就会清零,怎么解决呢?我们可以在订阅的过程中设置限流,一次性处理定量的请求

RabbitMQ_第31张图片

二、交换机

1.交换机简介

Exchange(交换机)的作用
   在RabbitMQ中,生产者发送消息不会直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列,
   队列再将消息以推送或者拉取方式给消费者进行消费

          创建消息              路由键         pull/push
   生产者------------>交换机------------>队列------------>消费者

RabbitMQ交换机的作用

RabbitMQ_第32张图片

Exchange(交换机)的类型
   1.直连交换机:Direct Exchange
     直连交换机是一种带路由功能的交换机,一个队列会和一个交换机绑定,除此之外再绑定一个routing_key,当消息被发送的时候,需要指定一个binding_key,这个消息被送达交换机的时候,就会被这个交换机送到指定的队列里面去。同样的一个binding_key也是支持应用到多个队列中的。

     这样当一个交换机绑定多个队列,就会被送到对应的队列去处理。

     注1:什么是路由键 
          每个消息都有一个称为路由键(routing key)的属性,它其实就是一个简单的字符串

     注2:直连交换机适用场景
          有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。


   2.主题交换机:Topic Exchange
     直连交换机的缺点!
     直连交换机的routing_key方案非常简单,如果我们希望一条消息发送给多个队列,那么这个交换机需要绑定上非常多的routing_key,
     假设每个交换机上都绑定一堆的routing_key连接到各个队列上。那么消息的管理就会异常地困难。
     
     所以RabbitMQ提供了一种主题交换机,发送到主题交换机上的消息需要携带指定规则的routing_key,
     主题交换机会根据这个规则将数据发送到对应的(多个)队列上。

     主题交换机的routing_key需要有一定的规则,交换机和队列的binding_key需要采用*.#.*.....的格式,每个部分用.分开,其中
     *表示一个单词 
     #表示任意数量(零个或多个)单词。

     示例:
     队列Q1绑定键为 *.TT.*
     队列Q2绑定键为TT.#

     如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到 
     如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到


   3.扇形交换机:Fanout Exchange
     扇形交换机是最基本的交换机类型,它所能做的事情非常简单———广播消息。
     扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要“思考”,
     所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。 

     这个交换机没有路由键概念,就算你绑了路由键也是无视的。 

   4.首部交换机:Headers exchange

   5.默认交换机
     实际上是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange)。它有一个特殊的属性使得它对于
     简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。

     如:当你声明了一个名为”hello”的队列,RabbitMQ会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为”hello”。
     因此,当携带着名为”hello”的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为”hello”的队列中
  
     类似amq.*的名称的交换机:
     这些是RabbitMQ默认创建的交换机。这些队列名称被预留做RabbitMQ内部使用,不能被应用使用,否则抛出403 (ACCESS_REFUSED)错误 

   6.Dead Letter Exchange(死信交换机)
     在默认情况,如果消息在投递到交换机时,交换机发现此消息没有匹配的队列,则这个消息将被悄悄丢弃。
     为了解决这个问题,RabbitMQ中有一种交换机叫死信交换机。当消费者不能处理接收到的消息时,将这个消息重新发布到另外一个队列中,
     等待重试或者人工干预。这个过程中的exchange和queue就是所谓的”Dead Letter Exchange 和 Queue

 直连交换机-单个绑定

RabbitMQ_第33张图片

 直连交换机-多个绑定

RabbitMQ_第34张图片

交换机的属性
   除交换机类型外,在声明交换机时还可以附带许多其他的属性,其中最重要的几个分别是:
   Name:交换机名称
   Durability:是否持久化。如果持久性,则RabbitMQ重启后,交换机还存在
   Auto-delete:当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
   Arguments:扩展参数

2.直连交换机实操

打开虚拟机以及虚拟机的连接工具和我们的开发工具idea

要确保我们的rabbitMQ的后端能够连上

RabbitMQ_第35张图片

 能够登录进去RabbitMQ_第36张图片

 创建一个主模块rabbitmq02

注意:主模块是一个maven项目 记得要修改maven的地址

RabbitMQ_第37张图片

 创建完成后删除src 避免子模块的代码写错位置

RabbitMQ_第38张图片

 建立子模块rabbitmq-provider  

生产者模块 这是一个spring项目

RabbitMQ_第39张图片

 选择maven工程RabbitMQ_第40张图片

 RabbitMQ_第41张图片

然后下一步就欧克了

  建立子模块rabbitmq-consumer 

消费者模块和生产者模块一样

RabbitMQ_第42张图片

 创建子模块完成后,给子模块添加pom依赖


       org.springframework.boot
       spring-boot-starter-amqp
     

     
       org.springframework.boot
       spring-boot-starter-web
     

     
       org.projectlombok
       lombok
       true
     

 

 生产者模块的



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.6
         
    
    com.cdl
    rabbitmq-provider
    0.0.1-SNAPSHOT
    rabbitmq-provider
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
            true
        


    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


消费者模块



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.6
         
    
    com.cdl
    rabbitmq-consumer
    0.0.1-SNAPSHOT
    rabbitmq-consumer
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
            true
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


主模块的不需要改变

生产者生产消息

配置类RabbitmqDirectConfig 

package com.cdl.rabbitmqprovider.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 17:29
 */
@Configuration
public class RabbitmqDirectConfig {

    //直连交换机对应的队列
    @Bean
    public Queue directQueue(){
        return new Queue("cdl-direct-Queue");
    }

    //直连交换机
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("cdl-direct-Exchange");
    }

    //直连交换机与对列的绑定关系
    @Bean
    public Binding directBinding(){
        return BindingBuilder.bind(directQueue())
                .to(directExchange())
                .with("direct_routing_key");
    }

}

 SendMessageController 

package com.cdl.rabbitmqprovider.controller;

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

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 17:48
 */
@RestController
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/sendDirect")
    public Map sendDirect(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }

}

application.yml 消费者的是一样的除了端口

server:
  port: 8080
spring:
  rabbitmq:
    host: 192.168.26.128
    password: 123456
    port: 5672
    username: cdl
    virtual-host: my_vhost

运行启动类

RabbitMQ_第43张图片

 效果图

RabbitMQ_第44张图片

 RabbitMQ_第45张图片

 可以看见 接收到了 当我们咋访问页面重复刷新时 接收的数量不会发生改变

RabbitMQ_第46张图片

 RabbitMQ_第47张图片

 刷新几次队列的此时就是多少RabbitMQ_第48张图片

 消费者消费消息

改变端口 

RabbitMQ_第49张图片

创建消息接收监听类 DirectReceiver 

package com.cdl.rabbitmqconsumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"cdl-direct-Queue"})
public class DirectReceiver {
	@RabbitHandler
	public void handler(Map msg){
		System.out.println(msg);
	}
}

运行启动类

RabbitMQ_第50张图片

 再查看

RabbitMQ_第51张图片

 RabbitMQ_第52张图片

注1:新版jdk日期及格式化
          LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

     注2:rabbitTemplate和amqpTemplate有什么关系
          查看源码中会发现rabbitTemplate实现自amqpTemplate接口,两者使用起来并无区别,功能一致

     注3:不要@Configuration写成了@Configurable,这两个长得很像
          @Configuration该注解是可以用来替代XML文件。
          手动new出来的对象,正常情况下,Spring是无法依赖注入的,这个时候可以使用@Configurable注解

3.主题交换机实操

生产者发送消息

RabbitTopicConfig 

package com.cdl.rabbitmqprovider.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 18:52
 */
@Configuration
public class RabbitTopicConfig {//主题交换机的配置类

    //对列
    @Bean
    public Queue topicQueueA(){
        return new Queue("cdl-topic-queue-a");
    }

    @Bean
    public Queue topicQueueB(){
        return new Queue("cdl-topic-queue-b");
    }

    @Bean
    public Queue topicQueueC(){
        return new Queue("cdl-topic-queue-c");
    }

    //交换机
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("cdl-topic-Exchange");
    }


    //绑定关系
    @Bean
    public Binding binding1(){
            return BindingBuilder.bind(topicQueueA())
                    .to(topicExchange())
                    .with("cdl.person.xx");
    }

    @Bean
    public Binding binding2(){
        return BindingBuilder.bind(topicQueueB())
                .to(topicExchange())
                .with("cdl.person.yy");
    }


    @Bean
    public Binding binding3(){
        return BindingBuilder.bind(topicQueueC())
                .to(topicExchange())
                .with("cdl.person.*");
    }





}

 SendMessageController 中添加一个方法

package com.cdl.rabbitmqprovider.controller;

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

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 17:48
 */
@RestController
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //直连交换机推送消息
    @RequestMapping("/sendDirect")
    public Map sendDirect(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }

    //主题交换机推送消息
    @RequestMapping("/sendTopic")
    public Map sendTopic(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","主题交换机 cdl-topic-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-topic-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }


}

测试一下


RabbitMQ_第53张图片

 RabbitMQ_第54张图片

 ②

 RabbitMQ_第55张图片

 ③

RabbitMQ_第56张图片

 RabbitMQ_第57张图片

 消费者订阅消息

TopicReceiver 

package com.cdl.rabbitmqconsumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 19:33
 */
@Component
public class TopicReceiver {

    @RabbitListener(queues = {"cdl-topic-queue-a"})
    @RabbitHandler
    public void handler1(Map msg){
        System.out.println("消费者订阅:cdl-topic-queue-a队列的消息为:"+msg);
    }


    @RabbitListener(queues = {"cdl-topic-queue-b"})
    @RabbitHandler
    public void handler2(Map msg){
        System.out.println("消费者订阅:cdl-topic-queue-b队列的消息为:"+msg);
    }

    @RabbitListener(queues = {"cdl-topic-queue-c"})
    @RabbitHandler
    public void handler3(Map msg){
        System.out.println("消费者订阅:cdl-topic-queue-c队列的消息为:"+msg);
    }

}

运行启动类

RabbitMQ_第58张图片

RabbitMQ_第59张图片

4.扇形交换机实操

生产者发送消息

RabbitmqFanoutConfig 

package com.cdl.rabbitmqprovider.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;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 19:44
 */
@Configuration
public class RabbitmqFanoutConfig {//扇形交换机的配置类

    //对列
    @Bean
    public Queue fanoutQueueA(){
        return new Queue("cdl-fanout-queue-a");
    }

    @Bean
    public Queue fanoutQueueB(){
        return new Queue("cdl-fanout-queue-b");
    }

    @Bean
    public Queue fanoutQueueC(){
        return new Queue("cdl-fanout-queue-c");
    }

    //交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("cdl-fanout-Exchange");
    }


    //绑定关系
    @Bean
    public Binding bindingA(){
        return BindingBuilder.bind(fanoutQueueA())
                .to(fanoutExchange());
    }

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


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

}

 SendMessageController 中添加一个方法

package com.cdl.rabbitmqprovider.controller;

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

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 17:48
 */
@RestController
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //直连交换机推送消息
    @RequestMapping("/sendDirect")
    public Map sendDirect(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }

    //主题交换机推送消息
    @RequestMapping("/sendTopic")
    public Map sendTopic(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","主题交换机 cdl-topic-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-topic-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }


    //扇形交换机推送消息
    @RequestMapping("/sendFanout")
    public Map sendFanout(){
        Map msg = new HashMap();
        msg.put("msg","扇形交换机 cdl-fanout-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-fanout-Exchange",null,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }


}

运行启动类

RabbitMQ_第60张图片

 RabbitMQ_第61张图片

 RabbitMQ_第62张图片

 再刷新一次

RabbitMQ_第63张图片

 消费者订阅消息

FanoutReceiver 

package com.cdl.rabbitmqconsumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 20:02
 */
@Component
public class FanoutReceiver {

    @RabbitListener(queues = {"cdl-fanout-queue-a"})
    @RabbitHandler
    public void handler1(Map msg){
        System.out.println("消费者订阅:cdl-fanout-queue-a队列的消息为:"+msg);
    }


    @RabbitListener(queues = {"cdl-fanout-queue-b"})
    @RabbitHandler
    public void handler2(Map msg){
        System.out.println("消费者订阅:cdl-fanout-queue-b队列的消息为:"+msg);
    }

    @RabbitListener(queues = {"cdl-fanout-queue-c"})
    @RabbitHandler
    public void handler3(Map msg){
        System.out.println("消费者订阅:cdl-fanout-queue-c队列的消息为:"+msg);
    }

}

RabbitMQ_第64张图片

 RabbitMQ_第65张图片

三、延迟队列

1.延迟队列的应用场景

1. 场景:“订单下单成功后,15分钟未支付自动取消”
   1.传统处理超时订单
     采取定时任务轮训数据库订单,并且批量处理。其弊端也是显而易见的;对服务器、数据库性会有很大的要求,
     并且当处理大量订单起来会很力不从心,而且实时性也不是特别好。当然传统的手法还可以再优化一下,
     即存入订单的时候就算出订单的过期时间插入数据库,设置定时任务查询数据库的时候就只需要查询过期了的订单,
     然后再做其他的业务操作 

   2.rabbitMQ延时队列方案
     一台普通的rabbitmq服务器单队列容纳千万级别的消息还是没什么压力的,而且rabbitmq集群扩展支持的也是非常好的,
     并且队列中的消息是可以进行持久化,即使我们重启或者宕机也能保证数据不丢失

死信队列产生流程

RabbitMQ_第66张图片

2. TTL和DLX
   rabbitMQ中是没有延时队列的,也没有属性可以设置,只能通过死信交换机(DLX)和设置过期时间(TTL)结合起来实现延迟队列

   1.TTL
     TTL是Time To Live的缩写, 也就是生存时间。
     RabbitMq支持对消息和队列设置TTL,对消息这设置是在发送的时候指定,对队列设置是从消息入队列开始计算, 只要超过了队列的超时时间配置, 那么消息会自动清除。
     如果两种方式一起使用消息的TTL和队列的TTL之间较小的为准,也就是消息5s过期,队列是10s,那么5s的生效。
     默认是没有过期时间的,表示消息没有过期时间;如果设置为0,表示消息在投递到消费者的时候直接被消费,否则丢弃。

     设置消息的过期时间用 x-message-ttl 参数实现,单位毫秒。
     设置队列的过期时间用 x-expires 参数,单位毫秒,注意,不能设置为0。

     消息:生产者 -> 交换机 消息在生产者制造消息的时候就开始计算了TTL  TTL=5
     队列:生产者 -> 交换机 -> 路由键 -> 队列 当消息送达到队列的时候才开始计算TTL  TTL=10
     

   2.DLX和死信队列
     DLX即Dead-Letter-Exchange(死信交换机),它其实就是一个正常的交换机,能够与任何队列绑定。

     死信队列是指队列(正常)上的消息(过期)变成死信后,能够发送到另外一个交换机(DLX),然后被路由到一个队列上,
     这个队列,就是死信队列

     成为死信一般有以下几种情况:
     消息被拒绝(basic.reject or basic.nack)且带requeue=false参数
     消息的TTL-存活时间已经过期
     队列长度限制被超越(队列满)

     
     注1:如果队列上存在死信, RabbitMq会将死信消息投递到设置的DLX上去 ,
     注2:通过在队列里设置x-dead-letter-exchange参数来声明DLX,如果当前DLX是direct类型还要声明
          x-dead-letter-routing-key参数来指定路由键,如果没有指定,则使用原队列的路由键

2.延迟队列中的消息投递

准备工作打开我们的虚拟机,连接工具,确保环境是没有问题的

RabbitMQ_第67张图片

 我们先登录进去,之后再打开我们的idea

在生产者的项目中编写配置类RabbitmqDLXConfig 

package com.cdl.rabbitmqprovider.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 17:29
 */
@Configuration
public class RabbitmqDLXConfig {

    //普通的
    public  final static String NORMAL_QUEUE = "normal_queue";
    public  final static String NORMAL_EXCHANGE = "normal_exchange";
    public  final static String NORMAL_ROUTING_KEY = "normal_routing_key";


    //延迟的 delay
    public  final static String DLX_QUEUE = "dlx_queue";
    public  final static String DLX_EXCHANGE = "dlx_exchange";
    public  final static String DLX_ROUTING_KEY = "dlx_routing_key";

    //普通交换机、队列、routing_key
    @Bean
    public Queue normalQueue(){
        //在正常队列中要添加参数,2:25发送的消息 要在2:40发送到死信交换机中,再由死信交换机路由到死信队列
        Map map = new HashMap();
        map.put("x-message-ttl", 30000);//message在该队列queue的存活时间最大为10秒(10000)
        map.put("x-dead-letter-exchange", DLX_EXCHANGE); //x-dead-letter-exchange参数是设置该队列的死信交换器(DLX)
        map.put("x-dead-letter-routing-key", DLX_ROUTING_KEY);//x-dead-letter-routing-key参数是给这个DLX指定路由键
        return new Queue(NORMAL_QUEUE,true,false,false,map);
    }

    @Bean
    public DirectExchange normalDirectExchange(){
        return new DirectExchange(NORMAL_EXCHANGE,true,false);
    }

    @Bean
    public Binding normalBinding(){
        return BindingBuilder.bind(normalQueue())
                .to(normalDirectExchange())
                .with(NORMAL_ROUTING_KEY);
    }

    //死信交换机、队列、routing_key
    @Bean
    public Queue dlxQueue(){
        return new Queue(DLX_QUEUE);
    }

    @Bean
    public DirectExchange dlxDirectExchange(){
        return new DirectExchange(DLX_EXCHANGE);
    }

    @Bean
    public Binding dlxBinding(){
        return BindingBuilder.bind(dlxQueue())
                .to(dlxDirectExchange())
                .with(DLX_ROUTING_KEY);
    }

}

 SendMessageController 中添加方法

package com.cdl.rabbitmqprovider.controller;

import com.cdl.rabbitmqprovider.config.RabbitmqDLXConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cdl
 * @site www.cdl.com
 * @create 2022-12-21 17:48
 */
@RestController
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //直连交换机推送消息
    @RequestMapping("/sendDirect")
    public Map sendDirect(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }

    //主题交换机推送消息
    @RequestMapping("/sendTopic")
    public Map sendTopic(String routingKey){
        Map msg = new HashMap();
        msg.put("msg","主题交换机 cdl-topic-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-topic-Exchange",routingKey,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }


    //扇形交换机推送消息
    @RequestMapping("/sendFanout")
    public Map sendFanout(){
        Map msg = new HashMap();
        msg.put("msg","扇形交换机 cdl-fanout-Exchange 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend("cdl-fanout-Exchange",null,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }

    //死信交换机发送消息
    @RequestMapping("/sendDLX")
    public Map sendDLX(){
        Map msg = new HashMap();
        msg.put("msg","死信交换机 发送的消息");
        msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        rabbitTemplate.convertAndSend(RabbitmqDLXConfig.NORMAL_EXCHANGE,RabbitmqDLXConfig.NORMAL_ROUTING_KEY,msg);
        Map res = new HashMap();
        res.put("code",200);
        res.put("msg","成功");
        return res;
    }


}

运行启动类

RabbitMQ_第68张图片

 再到第一张图的页面去刷新一次

RabbitMQ_第69张图片

 再等大概30秒

RabbitMQ_第70张图片

3.延迟队列的消息消费

在消费者中写消费的方法

package com.cdl.rabbitmqconsumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"dlx_queue"})
public class DLXReceiver {
@RabbitHandler
public void handler(Map msg){
	//修改订单的状态的业务逻辑写在这
		System.out.println("死信队列中接收到的消息:"+msg);
	}
}

运行启动类

RabbitMQ_第71张图片

 RabbitMQ_第72张图片

 好啦 RabbitMQ的分享就到这里啦 下次分享的技术为微信小程序

喜欢就点个赞或者关注吧

你可能感兴趣的:(Docker,java-rabbitmq,rabbitmq,分布式)