springcloud笔记(实用篇day03)
1.初始MQ
什么是mq,首先我们要先了解到什么是异步通信,什么是同步通信,
同步通信肯定很简单:比如我们打的微信视频,能时刻知道你在干什么,这个就是同步通讯
异步通讯也很好理解:就比如我们发微信给某个人,但是可能他没看到消息,没有回你,也可能是压根就不想回你,没回你消息,没有第一时间回你消息,这个就是异步通信。
同步通讯
-
优点:时效性强,能立刻知道结果
-
缺点:耦合度很高,并且如果一个模块出了问题,那么另外一个的所有服务都无法执行
异步通信
- 优点:解耦合,分模块,各模块之间不会相互影响,吞吐量提升,无需等待订阅者处理完成,响应更快速
- 缺点:架构复杂了,业务没有明显的流程线,不好管理,不知道是谁先执行的,谁后执行的,需要依赖于Broker的可靠、安全、性能,就是那个中间层,对他的要求是很高的,需要接受高并发
那么Broker就是我们的mq
1.1.常见的MQ
几种常见MQ的对比:
RabbitMQ | ActiveMQ | RocketMQ | Kafka | |
---|---|---|---|---|
公司/社区 | Rabbit | Apache | 阿里 | Apache |
开发语言 | Erlang | Java | Java | Scala&Java |
协议支持 | AMQP,XMPP,SMTP,STOMP | OpenWire,STOMP,REST,XMPP,AMQP | 自定义协议 | 自定义协议 |
可用性 | 高 | 一般 | 高 | 高 |
单机吞吐量 | 一般 | 差 | 高 | 非常高 |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 毫秒以内 |
消息可靠性 | 高 | 一般 | 高 | 一般 |
追求可用性:Kafka、 RocketMQ 、RabbitMQ
追求可靠性:RabbitMQ、RocketMQ
追求吞吐能力:RocketMQ、Kafka
追求消息低延迟:RabbitMQ、Kafka
1.2.MQ的安装
使用docker拉取下来,然后挂载容器卷,绑定两个默认的端口,15672 和 5672,5672是实现消息的读取的,15672是实现后台的管理的,进行可视化的查看。
1.3.快速入门
有两个部分:
- 生产者 publish (产生消息的)
- 消费者 consumer (消费消息的)
简单队列模式的模型图:
官方的HelloWorld是基于最基础的消息队列实现的
1.3.1.publish(生产者)
public class PublisherTest {
@Test
public void testSendMessage() throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
String message = "hello, rabbitmq!";
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("发送消息成功:【" + message + "】");
// 5.关闭通道和连接
channel.close();
connection.close();
}
}
1.3.2.consumer(消费者)
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
System.out.println("等待接收消息。。。。");
}
}
向上面这样就能读取到我们生产者产生的消息,注意这个是读取消息后就没有,直接销毁了。
总结:
这个是很麻烦的方式,对吧,每次都要写这么多的代码,所以很麻烦
下面就给大家介绍整合到spring里面的MQ。
2.springAMQP
这个就是spring进行了整合,帮助我们简单开发(注解开发)。
简单消息队列模型
1.导入依赖
在我们的pom文件里面引入
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.在yml配置文件里面配置mq的基本配置
端口,ip,管理的虚拟机等
spring:
rabbitmq:
host: 114.132.73.10 # 主机名
port: 5672 # 端口 接受消息的端口
virtual-host: / # 虚拟主机
username: zlm # 用户名 这个是docker启动的时候设置的账号和密码
password: zlm666 # 密码
3.使用
下面我们就来使用一下spring帮助我们管理后,究竟有多方便。
要注入一个类
RabbitTemaplte 和 redis那个是一样的
3.1.publish的创建
@Test
public void sendMsgBasicQueue() {
String queueName = "simple.queue";
String message = "hello spring Ampq!";
// 这个就是发送消息
// 调用这个convertAndSend的方法进行发送到一个队列上面去
rabbitTemplate.convertAndSend(queueName, message);
}
3.2.consumer的创建
package cn.itcast.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
// 被spring管理
@Component
public class SpringRabbitListener {
// 使用RabbitListener的注解 queues = "队列的名称" msg 就是消费的信息
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(String msg) throws InterruptedException {
System.out.println("spring 消费者接收到消息:【" + msg + "】");
}
}
3.四种常用的mq
3.1.WorkQueue
Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。我们就可以在多个队列上绑定多个消费者。
生产者:将消息传送到固定的queue
消费者:从他们共同绑定的队列里面读取消息
3.1.1.publish(生产者)
@Test
public void sendMsgBasicQueue() {
String queueName = "work.queue";
String message = "hello workqueue!";
// 这个就是发送消息
// 调用这个convertAndSend的方法进行发送到一个队列上面去
for(int i = 1 ; i <= 50; i++){
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20); // 休息20毫秒发送一次 那么1秒就会发送完这个50个信息
}
}
3.1.2.consumer(消费者)
package cn.itcast.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
// 被spring管理
@Component
public class SpringRabbitListener {
// 使用RabbitListener的注解 queues = "队列的名称" msg 就是消费的信息
// 消费者1
@RabbitListener(queues = "work.queue")
public void listenWorkQueueMessage1(String msg) throws InterruptedException {
System.out.println("spring 消费者1接收到消息:【" + msg + "】");
Thread.sleep(20); // 休眠20毫秒
}
// 注意他们绑定的都是work.queue这个队列
// 消费者2
@RabbitListener(queues = "work.queue")
public void listenWorkQueueMessage2(String msg) throws InterruptedException {
System.out.println("spring 消费者2接收到消息:【" + msg + "】");
Thread.sleep(200); // 休眠200毫秒
}
}
可以看到消费者1很快完成了自己的25条消息。消费者2却在缓慢的处理自己的25条消息。
也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。这样显然是有问题的。
我们必须要实现能者多劳的一个效果,你能力好多消费点,那么怎么实现喃?
需要在yml里面进行配置。
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
# 只要你处理完一条信息你就可以消费下一个消息,就不会平均分配
3.1.3.总结:
Work模型的使用:
- 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量
发布/订阅
下面的三种
3.2.Fanout
这个就是发送到交换机上面去
发布订阅的模型如图:
首先我们需要为fanout写一个配置类,来进行交换机和queue的绑定
配置类
@Configuration // 配置类注解
public class FanoutConfig{
@Bean
public FanoutExchange fanoutExchange(){
// 交换机的名称
return new FanoutExchange("itcast.fanout");
}
// 创建队列1
@Bean
public Queue fanoutQueue1(){
// 队列的名称
return new Queue("fanout.Queue1");
}
// 与交换机进行绑定
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder
.bind(fanoutQueue1)
.to(fanoutExchange);
}
// 创建队列2
@Bean
public Queue fanoutQueue2(){
// 队列的名称
return new Queue("fanout.Queue2");
}
// 与交换机进行绑定
@Bean
public Binding fanoutBinding2(Queue fanoutQueu2, FanoutExchange fanoutExchange){
// 队列2进行绑定
return BingBuilder
.bind(fanoutQueue2)
.to(fanoutExchange);
}
}
经过上面的步骤后就实现了绑定了 下面我们就写生产者 和 消费者
3.2.1.publish(生产者)
这个消息就是发送到交换机上面了 就不是队列上面了
// 发送到交换机上面
@Test
public void testSendFanoutQueueExchage() {
// 交换机名称呢
String exchangeName = "itcast.fanout";
// 发送的消息
String msg = "hello, everyone";
// 发送消息
// 这个就是发送到转换机上面
// 然后转换机自动发送到绑定的队列上面去 然后队列就可以接受到消息了
// 直接发送到交换机上面去
rabbitTemplate.convertAndSend(exchangeName, "", msg);
}
3.2.2.consumer(消费者)
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutListener1(String msg){
System.out.println("消费者1接受到的消息是[" + msg + "]");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutListener2(String msg){
System.out.println("消费者2接受到的消息是[" + msg + "]");
}
3.2.3.总结:
需要单独写一个配置文件类,来实现配置。
3.3.Direct
这个也是依靠交换机实现队列的绑定来实现,只不过他有一个特别的地方,
就是每一个队列接受的东西是有一定的限制的,比如队列已对于交换机传过来的会根据里面的key来看自己是否有资格获取这个消息。
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个
RoutingKey
(路由key) - 消息的发送方在 向 Exchange发送消息时,也必须指定消息的
RoutingKey
。 - Exchange不再把消息交给每一个绑定的队列,而是根据消息的
Routing Key
进行判断,只有队列的Routingkey
与消息的Routing key
完全一致,才会接收到消息
3.3.1.publish(生产者)
// 发送到交换机上面
@Test
public void testSendFanoutQueueExchage() {
// 交换机名称呢
String exchangeName = "itcast.direct";
// 发送的消息
String msg = "hello, everyone";
// 发送消息
// 这个就是发送到转换机上面
// 然后转换机自动发送到绑定的队列上面去 然后队列就可以接受到消息了
// 直接发送到交换机上面去
// 第二个参数就是我们的key
rabbitTemplate.convertAndSend(exchangeName, "red", msg);
}
3.3.2.consumer(消费者)
使用注解开发,交换机默认的类型就是Direct 所以我们exchange里面的type不写也可以的
// direct
// 这个只接受key为 red 和 yellow的
@RabbitListener(bindings = @QueueBinding(
value = @Queue("direct.queue1"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
))
public void listenDirectQueue1(String msg){
System.out.println("direct.queue1的消息:【" + msg + "】");
}
// 这个只接受key为red 和 blue的
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","blue"}
))
public void listenDirectQueue2(String msg){
System.out.println("direct.queue2的消息:【" + msg + "】");
}
3.3.3.总结:
描述下Direct交换机与Fanout交换机的差异?
- Fanout交换机将消息路由给每一个与之绑定的队列
- Direct交换机根据RoutingKey判断路由给哪个队列
- 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
基于@RabbitListener注解声明队列和交换机有哪些常见注解?
- @Queue
- @Exchange
3.4.Topic
这个和我们的direct是很相似的,这个主要就可以使用表达是来进行区分key,
比如key = “chain.#” 表示只要key中含有 chain.什么的都可以进行消费
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
图示:
解释:
- Queue1:绑定的是
china.#
,因此凡是以china.
开头的routing key
都会被匹配到。包括china.news和china.weather - Queue2:绑定的是
#.news
,因此凡是以.news
结尾的routing key
都会被匹配。包括china.news和japan.news
3.4.1.publish(生产者)
这个和我们的direct的生产者基本上不变的 只是key会变化
// 发送到交换机上面
@Test
public void testSendFanoutQueueExchage() {
// 交换机名称呢
String exchangeName = "itcast.topic";
// 发送的消息
String msg = "hello, everyone";
// 发送消息
// 这个就是发送到转换机上面
// 然后转换机自动发送到绑定的队列上面去 然后队列就可以接受到消息了
// 直接发送到交换机上面去
// 第二个参数就是我们的key
// 这个key就是 chain.# 可以接受到 #.news也可以接受到
rabbitTemplate.convertAndSend(exchangeName, "chain.news", msg);
}
3.4.2.consumer(消费者)
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String msg){
System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg){
System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
3.4.3.总结
描述下Direct交换机与Topic交换机的差异?
- Topic交换机接收的消息RoutingKey必须是多个单词,以
**.**
分割 - Topic交换机与队列绑定时的bindingKey可以指定通配符
#
:代表0个或多个词*
:代表1个词