一、RabbitMQ
1.初始MQ
1.1同步通讯
同步:如视频电话,一旦接通就可以建立连接
异步:微信,消息发过去,不立马得到回复,时效性差
同步调用存在的问题
微服务间基于Feign的调用就属于同步方式,存在一些问题。
1.代码耦合问题,随着业务功能的增加还需要修改原本代码
2.性能下降,吞吐量下降(因为是同步调用,支付服务调用订单服务结束后才能调仓储服务)
3.资源浪费,资源利用不充分,支付服务调用业务后只能等待
4.级联失败,仓储服务如果挂了,支付服务调不到,只能卡着,多了就挂了
同步调用的优点
- 时效性强,可以立即得到结果
同步调用的问题
- 耦合度高
- 性能和吞吐能力下降
- 有额外的资源消耗
- 有级联失败问题
1.2异步通讯
异步调用方案:
异步调用常见实现就是事件驱动模式
异步通信的优点:
- 耦合度低
- 吞吐量提升
- 故障隔离
- 流量削峰
异步通信的缺点
- 依赖于Broker的可靠性、安全性、吞吐能力
- 架构复杂了,业务没有明显的流程线,不好追踪管理
1.3MQ常见框架
MQ就是消息队列,字面来看就是存放消息的队列,也就是事件驱动架构中的BROKER
2.RabbitMQ快速入门
2.1RabbitMQ概述和安装
docker load -i mq.tar
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
RabbitMQ中的几个概念:
- channel:操作MQ的工具
- exchange:路由消息到队列中
- queue:缓存消息
- virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
2.2常见消息模型
2.3快速入门
publisher:消息发布者,将消息发送到队列queue
queue:消息队列,负责接受并缓存消息
consumer:订阅队列,处理队列中的消息
基本消息队列的消息发送流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.利用channel向队发送消息
基本消息队列的消息接受流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery()
5.利用channel将消费者与队列绑定
为了防止队列不存在,生产者和消费者都声明队列
3.SpringAMQP
3.1Basic Queue 简单队列模型
利用SpringAMQP实现HelloWorld中的基础消息队列功能
第一步:引依赖
因为publisher和consumer服务都需要amqp依赖,因此这里把依赖直接放到父工程mq-demo中
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
第二步
1.在publish服务中编写application.yml,添加mq连接信息:
spring:
rabbitmq:
host: 106.55.251.245 # rabbitMQ的ip地址
port: 5672 # 端口
username: itcast # 用户名
password: 123321 # 密码
virtual-host: / # 虚拟主机
2.在publish服务中新建一个测试类,编写测试方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue(){
String queueName = "simple.queue";
String message = "hello,spring amqp";
rabbitTemplate.convertAndSend(queueName, message);
}
}
什么是AMQP?
- 应用间消息通信的一种协议,与语言和平台无关
SpringAMQP如何发送消息?
- 引入amqp的starter依赖
- 配置RabbitMQ地址
- 利用RabbitTemplate的convertAndSend方法
步骤3:在consumer中编写消费者逻辑,监听simple.queue
1.在consumer服务中编写application.yml添加mq连接信息
spring:
rabbitmq:
host: 106.55.251.245 # rabbitMQ的ip地址
port: 5672 # 端口
username: itcast # 用户名
password: 123321 # 密码
virtual-host: / # 虚拟主机
2.在consumer服务中新建一个类,编写消费逻辑:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg){
System.out.println("消费者接受到simple.queue的消息:[" + msg + "]");
}
}
SpringAMQP如何接受消息?
- 引入amqp的starter依赖
- 配置RabiitMQ地址
- 定义类,添加@Component注解
- 类中声明方法,添加@RabbitListener注解,方法参数就时消息
注意:消息一旦消费者就会从队列删除,RabbitMQ没有消息回溯功能
3.2Word Queue 动作队列模型
提高消息处理速度,避免队列消息堆积
案例:模拟WorkQueue,实现一个队列绑定多个消费者
步骤一:生产循环发送消息到simple.queue
在publisher服务中添加一个测试方法,循环发送50条消息到simple.queue队列
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2WorkQueue() throws InterruptedException {
String queueName = "simple.queue";
String message = "hello,message_";
for (int i = 1; i <= 50; i++) {
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
步骤2:编写两个消费者,都监听simple.queue
在consumer服务中添加一个消费者,也监听simple.queue:
@RabbitListener(queues = "simple.queue")
public void listenWork1Queue(String msg) throws InterruptedException {
System.out.println("消费者1接受到的消息:[" + msg + "]" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenWork2Queue(String msg) throws InterruptedException {
System.err.println("消费者2.......接受到的消息:[" + msg + "]" + LocalTime.now());
Thread.sleep(200);
}
3.3消费预取限制
修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限:
spring:
rabbitmq:
host: 106.55.251.245 # rabbitMQ的ip地址
port: 5672 # 端口
username: itcast # 用户名
password: 123321 # 密码
virtual-host: / # 虚拟主机
listener:
simple:
prefetch: 1 # 每次只能读取一个消息,处理完成才能获取下一个消息
Work模型的使用:
- 多个消费者绑定到一个队列,同一个消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量
3.4发布、订阅模型-Fanout
发布订阅模式与之前案例的区别就是允许将同一消息发送个多个消费者。实现方式是加入了exchange(交换机).
注意:exchange负责消息路由,而不是存储,路由失败则消息丢失
利用SpringAMQP演示FanoutExchange的使用
步骤1:在consumer服务声明Exchange、Queue、Bindindm,添加@Configuration注解,并声明FanoutExchange、Queue和绑定关系对象Binding,代码如下:
@Configuration
public class FanoutConfig {
//1.声明交换机itcast.fanout
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
//2.声明队列fanout.queue1
@Bean
public Queue fanoutqueue1(){
return new Queue("fanout.queue1");
}
//2.声明队列fanout.queue2
@Bean
public Queue fanoutqueue2(){
return new Queue("fanout.queue2");
}
//绑定队列1到交换机
@Bean
public Binding fanoutBinding(Queue fanoutqueue1,FanoutExchange fanoutExchange){
return BindingBuilder
.bind(fanoutqueue1)
.to(fanoutExchange);
}
//绑定队列2到交换机
@Bean
public Binding fanoutBinding2(Queue fanoutqueue2,FanoutExchange fanoutExchange){
return BindingBuilder
.bind(fanoutqueue2)
.to(fanoutExchange);
}
}
步骤2:在consumer服务声明两个消费者
在consumer服务的springRabbitLister类中,添加连个方法,分别监听fanout.queue1和fanout.queue2:
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue(String msg){
System.out.println("消费者接受到fanout.queue1的消息:[" + msg + "]");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanout2Queue(String msg){
System.out.println("消费者接受到fanout.queue2的消息:[" + msg + "]");
}
步骤3:在pulish服务中的SpringAmqpTest类中添加测试方法:
@Test
public void testSendFanoutExchange(){
// 交换机名称
String exchangeName = "itcast.fanout";
//消息
String message = "hello,everyone";
rabbitTemplate.convertAndSend(exchangeName,"",message);
}
交换机的作用是什么?
- 接受publisher发送的消息
- 将消息按规则路由到与之绑定的队列
- 不能缓存消息,路由失败,消息丢失
- FanoutExchange的会将消息路由到每个绑定的队列
声明队列、交换机、绑定关系的Bean是什么?
- Queue
- FanoutExchange
- Binding
3.5发布、订阅模型-Direc
案例:利用SpringAMQP演示DirectExchange的使用
步骤一:在consumer服务中,编写两个消费者方法,分别监听direct.queue1和queue2,
2.并利用@RabbitListener声明Exchange、Queue、RoutingKey
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queu1"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){
System.out.println("消费者收到direct.queue1的消息:[" + msg +