RabbitMQ快速上手

193 阅读4分钟

一、快速上手

helloworld模式

graph LR
producer --> queue --> consumer

springboot引入

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

修改配置文件

spring:
  rabbitmq:
    host: ip地址
    port: 端口
    username: 用户名
    password: 密码
    virtual-host: 虚拟主机

先在rabbitmq控制台创建队列simple.queue

1706627845125.png

创建生产者

@RestController
@RequestMapping("mq")
@Api(tags = "测试mq")
@AllArgsConstructor
public class TestMqController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/helloRabbit")
    public String helloRabbit() {
        String queueName = "simple.queue";
        String message = "hello, rabbitmq!";
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
        return "success";
    }

}

创建消费者

@Component
public class NotifyMsgConsumer {

    @RabbitListener(queues = "simple.queue")
    public void simpleQueue(String message) {
        System.out.println("simpleQueue收到消息:"+message);
    }
}

二、工作模式

1、workerqueue 工作队列模式

一个队列绑定多个消费者 基本思路如下:

  1. 在RabbitMQ的控制台创建一个队列,名为work.queue
  2. 在生产者中定义测试方法,产生10条消息,发送到work.queue
  3. 在consumer服务中定义两个消息监听者,都监听work.queue队列
  4. 消费者1处理的快,消费者2处理的慢
graph LR
producer --> queue --> consumer1
queue --> consumer2
                   

先在rabbitmq控制台创建队列work.queue,创建方式同上,不再赘述

生产者

@GetMapping("/workQueue")
public String workQueue() throws InterruptedException {
    String queueName = "work.queue";
    for (int i = 0; i < 10; i++){
        String message = "hello, workQueue! ----- message" + i;
        rabbitTemplate.convertAndSend(queueName, message);
        Thread.sleep(200);
    }
    return "success";
}

消费者

@Component
public class NotifyMsgConsumer {

    @RabbitListener(queues = "work.queue")
    public void consumer1(String message) {
        System.out.println("workQueue 1收到消息:"+message);
    }
    @RabbitListener(queues = "work.queue")
    public void consumer2(String message) throws InterruptedException {
        System.out.println("workQueue 2收到消息:"+message);
        Thread.sleep(20000);
    }
}
workQueue 2收到消息:hello, workQueue! ----- message0
workQueue 1收到消息:hello, workQueue! ----- message1
workQueue 1收到消息:hello, workQueue! ----- message3
workQueue 1收到消息:hello, workQueue! ----- message5
workQueue 1收到消息:hello, workQueue! ----- message7
workQueue 1收到消息:hello, workQueue! ----- message9
workQueue 2收到消息:hello, workQueue! ----- message2
...

这时我们发现一个问题消息是一人一个,消费者1处理的效率快,但是分给他的少,造成了资源浪费

消费者消息推送限制

默认情况下,RabbitMQ的会将消息依次轮询投递给绑定在队列上的每一个消费者。但这并没有考虑到消费者是否已经处理完消息,可能出现消息堆积。 因此我们需要修改application.yml,设置preFetch值为1,确保同一时刻最多投递给消费者1条消息:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1  #每次只能获取一条消息,处理完成才能获取下一个消息
workQueue 2收到消息:hello, workQueue! ----- message0
workQueue 1收到消息:hello, workQueue! ----- message1
workQueue 1收到消息:hello, workQueue! ----- message2
workQueue 1收到消息:hello, workQueue! ----- message3
workQueue 1收到消息:hello, workQueue! ----- message4
workQueue 1收到消息:hello, workQueue! ----- message5
workQueue 1收到消息:hello, workQueue! ----- message6
workQueue 1收到消息:hello, workQueue! ----- message7
workQueue 1收到消息:hello, workQueue! ----- message8
workQueue 1收到消息:hello, workQueue! ----- message9

这样就是能者多劳

为什么非要用俩消费者?

消息如果很多,处理不过来,两个消费者甚至更多消费者可以加快消息的处理速度

2、Fanout 广播模式

下面的的都需要用到交换机才能完成,交换机有三种类型

  1. fanout:广播
  2. direct:定向
  3. topic:话题
graph LR
producer --> exchange --> queue1 --> consumer1
exchange --> queue2 --> consumer2

控制台添加交换机heima.finout,绑定两个队列fanout.queue1和fanout.queue2

生产者

@GetMapping("/fanout")
public String fanout() throws InterruptedException {
    String exchange = "heima.finout";
    for (int i = 0; i < 10; i++){
        String message = "hello, fanout! ----- message" + i;
        rabbitTemplate.convertAndSend(exchange,null, message);
    }
    return "success";
}

消费者

@RabbitListener(queues = "fanout.queue1")
public void fanout1(String message) {
    System.out.println("fanout 1收到消息:"+message);
}
@RabbitListener(queues = "fanout.queue2")
public void fanout2(String message) {
    System.out.println("fanout 2收到消息:"+message);
}

两个消费者都能收到消息

fanout 2收到消息:hello, fanout! ----- message0
fanout 1收到消息:hello, fanout! ----- message0
fanout 2收到消息:hello, fanout! ----- message1
fanout 1收到消息:hello, fanout! ----- message1
...

3、Direct 定向模式

根据路由规则,路由到指定的Queue

graph LR
producer --> exchange --> queue1 --> consumer1
exchange --> queue2 --> consumer2
  • 每一个Queue都与Exchange设置一个BindingKey
  • 发布者发送消息时,指定消息的RoutingKey
  • Exchange将消息路由到BindingKey与消息RoutingKey一致的队列

控制台添加交换机heima.direct,绑定两个队列direct.queue1和direct.queue2,direct.queue1设置路由key 是one,direct.queue2 设置路由key是two

@GetMapping("/direct")
public String direct(Integer i) throws InterruptedException {
    String exchange = "heima.direct";
    if(i == 1){
        rabbitTemplate.convertAndSend(exchange,"one", i.toString());
    }else{
        rabbitTemplate.convertAndSend(exchange,"two", i.toString());
    }

    return "success";
}
@RabbitListener(queues = "direct.queue1")
public void direct1(String message) {
    System.out.println("direct 1收到消息:"+message);
}
@RabbitListener(queues = "direct.queue2")
public void direct2(String message) {
    System.out.println("direct 2收到消息:"+message);
}
direct 1收到消息:1
direct 2收到消息:2
direct 2收到消息:2
direct 2收到消息:3

4、Topic 主题模式

TopicExchange与DirectExchange类似,区别在于routingKey可以是多个单词的列表,并且以.分割。

Queue与Exchange指定BindingKey时可以使用通配符:

  • # : 代指0个或多个单词
  • * : 代指1个单词

这个不再赘述和上面相同就是设置交换机的类型不同,绑定路由key的时候可以用通配符

三、java中声明队列和交换机

image.png

image.png

方式1

@Configuration
public class FanoutConfig {

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("myFanout");
    }

    @Bean
    public Queue fanoutQueue1() {
        return new Queue("myFanout.queue1", true);
    }
    @Bean
    public Queue fanoutQueue2() {
        return new Queue("myFanout.queue2", true);
    }


    @Bean
    public Binding binding1() {
        return BindingBuilder
                .bind(fanoutQueue1())
                .to(fanoutExchange());
    }
    @Bean
    public Binding binding2() {
        return BindingBuilder
                .bind(fanoutQueue2())
                .to(fanoutExchange());
    }
}

方式2

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "myFanout.queue3"),
        exchange = @Exchange(name = "myFanout", type = "fanout"),
        //key = {"routing.key1", "routing.key2"}
))
public void myFanout3(String message) {
    System.out.println("myFanout 2收到消息:"+message);
}