RabbitMQ小结

同步

优点:时效性较强,可以立即得到结果
缺点:
耦合度高-服务之间都是直接调用
性能和吞吐能力下降-响应时长为几个服务处理的后的总和
有额外的资源消耗-上游服务需要等下游服务的结果才能往后执行
有级联失败问题-下游服务如果出错,会造成上游服务也失败

总结

调用目标服务时,需要等待目标服务给出响应,才能继续执行下一步操作

 使用场景

微服务:订单服务调用用户服务,获取用户信息

异步

优点:
耦合度低-只跟Broker耦合
吞吐量提升-整个调用响应时长变短,提升吞吐量
故障隔离-下游服务故障,不会影响上游服务
流量削峰-高并发的请求数,由Broker来抗,不会直接打到具体的服务,后面可以根据服务的消费能力来慢慢消费
缺点:
依赖于Broker,对其要求高
引入了其他组件,架构变得复杂

总结

主业务执行后,不需要等后续的其他服务执行的结果,就可以给出响应。相当于开了一个线程去执行其他业务

使用场景

医院APP挂号业务:
1、用户在医院APP挂号,插入挂号记录到数据库
2、发送挂号成功的信息给用户
3、记录用户挂号的日志记录到日志文件中
以上三个步骤,在第一步成功后,2、3两步的执行先后顺序是没有要求的

RabbitMQ

RabbitMQ由Rabbit公司基于Erlang语言开发的,支持多协议的、性能优秀的消息队列产品。一般消息队列,俗称消息中间件

安装

第一步:

获取 rabbitmq 的镜像

第二步:

加载资料的 mq.tar

第三步:

docker load -i mq.tar

第四步:

启动容器

5672:MQ的通信端口

15672:管控台的端口

docker run \
 -e RABBITMQ_DEFAULT_USER=itheima \
 -e RABBITMQ_DEFAULT_PASS=itheima \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 --restart=always \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 rabbitmq:3-management

结构:

 

角色:

 1、publisher:生产者-用于生产消息的
2、consumer:消费者-用于消费消息的
3、exchange:交换机-按照指定的规则把消息路由到具体的队列
4、queue:队列-存储消息
5、virtualHost:虚拟主机-对不同用户的 exchange、queue、消息 的进行隔离

测试RabbitMQ

父工程依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.9.RELEASE</version>
    <relativePath/>
</parent>

<properties>
    <java.version>11</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--AMQP依赖,包含RabbitMQ-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!--单元测试-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

 

模仿生产者发送消息

@Test
    public void testSendMessage() throws IOException, TimeoutException {
        //1.创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.138.100");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("itheima");
        factory.setPassword("itheima");
        Connection connection = factory.newConnection();
        //2.创建通道
        Channel channel = connection.createChannel();
        //3.声明交换机与队列
        /**
         * @param queue 队列名称
         * @param durable 是否持久化
         * @param exclusive 是否排它
         * @param autoDelete 是否自动删除
         * @param arguments 队列其他参数
         * @return a declaration-confirm method to indicate the queue was successfully declared
         * @throws java.io.IOException if an error is encountered
         */
        channel.queueDeclare("simple.queue", true, false, false, null);
        //4.发送消息
        /**
         * exchange 交换机,默认的交换机是""
         * routingKey 路由Key
         * props 消费其他参数
         * body 消息内容
         */
        channel.basicPublish("", "simple.queue", null, "Hello,RabbitMQ!! Two".getBytes());
        //5.释放资源
        channel.close();
        connection.close();
    }

模仿消费者消费消息

 public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.138.100");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("itheima");
        factory.setPassword("itheima");
        Connection connection = factory.newConnection();
        //2.创建通道
        Channel channel = connection.createChannel();
        //3.声明交换机与队列
        /**
         * 如果队列已经存在,则不会重复创建
         */
        channel.queueDeclare("simple.queue", true, false, false, null);
        //4.消费消息
        channel.basicConsume("simple.queue", new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag  消息标签
             * Envelope envelope  封装了消息的对象(没有内容)
             * AMQP.BasicProperties properties  其他数据
             * byte[] body  消息内容
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(consumerTag);
                System.out.println(envelope.getDeliveryTag());
                System.out.println(envelope.getExchange());
                System.out.println(envelope.getRoutingKey());
                System.out.println("消息内容:" + new String(body));
            }
        });
        //5.卡住
        System.in.read();
    }

 

Spring AMQP的五种模式:

Simple-简单模式

模型

一个生产者,一个消费者

单发: null

配置文件:

spring:
  rabbitmq:
    host: 192.168.138.100 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itheima # 用户名
    password: itheima # 密码

生产者:  

 @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     *
     *@throws
     */
    @Test
    public void simpleTest() throws Exception{
        //这个API,必须先提前创建好队列,才能够发送
        //转换并发送        
        rabbitTemplate.convertAndSend("","simple.queue","SpringAMQP");
    }

消费者:

 @Component
public class MessageListener {
    
    //消费完后会自动签收
    @RabbitListener(queues = "simple.queue")
    public void simpleConsume(String message){
        System.out.println("收到的消息" + message);
    }
}

#这段代码是一个bean所以要启动引导类才会生效  

WorkQueue-工作队列模式

模型

 

 

一个生产者,多个消息者

单发:多个消费者之间是竞争关系

为了测试WorkQueue我们先要RabbitMQ页面重新创建一个名为mysimple.queue的消息队列

 

生产者  

/**
     * @throws Exception
     */
    @Test
    public void workQueueTest() throws Exception {
        for (int i = 0; i < 100; i++) {
            //转换并发送
            //这个API,必须先提前创建好队列,才能够发送
            //amqpTemplate和rabbitTemplate是一样的
            amqpTemplate.convertAndSend("", "mysimple.queue", "WorkQueue Message !!" + i);
        }

    }

 

消费者

#根据不同的消费能力,竞争消费的数量

 

//消费完后会自动签收
@RabbitListener(queues = "mysimple.queue")
public void workQueue1(String message){
    System.out.println("消费者1:" + message);
}

//消费完后会自动签收
@RabbitListener(queues = "mysimple.queue")
public void workQueue2(String message){
    System.out.println("消费者2:" + message);
}

 配置文件

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

这里有一个机制,就是RabbitMQ默认的机制是,根据就是根据消费者的数量平均分,而加了这个配置文件的话两个消费者才根据消费能确定竞争。  

Fanout-配置队列交换机及绑定关系

 

群发  

 一个生产者,多个消息者
通过交换机,把消息路由到队列,实现群发 #无条件群发

创建队列(Queue)、交换机(FanoutExchange)、指定队列与交换机的绑定关系(Binding)

值得注意的一点:配置类里面的Queue、FanoutExchange、Binding以上的类库都是 amqp.core 包下并且都是写在消费者应用

方式一: 创建队列和交换机使用的是new方式

 

@Configuration
public class RabbitConfig {
    //1.声明队列
    @Bean
    public Queue fanoutQueue1(){
        //创建队列,默认是持久化
        return new Queue("fanout.queue1");
    }

    @Bean
    public Queue fanoutQueue2(){
        //创建队列,默认是持久化
        return new Queue("fanout.queue2");
    }
    //2.声明交换机
    @Bean
    public Exchange itcastFanout(){
        //创建Fanout交换机,默认是持久化
        return new FanoutExchange("itcast.fanout");
    }
    //3.绑定队列到交换机
    @Bean
    public Binding queue1Exchange(Queue fanoutQueue1,FanoutExchange itcastFanout){
        return BindingBuilder.bind(fanoutQueue1).to(itcastFanout);
    }

    @Bean
    public Binding queue2Exchange(Queue fanoutQueue2,FanoutExchange itcastFanout){
        return BindingBuilder.bind(fanoutQueue2).to(itcastFanout);
    }
}
 

首先,通过 fanoutQueue1() 和 fanoutQueue2() 两个方法声明了两个队列,并返回相应的 Queue 对象。
    然后,通过 itcastFanout() 方法声明了一个 Fanout 类型的交换机,并返回一个 Exchange 对象。
    最后,通过 queue1Exchange() 和 queue2Exchange() 方法分别将队列 fanoutQueue1 和 fanoutQueue2 绑定到交换机 itcastFanout 上,并返回一个 Binding 对象。
    这样就完成了队列、交换机和绑定的配置。在 RabbitMQ 中,生产者发送消息到交换机,交换机根据绑定关系将消息分发到相应的队列中。
 

 方法二:使用建造者的方式,创建队列和交换机。

 

@Configuration
public class RabbitFanoutConfig {

    @Bean
    public Queue itheimaFanoutQueue1(){
        return QueueBuilder.durable("itheima.fanout.queue1").build();
    }

    @Bean
    public Queue itheimaFanoutQueue2(){
        return QueueBuilder.durable("itheima.fanout.queue2").build();
    }

    @Bean
    public FanoutExchange itheimaFanout(){
        return ExchangeBuilder.fanoutExchange("itheima.fanout").durable(true).build();
    }

    @Bean
    public Binding itheimaFanoutQueue1ItheimaFanout(Queue itheimaFanoutQueue1, FanoutExchange itheimaFanout){
        return BindingBuilder.bind(itheimaFanoutQueue1).to(itheimaFanout);
    }

    @Bean
    public Binding itheimaFanoutQueue2ItheimaFanout(Queue itheimaFanoutQueue2, FanoutExchange itheimaFanout){
        return BindingBuilder.bind(itheimaFanoutQueue2).to(itheimaFanout);
    }
}
 

 队列的创建方式:使用了 QueueBuilder.durable() 方法来创建队列,并将 durable 设置为 true,表示队列是持久化的。
交换机的创建方式:使用了 ExchangeBuilder.fanoutExchange().durable(true).build() 方法来创建 Fanout 类型的交换机,并设置为持久化的。

配置Direct交换机

模型

 

 群发

一个生产者,多个消息者
通过交换机,通过路由key的精确名称把消息路由到队列,实现群发
路由key【Routingkey】→一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

消费者

 @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(name = "direct.queue2"),//队列
                    exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),//交换机
                    key = {"yellow","red"}
            )
    })
    public void handleDirectQueue2(String message){
        System.out.println("direct.queue2 : " + message);
    }
}

 

  1. @RabbitListener: 该注解标识该方法是一个消息监听器,用于监听指定队列中的消息。

  2. @QueueBinding: 该注解定义了队列与交换机之间的绑定关系。

value: 指定队列的相关属性,@Queue(name = "direct.queue2")表示声明了一个名为"direct.queue2"的队列。

exchange: 指定交换机的相关属性,@Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT)表示声明了一个名为"itcast.direct"的直连类型交换机。

key: 绑定键,表示某个消息将被路由到指定的队列,key = {"yellow","red"}表示将绑定键为"yellow"和"red"的消息路由到该队列。
 

 

生产者  

@Test
    public void handleDirectTest() throws Exception{
        //转换并发送
        //这个API,必须先提前创建好队列,才能够发送
        rabbitTemplate.convertAndSend("itcast.direct","blue" , "Hello,blue");
        rabbitTemplate.convertAndSend("itcast.direct","yellow" , "Hello,yellow");
        rabbitTemplate.convertAndSend("itcast.direct","red" , "Hello,red");
    }

配置Topic交换机

 

 群发

一个生产者,多个消息者
通过交换机,通过路由key的部分名称把消息路由到队列,实现群发
1.*:匹配单个字符
2.#:匹配一个或多个字符

 消费者

@RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(name = "topic.queue1",durable = "true"),//队列
                    exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),//交换机
                    key = {"china.#"}
            )
    })
    public void handleTopicQueue1(String message){
        System.out.println("topic.queue1 : " + message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(name = "topic.queue2",durable = "true"),//队列
                    exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),//交换机
                    key = {"#.news"}
            )
    })
    public void handleTopicQueue2(String message){
        System.out.println("topic.queue2 : " + message);
    }

 

其中

value = @Queue(name = "topic.queue1",durable = "true"),//队列

这段代码中的注解配置对队列的名称和持久化属性进行了设置,具体解析如下:

value = @Queue(name = "topic.queue1", durable = "true")

name = "topic.queue1": 声明了一个名为 “topic.queue1” 的队列,名称为 “topic.queue1”。
durable = "true": 设置队列的持久化属性为 true,表示该队列在消息代理重启后仍然存在。

  exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),//交换机

name = "itcast.topic": 声明了一个名为 “itcast.topic” 的交换机,名称为 “itcast.topic”。
type = ExchangeTypes.TOPIC: 设置交换机的类型为主题类型,表示该交换机将使用主题匹配的方式来路由消息。 

key = {"china.#"}  

key = {"china.#"}`: 设置绑定键为 “china.#”,这里使用了主题匹配的通配符 "#”。“china.#” 表示匹配以 “china.” 开头的所有路由键。 

key = {"#.news"}  

key = {"#.news"}: 设置绑定键为 “#.news”,这里使用了主题匹配的通配符 “#”。”#.news" 表示匹配以 “.news” 结尾的所有路由键,并且可以包含任意数量的字词作为前缀。 

 测试

@Test
    public void topicTest() throws Exception{
        //转换并发送
        //这个API,必须先提前创建好队列,才能够发送
        rabbitTemplate.convertAndSend("itcast.topic","china.big" , "Hello,china.big");
        rabbitTemplate.convertAndSend("itcast.topic","china.big.news" , "Hello,china.big.news");
        rabbitTemplate.convertAndSend("itcast.topic","usa.news" , "Hello,usa.news");
        rabbitTemplate.convertAndSend("itcast.topic","china.gs.usa.news" , "Hello,china.gs.usa.news");
    } 

消息转换器

 

消息转换器,首先消息有神多种形式比如:加密、序列化、josh字符串、或者普通字符串。

一般很多时候我们以传输Josh字符串居多。

@Test
public void testSendMap() throws InterruptedException {
    // 准备消息
    Map<String,Object> msg = new HashMap<>();
    msg.put("name", "Jack");
    msg.put("age", 21);
    // 发送消息
    rabbitTemplate.convertAndSend("simple.queue","", msg);
}

这样未经过序列化和转Josh处理的代码,使用的默认的序列化器,就是jdk序列化器所以说最终的结果一定加密的字符串,它的占用的内存大。  

JSON转换器

第一步:

导入依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.5</version>
</dependency> 

第二步:

配置bean

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值