什么是SpringAMQP
AMQP全称Advanced Message Queuing Protocol:是用于在应用程序或之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。
Spring AMQP:是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
SpringAmqp的官方地址: Spring AMQP
官网中对于SpringAmqp的特征是这样描写的:
利用SpringAMQP实现HelloWorld中的基础消息队列功能
步骤1:引入AMQP依赖
因为publisher和consumer服务都需要amqp依赖,因此这里把依赖直接放到父工程mq-demo中:
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
步骤2: 在publisher中编写测试方法,向simple.queue发送消息
1.在publisher服务中编写application.yml,添加mq连接信息
spring:
rabbitmq:
host: 43.139.105.52 #主机名
port: 15672 #端口
virtual-host: / #虚拟主机
username: zstc
password: 123321
2.在publisher服务中新建一个测试类,编写测试方法:
@Test
public void testSendMessage2SimpleQueue(){
String queueName = "simple.queue";
String message = "hello,spring amqb!";
rabbitTemplate.convertAndSend(queueName, message);
}
步骤3: 在consumer中编写消费逻辑,监听simple.queue
1.在consumer服务中编写application.yml,添加mq连接信息:
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast #用户名
password: 123321 # 密码
2.在consumer服务中新建一个类,编写消费逻辑:
总结:SpringAMQP如何接收消息?
- 引入amqp的starter依赖
- 配置RabbitMQ地址
- 定义类,添加@Component注解
- 类中声明方法,添加@RabbitListener注解,方法参数就时消息
注意:消息一旦消费就会从队列删除,RabbitMO没有消息回溯功能
Work Queue工作队列
工作队列其实还是普通队列,不过在设计的时候挂了多个消费者,就可以提高消息处理速度,避免队列消息堆积。
模拟WorkQueue,实现一个队列绑定多个消费者
基本思路如下:
1.在publisher服务中定义测试方法,每秒产生50条消息发送到simple.queue
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列
3.消费者1每秒处理50条消息,消费者2每秒处理10条消息
生产者
消费者1
消费者2
但是,消息处理的事件却远远超出了我们的预期,消费者1处理了所有奇数的消息,消费者2处理了所有偶数的消息,为什么呢?
因为mq其实有一个消息预取的机制,会预先平均分配好消息给消费者,没有考虑到消费者的处理能力。
解决方案:修改application.ym[文件,设置preFetch这个值,可以控制预取消息的上限:
发布订阅模型
前面的两种消息队列有局限性,因为消费者读取消息后会把消息从消息队列中删除,一个消息只能被一个消费者读取
发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange(交换机)
常见exchange类型包括:
- Fanout: 广播
- Direct: 路由
- Topic: 话题
广播——FanoutExchange
Fanout Exchange 会将接收到的消息路由到每一个跟其绑定的queue
利用SpringAMQP演示FanoutExchange的使用
实现思路如下:
1.在consumer服务中,利用代码声明队列、交换机,并将两者绑定
2.在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
3.在publisher中编写测试方法,向itcast.fanout发送消息
在消费者中新建FanoutConfig配置类,进行交换机和队列的绑定
@Configuration
public class FanoutConfig {
//声明交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("zstc.fanout");
}
//声明队列1
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定队列1到交换机1
@Bean
//这里参数名字不要写错,它会根据类型和名字进行注入
public Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder
.bind(fanoutQueue1)
.to(fanoutExchange);
}
//声明队列2
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
public Binding fanoutBinding2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder
.bind(fanoutQueue2)
.to(fanoutExchange);
}
编写SpringRabbitListener类编写消费逻辑
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg){
System.out.println("消费者接收到fanout.queue1的消息:["+msg+"]");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue1(String msg){
System.out.println("消费者接收到fanout.queue2的消息:["+msg+"]");
}
生产者发布信息到队列里
@Test
public void testSendFanoutExchange(){
//交换机名称
String exchangeName = "zstc.fanout";
//消息
String message = "hello,every one";
//发送消息
rabbitTemplate.convertAndSend(exchangeName, message);
}
路由——TopicExchange
Direct Exchange 会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式 (routes)。
每一个Queue都与Exchange设置一个BindingKey
发布者发送消息时,指定消息的RoutingKey
Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
利用@RabbitListener声明Exchange,Queue,RountingKey
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "zstc.direct",type = ExchangeTypes.DIRECT),
key = {"green", "yellow"}
))
public void listenDirectQueue1(String msg){
System.out.println("消费者接收到direct.queue1的消息:["+msg+"]");
}
描述下Direct交换机与Fanout交换机的差异?·
1. Fanout交换机将消息路由给每一个与之绑定的队列
2. Direct交换机根据RoutingKey判断路由给哪个队列
3.如果多个队列具有相同的RoutingKey,则与Fanout功能类似基于
@RabbitListener注解声明队列和交换机有哪些常见注解?
@Queue
@Exchange
话题——DirectExchange
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以.分割。
利用@RabbitListener声明Exchange,Queue,RountingKey
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "zstc.topic",type = ExchangeTypes.TOPIC),
key = {"china.#"}
))
public void listenTopicQueue1(String msg){
System.out.println("消费者接收到direct.queue1的消息:["+msg+"]");
}
消息转换器
放入消息队列的本应该是字节流,但是这里传了个map集合进去还是成功了
从这里可以看出,mq将对象进行了java序列化转换成字节流,但是这种序列化方式转换出来的消息体很大,所以非常不推荐这种默认的方式进行序列化
修改消息转换器
接收消息
也需要 引入jakson依赖,因为发送的时候序列化成字节流了,所以接收的时候需要用jakson将字节流反序列化成对象
SpringAMQP中消息的序列化和反序列化是怎么实现的?
利用MessageConverter实现的,默认是JDK的序列化
注意:发送方与接收方必须使用相同的MessageConverter
学习笔记from黑马程序员