一、快速上手
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
创建生产者
@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 工作队列模式
一个队列绑定多个消费者 基本思路如下:
- 在RabbitMQ的控制台创建一个队列,名为work.queue
- 在生产者中定义测试方法,产生10条消息,发送到work.queue
- 在consumer服务中定义两个消息监听者,都监听work.queue队列
- 消费者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 广播模式
下面的的都需要用到交换机才能完成,交换机有三种类型
- fanout:广播
- direct:定向
- 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中声明队列和交换机
方式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);
}