springcloud(day03)

springcloud笔记(实用篇day03)

1.初始MQ

什么是mq,首先我们要先了解到什么是异步通信,什么是同步通信

同步通信肯定很简单:比如我们打的微信视频,能时刻知道你在干什么,这个就是同步通讯

异步通讯也很好理解:就比如我们发微信给某个人,但是可能他没看到消息,没有回你,也可能是压根就不想回你,没回你消息,没有第一时间回你消息,这个就是异步通信。

同步通讯

  • 优点:时效性强,能立刻知道结果

  • 缺点:耦合度很高,并且如果一个模块出了问题,那么另外一个的所有服务都无法执行

异步通信

  • 优点:解耦合,分模块,各模块之间不会相互影响,吞吐量提升,无需等待订阅者处理完成,响应更快速
  • 缺点:架构复杂了,业务没有明显的流程线,不好管理,不知道是谁先执行的,谁后执行的,需要依赖于Broker的可靠、安全、性能,就是那个中间层,对他的要求是很高的,需要接受高并发

那么Broker就是我们的mq

1.1.常见的MQ

几种常见MQ的对比:

RabbitMQActiveMQRocketMQKafka
公司/社区RabbitApache阿里Apache
开发语言ErlangJavaJavaScala&Java
协议支持AMQP,XMPP,SMTP,STOMPOpenWire,STOMP,REST,XMPP,AMQP自定义协议自定义协议
可用性一般
单机吞吐量一般非常高
消息延迟微秒级毫秒级毫秒级毫秒以内
消息可靠性一般一般

追求可用性:Kafka、 RocketMQ 、RabbitMQ

追求可靠性:RabbitMQ、RocketMQ

追求吞吐能力:RocketMQ、Kafka

追求消息低延迟:RabbitMQ、Kafka

1.2.MQ的安装

使用docker拉取下来,然后挂载容器卷,绑定两个默认的端口,15672 和 5672,5672是实现消息的读取的,15672是实现后台的管理的,进行可视化的查看。

1.3.快速入门

有两个部分:

  1. 生产者 publish (产生消息的)
  2. 消费者 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个词

4.消息转换器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值