🌟个人主页:时间会证明一切.
目录
1.基本介绍
RabbitMQ中的一些角色:
- channel:操作 MQ 的工具
- publisher:生产者
- consumer:消费者
- exchange:交换机,负责消息路由
- queue:队列,存储消息
- virtualHost:虚拟主机,隔离不同租户的exchange、queue等资源的逻辑分组、消息的隔离
exchange(交换机) 负责路由和转发消息的,无存储消息能力。
需要绑定队列才可以将消息存放到队列当中。队列也需绑定交换机。
将来消费者通过绑定队列就可以获取消息了。
2.数据隔离
默认虚拟主机为 /
不同项目都可以有一个自己用户名已经对应的虚拟主机
3.AMQP
SpringAmqp的官方地址:https://spring.io/projects/spring-amqp
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CUOFeluT-1691631776241)(https://li-zhien.oss-cn-hangzhou.aliyuncs.com/img/202308091516481.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VCL4Og1w-1691631776241)(https://li-zhien.oss-cn-hangzhou.aliyuncs.com/img/202308091518721.png)]
1、引入依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、配置RabbitMQ服务端信息
SpringAMQP提供了 RabbitTemplate工具类 ,方便我们发送消息。发送消息代码如下:
spring:
rabbitmq:
host: 111.231.26.106 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 # 密码
3、发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue() {
// 队列名称
String queueName = "simple.queue";
// 消息
String message = "hello,spring amqp!";
// 发送消息
rabbitTemplate.convertAndSend(queueName,message);
}
4、接收消息
SpringAMQP提供声明式的消息监听,我们只需要通过注解在方法上声明要监听的队列名称,将来
SpringAMQP就会把消息传递给当前方法:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(String msg) {
System.out.println("spring 消费者接收到消息:【" + msg + "】");
}
}
5、总结
SpringAMQP如何收发消息?
- 引入 pring-boot-starter-amqp 依赖
- 配置 rabbitmq 服务端信息
- 利用 RabbitTemplate 发送消息
- 利用
@RabbitListener
注解声明要监听的队列,监听消
4.work 模型
Work queues, 任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
1、案例
2、发送消息
@Test
public void testWorkQueue() throws InterruptedException {
// 队列名称
String queueName = "work.queue";
// 消息
String message = "hello, message_";
for (int i = 0; i < 50; i++) {
// 发送消息
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
3、接收消息
@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(200);
}
结果:
- 一条消息只能被消费一次
- 生产者发送的消息平均分配给了两个消息
- 修改处理消息能力还是平均分配
4、消费者消息推送限制
消费者一次最多只能处理一个消息(充分利用不同性能的处理消息的能力)
为什么需要多个消费者?
- 当一个消费者处理消息的能力已经达到了它的上限就需要多个消费者。
5、总结
Work模型的使用:
- 多个消费者绑定到一个队列,可以加快消息处理速度
- 同一条消息只会被一个消费者处理
- 通过设置
prefetch
来控制消费者预取的消息数量,处理完一条再处理下一条,实现能者多劳
5.fanout 交换机
Fanout Exchange 会将接收到的消息广播到每一个跟其绑定的 queue 所以也叫广播模式
1、案例
2、添加队列/交换机
添加队列
添加交换机
绑定队列
3、发送消息
需要指定交换机的名字
@Test
public void testFanoutExchange() {
// 交换机名称
String exchangeName = "itcast.fanout";
// 消息
String message = "hello, blue!";
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
4、监听消息
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}
5、总结
交换机的作用是什么?
- 接收 publisher 发送的消息
- 将消息按照规则路由到与之绑定的队列
- FanoutExchange 的会将消息路由到每个绑定的队列
6.Direct交换机
1、案例
2、创建交换机
创建时绑定队列以及指定 key
3、发送消息
@Test
public void testSendDirectExchange() {
// 交换机名称
String exchangeName = "hmall.direct";
// 消息
String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "yellow", message);
}
red:消费者都可以收到
blue:只有消费者1收到
4、监听消息
@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg){
System.out.println("消费者接1收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg){
System.out.println("消费者接2收到direct.queue2的消息:【" + msg + "】");
}
7.Topic交换机
1、案例
2、创建队列/交换机
3、发送消息
@Test
public void testSendTopicExchange() {
// 交换机名称
String exchangeName = "hmall.topic";
// 消息
String message = "喜报!孙悟空大战哥斯拉,胜!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);
}
china.news:能可以收到
china.weather:只有消费者1 可以收到
4、接收消息
@RabbitListener(queues = "topic.queue1")
public void listenTopicQueue1(String msg){
System.out.println("消费者1 接收到topic.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "topic.queue2")
public void listenTopicQueue2(String msg){
System.out.println("消费者2 接收到topic.queue2的消息:【" + msg + "】");
}
5、总结
描述下Direct交换机与Topic交换机的差异?
- Topic交换机接收的消息 RoutingKey 可以是多个单词,以
.
分割 - Topic交换机与队列绑定时的 bindingKey 可以指定通配符
- #: 代表0个或多个词
- *: 代表1个词
8.声明队列和交换机
1、基于Bean
一般在消费者这里定义
@Configuration
public class FanoutConfig {
// 交换机
@Bean
public FanoutExchange fanoutExchange() {
// ExchangeBuilder.fanoutExchange("hmall.fanout");
return new FanoutExchange("itcast.fanout");
}
// 队列
@Bean
public Queue fanoutQueue1() {
// QueueBuilder.durable("fanout.queue").build();
return new Queue("fanout.queue1");
}
// 绑定
@Bean
public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
}
2、基于注解
顺序:队列、交换机、队列
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"), durable = "true"
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
}
durable = "true"
:用于持久化
3、总结
声明队列、交换机、绑定关系的Bean是什么?
- Queue
- FanoutExchange、DirectExchange、TopicExchange
- Binding
基于@RabbitListener注解声明队列和交换机有哪些常见注解?
- @Queue
- @Exchange
9.消息转换器
@Test
public void testSendMap() {
// 准备消息
Map<String, Object> msg = new HashMap<>();
msg.put("name", "Jack");
msg.put("age", 21);
// 发送消息
rabbitTemplate.convertAndSend("object.queue", msg);
}
Spring的对消息对象的处理是由org.springframework.amqp.support.converter…MessageConverter来
处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream:完成序列化。
存在下列问题:
- JDK的序列化有安全风险
- JDK序列化的消息太大
- JDK序列化的消息可读性差
出现异常可能出现消息丢失的风险!!!
10.业务改造
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
mvc 自带消息转换器,所以不用在引入了。但是需要配置 bean
2、添加配置
@ApiOperation("标记订单已支付")
@ApiImplicitParm(name = "orderId", value = "订单id", paramType = "path")
@PutMapping("/{orderId}")
public void markOrderPaySuccess(@PathVariable("orderid") Long orderId) {
orderService.markOrderPaySuccess(orderId);
}
3、config
在 common 模块下定义 mq 的 config
@Configuration
public class MqConfig {
@Bean
public MessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
4、声明队列
@Component
@RequireArgsConstructor
public class PayStatusListener {
private final IOrderService orderService;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "mark.order.pay.queue", durable = "true"),
exchange = @Exchange(name = "pay.topic", type = ExchangeTypes.TOPIC),
key = {"pay.success"}
))
public void listenOrderPay(Long orderId){
// 标记订单状态已支付
orderService.markOrderPaySuccess(orderId);
}
}
@Bean
public MessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
### 4、声明队列
~~~java
@Component
@RequireArgsConstructor
public class PayStatusListener {
private final IOrderService orderService;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "mark.order.pay.queue", durable = "true"),
exchange = @Exchange(name = "pay.topic", type = ExchangeTypes.TOPIC),
key = {"pay.success"}
))
public void listenOrderPay(Long orderId){
// 标记订单状态已支付
orderService.markOrderPaySuccess(orderId);
}
}