SpringBoot中集成RabbitMq及使用场景

在前面的博客中我们已经详细的介绍了RabbitMQ的5种模型,接下来让我们来看看在SpringBoot中如何使用RabbitMQ。

RabbitMQ5种模型回顾

1.点对点模型:生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。生产者和消费者是一一对应关系。如下图所示:
在这里插入图片描述

2.工作队列模型: 让多个消费者绑定到同一个队列中,共同消费队列中的消息。并且队列中的消息一旦消费,就会消息,因此任务不会被重复执行。如下图所示:
在这里插入图片描述
3. 广播模型: 可以将一个消息发送给多个消费者。如下图所示:
在这里插入图片描述
4.路由模型:可以通过RoutingKey实现不同的消息被不同的队列消费。如下图所示:
在这里插入图片描述
5.主题(Topic)模型: 和路由模型基本一致,只不过可以基于多重条件进行路由选择。如下图所示:
在这里插入图片描述

这5种模型,我在前面的博客中已经详细介绍了,没有了解的朋友,可以去看看我前面的博客。

SpringBoot整合RabbitMQ

1.导入依赖

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

2.在application.yml中加入如下配置

spring:
  rabbitmq:
    host: 172.16.114.135  //虚拟机的ip地址
    port: 5672 //端口号
    username: ems  //用户名
    password: 123  //密码
    virtual-host: /ems  //虚拟主机名称

这样导入RabbitMQ已经成功了,接下来讲一下RabbitMQ五种模式结合SpringBoot

HelloWorld

创建生产者

@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
public class TestRabbitMq {

    //注入rabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public  void  testHello(){
        //发送消息 参数1:队列的名称  参数2: 发送消息的内容
        //运行生产者并不会创建对应,需要有消费者才会去创建队列
        rabbitTemplate.convertAndSend("hello","hello world");
    }
 }

这里面的RabbitTemplate是SpringBoot封装好的用来操作消息队列的。convertAndSend是里面的其中一个方法,用作生产者匹配交换机、队列以及携带消息的。

创建消费者

@Component  //默认创建的队列是 持久化 非独占 不是自动删除队列
@RabbitListener(queuesToDeclare = @Queue(value = "hello",declare = "true")) 
//durable:是否持久化  autoDelete:是否自动删除  值都是String类型
public class Customer {

    @RabbitHandler //代表当我们从队列中取出消息时,是通过这个方法来处理消息的
    public  void  receivel(String message){
        System.out.println("message = "+message);
    }
}

@RabbitListener 是用来绑定队列的,queuesToDeclare属性: 用来创建队列的,如果没有就创建队列,否则不创建,和@Queue搭配使用。
@Queue:用来配置一个队列,主要有以下属性:

  • value: 队列名称
  • declare: 是否持久化队列
  • autoDelete:是否自动删除队列
  • exclusive: 是否独占队列, true:表示当前队列只允许当前连接可以,false:其他连接也可用当前队列
  • 需要注意的是:它们的值都是字符串类型,只能填"true" 或"false"
  • 如果只指定@Queue(“hello”) 那么默认创建的队列是持久化、非独占、不是自动删除的队列

@RabbitHandler:是用来表示该方法是消费者接收消息的方法

需要注意的是在SpringBoot中使用RabbitMQ和前面博客中使用RabbitMQ有一点区别:

  • 在前面博客中运行生产者就会直接创队列
  • 但是在SpringBoot中运行生产者并不会创建队列,创建队列是根据消费者来创建的,只有有消费者的时候才会去创建队列。

工作队列

1.生产者

   //注入rabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;
  
   @Test
   public  void testWork(){
       for(int i = 0;i<10;i++){
           rabbitTemplate.convertAndSend("work","hello work"+i);
       }
   }

2.消费者

@Component
public class WorkCustomer {

    @RabbitListener(queuesToDeclare = @Queue("work"))
    public  void receive1(String message){
        System.out.println("work message1 = "+message);
    }


    @RabbitListener(queuesToDeclare = @Queue("work"))
    public  void receive2(String message){
        System.out.println("work message2 = "+message);
    }
}

注意: 默认在Spring AMQP实现中Work这种方式就是公平调度。

如果消费者1处理业务比较快,早早的干完自己的活,消费者2处理业务比较慢,但是在RabbitMQ默认的分发机制(轮询分发)下,默认情况下RabbitMQ会将接收到的消息逐个分发给消费者,并且是一次性分发完,所以消费者1和消费者2获取的消息数量是相同的。

这显然不是我们想要的,我们可以使用能者多劳机制解决这个问题,给处理慢的消费者少发点,给处理快的消费者多发点。 有关详细的了解可以看这篇博客 RabbitMQ系列–工作队列模型

1.修改配置文件

spring:
  rabbitmq:
    host: 172.16.114.135
    port: 5672
    username: ems
    password: 123
    virtual-host: /ems
    listener:
      simple:
        prefetch: 1

prefetch=1:它表示限制每个Consumer在同一个时间点最多只能处理一个消息,我手里的活还没干完的话你就不能再给我分了。

2.在消费者2中使用Threed.sleep(1000)来模型处理业务慢的问题

    @RabbitListener(queuesToDeclare = @Queue("work"))
    public  void receive2(String message) throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("work message2 = "+message);
    }

运行代码可以发现,消费者1处理的快,处理的就比较多。
在这里插入图片描述

Fanout模型

1.创建生产者

    @Test
    public  void testFanout(){
        rabbitTemplate.convertAndSend("logs","","这是日志广播");
    }

2.创建消费者

@Component
public class FanoutCustomer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,
            exchange = @Exchange(name="logs",type = "fanout")
    ))
    public void receive1(String message){
        System.out.println("message1 = " + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //创建临时队列
            exchange = @Exchange(name="logs",type = "fanout")  //绑定交换机类型
    ))
    public void receive2(String message){
        System.out.println("message2 = " + message);
    }
}

@RabbitListener中的bingings属性是用来将交换机和队列进行绑定的
@QueueBinding: 绑定交换机和队列,主要参数如下:

  • value: 队列 @Queue创建一个临时队列
  • exchange: 交换机
  • key: 路由名称 是一个数组,可以定义多个名称

@Exchange:用来创建一个交换机

  • name:交换机名称
  • type: 交换机类型

运行代码,当生产者发送一条消息,所有的消费者都会收到这条消息,如下图所示:
在这里插入图片描述

路由(Direct)

1.生产者

	 @Test
    public void testDirect(){
    	//参数1:交换机名称  参数2: 路由名称  参数3:发送的消息
        rabbitTemplate.convertAndSend("directs","error","error 的日志信息");
    }

2.消费者

@Component
public class DirectCustomer {
    @RabbitListener(bindings ={
            @QueueBinding(
                    value = @Queue(),
                    key={"info","error"},
                    exchange = @Exchange(type = "direct",name="directs")
            )})
    public void receive1(String message){
        System.out.println("message1 = " + message);
    }

    @RabbitListener(bindings ={
            @QueueBinding(
                    value = @Queue(),
                    key={"error"},
                    exchange = @Exchange(type = "direct",name="directs")
            )})
    public void receive2(String message){
        System.out.println("message2 = " + message);
    }
}

在消费者代码中,消费者1只能接收生产者发送路由名为"info"或"error"的消息,消费者2只能接收生产者发送路由名为"error"的消息。

Topic(主题)

1.生产者

  //topic
    @Test
    public void testTopic(){
        rabbitTemplate.convertAndSend("topics","user.save.findAll","user.save.findAll 的消息");
    }

2.消费者

@Component
public class TopCustomer {
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    key = {"user.*"},
                    exchange = @Exchange(type = "topic",name = "topics")
            )
    })
    public void receive1(String message){
        System.out.println("message1 = " + message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    key = {"user.#"},
                    exchange = @Exchange(type = "topic",name = "topics")
            )
    })
    public void receive2(String message){
        System.out.println("message2 = " + message);
    }
}

*: 代表一个单词 #代表零个或多个单词。定义路由的名称用.隔开 比如在消费者1中定义路由key为user. *可以匹配user.z等消息。 消费者2可以匹配user 或user.save.findAll等消息。

其实我们也可以使用配置类来实现RabbitMQ, 这里拿Fanout模型举例

1.创建FanoutConfig类,然后创建队列,交换器,以及绑定队列与交换机

@Configuration
public class FanoutConfig {

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

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

    //队列3
    @Bean
    public Queue fanoutQueue3(){
        return  new Queue("fanout.c");
    }

    //交换器
    @Bean
    FanoutExchange fanoutExchange(){
        return  new FanoutExchange("fanoutExchange");
    }

    //绑定交换机和队列1
    @Bean
    Binding bindingFanout1(){
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }

    //绑定交换机和队列2
    @Bean
    Binding bindingFanout2(){
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
    }
    //绑定交换机和队列3
    @Bean
    Binding bindingFanout3(){
        return BindingBuilder.bind(fanoutQueue3()).to(fanoutExchange());
    }
}

2.创建生产者

 	@Test
    public  void testFanout1(){
        rabbitTemplate.convertAndSend("fanoutExchange","","这是日志广播");
    }

fanoutExchange是我们在配置中,创建的交换机名字

3.创建消费者

 	@RabbitListener(queues = "fanout.a")
    public void receive3(String message){
        System.out.println("message3 = " + message);
    }

    @RabbitListener(queues = "fanout.b")
    public void receive4(String message){
        System.out.println("message4 = " + message);
    }
    @RabbitListener(queues = "fanout.c")
    public void receive5(String message){
        System.out.println("message5 = " + message);
    }

queues: 绑定的在配置列中创建的队列名称。

6. MQ的应用场景

6.1 异步处理

场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式

  • 串行方式: 将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ftxf8Jrl-1608455305198)(E:/虚拟机/笔记图片/SouthEast-4860248.png)]

  • 并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pahfK3PT-1608455305203)(E:/虚拟机/笔记图片/SouthEast-20191127211112660.png)]

  • 消息队列:假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回. 消息队列: 引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kO9oXmC-1608455305205)(E:/虚拟机/笔记图片/592892-20190520220249900-1679743651.jpg)]

由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。

6.2 应用解耦

场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQaYSj9A-1608455305209)(E:/虚拟机/笔记图片/SouthEast-20191127211247287.png)]

这种做法有一个缺点:

当库存系统出现故障时,订单就会失败。 订单系统和库存系统高耦合. 引入消息队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rtz3BNVO-1608455305211)(E:/虚拟机/笔记图片/SouthEast-20191127211304085.png)]

  • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。

  • 库存系统:订阅下单的消息,获取下单消息,进行库操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失.

6.3 流量削峰

场景: 秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。

作用:

​ 1.可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)

​ 2.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MpjdIzAb-1608455305213)(E:/虚拟机/笔记图片/SouthEast-20191127211341601.png)]

1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.

2.秒杀业务根据消息队列中的请求信息,再做后续处理.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值