目录
1.初始MQ
1.1同步通讯
简单来说:
同步通讯:就是打电话,直接互通,一方做出反应另一方立刻会知道并回应。
同步通讯的缺点:电话同一时间只能同一人(别的进程无法进行)
异步通讯:是发微信,可以当时回也可以晚点或者不回,不具有时效性。但是可以多线操作。
1.耦合度高。每次加入新的需求,都要修改原来的代码。
2.性能下降。调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用时间之和
3.浪费资源。调用链中的每个服务在等待相应过程中,不能释放请求的资源,高并发场景下会极度浪费系统资源。
4.级联失败。如果服务提供者出现问题,所有的调用者都会出现问题,如多米诺骨牌一样,迅速导致整个微服务群故障。
1.2异步通讯
优势一,
异步调用常见实现就是事件驱动模式。(白话就是中间找个代理 ,broker。服务者订阅borker说大哥有事得喊我,叫做订阅事件。当有订单过来会同时通知所有得订阅者。所以支付服务跟broker说完就没事了回头可以跟用户说大哥我干完了,不需要等待后面的调用者回应 这就做到了解耦)。有新的事件出现后只需要订阅borker即可,取消得业务取消订阅即可。
优势二,
本来需要相加得时间50+10+150*3
通过异步通讯后50+10即可 提升性能,吞吐量提高。
优势三,
中间都通过borker代理了 ,自然也就没有依赖了。随意也不会担心级联失败。
区别于同步调用的有点:
流量削峰:
对于大型的高并发的如秒杀这样的场景下可以控制。压力给到broker(缓存)按自己能力处理。
缺点:
针对于以上的四个有点可以看出。对于borker的依赖相当之高。并且需要borker极强的功能。调用链相对比没那么清晰,定位问题难以排查。
1.3MQ常见框架
MQ(MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动构架中的Broker。
对于吞吐量极度依赖的公司会使用kafka ,大部分中小型公司都是用RabbitMQ。
2.RabbitMQ快速入门
2.1RabbitMQ安装和介绍
通过docker安装即可,这里不做赘述。
运行的命令,
docker run \
-e RABBITMQ_DEFAULT_USER=123 \ #账号
-e RABBITMQ_DEFAULT_PASS=123 \ #密码
--name mq -p 15672:15672 \ #MQ管理平台的端口
-p 5672:5672 \ #做消息通讯的端口
-d rabbitmq:3-management #后台运行和名称
发行者(publisher) 发送消息虚拟主机(VirtualHost)虚拟主机内(交换机exchange路由消息到队列queue,队列暂存消息)消费者(consumer)从队列获取消息而后处理消息。
每个用户都有自己的VirtualHost,每个VirtualHost(虚拟主机)都是相互隔离。
2.2常见的消息模型
红色代表queue(消息) 紫色代表exchange(交换机)
支付———broker————订单(等同于上面);
3.SpringAMQP
最主要的就是 p标准或者是规范(Protocol)。
listener监听(接收)
发送的 RabbitTenmplate (rabbit模板)
RabbitAdmin 用于自动声明队列,交换和绑定
3.1 Basic Queue 简单队列模型
发送
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
编写publisher
spring:
rabbitmq:
host: 192.168.130.143
port: 5672
virtual-host: /
username: 123
password: 123
@SpringBootTest
@RunWith(SpringRunner.class)
@EnableRabbit
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue(){
String queueName = "simple.queue";
String message = "hello spring amqp!";
rabbitTemplate.convertAndSend(queueName,message);
}
}
接收
3.2 Work Queue 工作队列模型
工作队列可以看作是经典队列的升级,队列是有一定容量的,当一个consumer能力不够时可以在创建一个consumer分担压力。
publisher:
@Test
public void testSendMessage2WorkQueue() throws InterruptedException {
String queueName = "simple.queue";
String message = "hello work amqp!";
for(int i = 1;i<=50;i++){
rabbitTemplate.convertAndSend(queueName,message+i);
Thread.sleep(20);
}
}
consumer *2:
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者接收到的simple.queque的消息:"+msg+ LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("消费者接收到的simple.queque的消息:"+msg+LocalTime.now());
Thread.sleep(200);
}
当前模式下属于消息预取模式 如50条消息两个consumer平均预取25条,但是consumer2的处理能力慢所以1执行完后要等2处理完自己的,所以在yml中添加以下条件。
设置预取1当一个consumer处理完一个会告诉queue,然后queue在分配一个。
设置2就是两个。也就是consumer会容纳两个然后回复完,在分配。
可以达到能者多劳。
4.发布订阅模式
在之前的队列模式中(simple,或work队列)一个queue只会给一个consumer。
不能满足多消费者同时处理一个消息(如 支付发同时发个订单,仓促,短信等)。
增加了exchenge(交换功能):只能消息路由不负责存储。
4.1 发布,订阅模型-Fanout
@Configuration
public class FanoutConfig {
//声明交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itbug.fanout");
}
//声明队列
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定交换机和队列
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
笨方法,首先声明配置类@Configuration搭载@Bean
通过交换机和声明队列 binding exchange和queue。
消费者:
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg){
System.out.println("消费者1接收到的simple.queque的消息:"+msg);
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg){
System.out.println("消费者2接收到的simple.queque的消息:"+msg);
}
@Test
public void testSendMemmageFanoutQueue(){
String exchangeName = "itbug.fanout";
String message = "hello fanout exchange";
rabbitTemplate.convertAndSend(exchangeName,"",message);
}
“”引号之间留给Direct(路由模型)
4.2 发布,订阅模型-Direct
路由模型相对于广播模型(fanout)功能更宽泛。
Direct相对于Fanout模式发送的消息更多些。在queue中提供相对的“暗号”,匹配后可对接。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itbug.direct",type = ExchangeTypes.DIRECT),
key = {"red","blue"}
))
public void listenDirectQueue1(String msg){
System.out.println("消费者1接收到的simple.queque的消息:"+msg);
}
通过@RabbitListener 绑定队列 交换机和暗号(绑定key)
@Test
public void testSendMessageDirectQueue(){
String exchangeName = "itbug.direct";
String message = "hello direct exchange";
rabbitTemplate.convertAndSend(exchangeName,"blue",message);
}
通过这里绑定key “blue” 则可以选择通过key把消息发送给谁。
也可以通过共有的key ”red“ 把消息同时发给共有 “red” 的consumer。
4.3 发布,订阅模型-Topic
在路由模式上功能更丰富一些。通过 . 的形式写入过个单词.#/*的模式,收到某一字段的信息。
listener:
/**
* Topic1
* @param msg
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itbug.topic",type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue1(String msg){
System.out.println("消费者1接收到的simple.queque的消息:"+msg);
}
/**
* Topic2
* @param msg
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic,queue2"),
exchange = @Exchange(name = "itbug.topic",type = ExchangeTypes.TOPIC),
key = "china.#"
))
发送:
@Test
public void testSendMessagetopicQueue(){
String exchangeName = "itbug.topic";
String message = "2023年2月10日12点04分";
rabbitTemplate.convertAndSend(exchangeName,"china.news",message);
}
这样的 key(chian。news) . china.# 和 #.key 都可以收到。
4.4 消息转换器
也就是 通过 rabbitTemplate 来发送数据 但是都是通过byte字节的形式。
通过 jackson转换的形式json
原理则是用 MessageConverter 来覆盖默认的jdk的序列化。