RabbitMQ学习笔记

RabbitMQ学习笔记

了解RabbitMQ

一.RabbitMQ简介:

RabbitMQ是一套开源的消息队列服务软件,是由 Advanced Message Queuing Protocol (AMQP) 协议实现 消息队列 ,使用高性能、健壮以及可伸缩性出名的 Erlang 写成。

协议:消息中间件负责数据的传递,存储,和分发消费三个部分,数据的存储和分发的过程中肯定要遵循某种约定成俗的规范,你是采用底层的TCP/IP,UDP协议还是其他的自己取构建等,而这些约定成俗的规范就称之为协议。

AMQP:(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
特性:
1:分布式事务支持。
2:消息的持久化支持。
3:高性能和高可靠的消息处理优势。

Erlang: Erlang是一种通用的面向并发的编程语言 , 是运行于虚拟机的解释性语言。Erlang同时支持的操作系统有linux,windows,unix等,可以说适用于主流的操作系统上,尤其是它支持多核的特性非常适合多核CPU,而分布式特性也可以很好融合各种分布式集群。

二.RabbitMQ的工作原理介绍:

在这里插入图片描述

核心概念:
Server:又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server
Connection:连接,应用程序与Broker的网络连接 TCP/IP/ 三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Queue:队列:也成为Message Queue,消息队列,保存消息并将它们转发给消费者。

三.RabbitMQ的运行流程在这里插入图片描述

基于注解使用SpringBoot整合RabbitMQ

一.fanout模式

Fanout 这种类型非常简单。正如从名称中猜到的那样,它将接收到的所有消息广播到它知道的 所有队列中。

模式图
在这里插入图片描述
1.在pom文件中导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.配置ymal文件

server:
  port: 8021  #服务端口号
spring:
  #给项目来个名字
  application:
    name: rabbitmq-provider
  #配置rabbitMq 服务器
  rabbitmq:
    host: localhost
    port: 5672
    username: root  
    password: root
    #虚拟host 可以不设置,使用server默认host
    virtual-host: /

3.代码部分

生产者:

package com.cainiao.producer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.UUID;
import static com.cainiao.producer.DirectRabbitConfig.EXCHANGE_NAME;
import static com.cainiao.producer.DirectRabbitConfig.ROUTING_KEY;
@Component
public class OrderService {
    @Resource
    private RabbitTemplate rabbitTemplate;
    //创建订单方法
    public void makeOrder(Long userId) {
        //生成订单编号
        String orderNumer = UUID.randomUUID().toString();
        System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
        // 发送订单信息给RabbitMQ fanout
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, orderNumer);
    }
}

绑定

package com.cainiao.producer.fandout;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutRabbitConfig {
    public final static String EXCHANGE_NAME = "fanout_order_exchange";  //自定义交换机名
    public final static String ROUTING_KEY = "";                         //自定义路由key
    //自定义队列名
    public final static String EMAIL_QUEUE = "email.fanout.queue";   //邮箱队列
    public final static String SMS_QUEUE = "sms.fanout.queue";       //短信队列
    public final static String WECHAT_QUEUE = "wechat.fanout.queue"; //微信队列
    @Bean
    public Queue emailQueue() {
//        参数解释:
//         durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:            当前连接有效
//         exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于            durable
//         autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
//        一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue(EMAIL_QUEUE, true,false,false);
    }
    @Bean
    public Queue smsQueue() {
        return new Queue(SMS_QUEUE, true,false,false);
    }
    @Bean
    public Queue weChatQueue() {
        return new Queue(WECHAT_QUEUE, true,false,false);
    }
    @Bean
    public FanoutExchange fanoutOrderExchange() {
        return new FanoutExchange(EXCHANGE_NAME, true, false);
    }
    //绑定  将队列和交换机绑定
    @Bean
    public Binding bindingFanout1() {
        return BindingBuilder.bind(weChatQueue()).to(fanoutOrderExchange());
    }
    @Bean
    public Binding bindingFanout() {
        return BindingBuilder.bind(smsQueue()).to(fanoutOrderExchange());
    }
    @Bean
    public Binding bindingFanout3() {
        return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange());
    }
}

消费者代码:

Email消费者

package com.cainiao.consumer.fanout;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "email.fanout.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "fanout_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.FANOUT)
))
@Component
public class EmailService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("邮箱-------------->" + message);
    }
}

SMS消费者

package com.cainiao.consumer.fanout;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@RabbitListener(bindings =@QueueBinding(
        value = @Queue(value = "sms.fanout.queue",autoDelete = "false"),
        exchange = @Exchange(value = "fanout_order_exchange",
                type = ExchangeTypes.FANOUT)
))
@Component
public class SMSService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("短信-------------->" + message);
    }
}

WeChat消费者

package com.cainiao.consumer.fanout;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@RabbitListener(bindings =@QueueBinding(
        value = @Queue(value = "wechat.fanout.queue",autoDelete = "false"),
        exchange = @Exchange(value = "fanout_order_exchange",
                type = ExchangeTypes.FANOUT)
))
@Component
public class WeChatService {
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("微信-------------->" + message);
    }
}

测试代码:

package com.cainiao.producer;
import com.cainiao.producer.fandout.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class ProducerApplicationTests {
    @Resource
    OrderService orderService;
    @Test
    public void contextLoads() throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            Long userId = 0L + i;
            orderService.makeOrder(userId);
        }
    }
}

二.Direct模式

Fanout模式中的我们的日志系统将所有消息广播给所有消费者,对此我们想做一些改变,例如我们希 望将日志消息写入磁盘的程序仅接收严重错误(errros),而不存储哪些警告(warning)或信息(info)日志 消息避免浪费磁盘空间。Fanout 这种交换类型并不能给我们带来很大的灵活性-它只能进行无意识的 广播,在这里我们将使用 direct 这种类型来进行替换,这种类型的工作方式是,消息只去到它绑定的 routingKey 队列中去。
在这里插入图片描述

实现思路:这里使用了三类消费者分别是man,woman,human。然后使用routingkey绑定到队列当中去。生产者产生消息时候附带routingkey,交换机将匹配到对应的队列,消费者去对应的队列消费消息。

前面的步骤和之前一样,我就不重复代码了

1.在pom文件中导入依赖

2.配置ymal文件

3.代码部分:

生产者代码:

package com.cainiao.producer.direct;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.UUID;
import static com.cainiao.producer.direct.DirectRabbitConfig.EXCHANGE_NAME;
@Component
public class PeopleService {
    @Resource
    private RabbitTemplate rabbitTemplate;
    //创建订单方法
    public void peopleOrder(Long userId) {
        //生成订单编号
        String orderNumer = UUID.randomUUID().toString();
        System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
        // 发送订单信息给RabbitMQ fanout
        //消息传入交换机后通过routingKey匹配到不同的队列,给予不同消费者消费
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "man", orderNumer);
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "woman", orderNumer);
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "human", orderNumer);
}
}

绑定:

package com.cainiao.producer.direct;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectRabbitConfig {
    public final static String EXCHANGE_NAME = "direct_order_exchange";  //自定义交换机名
    //自定义队列名
    public final static String MAN_QUEUE = "man.direct.queue";           //男人队列
    public final static String WOMAN_QUEUE = "woman.direct.queue";       //女人队列
    public final static String HUMAN_QUEUE = "human.direct.queue";       //人类队列
    @Bean
    public Queue manQueue() {
//        参数解释:
//         durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:            当前连接有效
//         exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于            durable
//         autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
//        一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue(MAN_QUEUE, true,false,false);
    }
    @Bean
    public Queue womanQueue() {
        return new Queue(WOMAN_QUEUE, true,false,false);
    }
    @Bean
    public Queue humanQueue() {
        return new Queue(HUMAN_QUEUE, true,false,false);
    }
    @Bean
    public DirectExchange directOrderExchange() {
        return new DirectExchange(EXCHANGE_NAME, true, false);
    }
    //绑定  将队列和交换机绑定
    @Bean
    public Binding bindingDirect1() {
        return BindingBuilder.bind(manQueue()).to(directOrderExchange()).with("man");
    }

    @Bean
    public Binding bindingDirect2() {
        return BindingBuilder.bind(womanQueue()).to(directOrderExchange()).with("woman");
    }
    @Bean
    public Binding bindingDirect3() {
        return BindingBuilder.bind(humanQueue()).to(directOrderExchange()).with("human");
    }
}

消费者:

Human消费者

package com.cainiao.consumer.direct;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "human.direct.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "direct_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.DIRECT)
))
@Component
public class HumanService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("人类-------------->" + message);
    }
}

Man消费者

package com.cainiao.consumer.direct;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "man.direct.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "direct_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.DIRECT)
))
@Component
public class ManService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("男人-------------->" + message);
    }
}

Woman消费者

package com.cainiao.consumer.direct;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "woman.direct.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "direct_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.DIRECT)
))
@Component
public class WomanService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("女人-------------->" + message);
    }
}

测试代码

 @Resource
    PeopleService peopleService;
    @Test
    public void DirectExchangeTest() throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            Long userId = 0L + i;
            peopleService.peopleOrder(userId);
        }
    }

三.Topic模式

在direct模式中,我们改进了系统。我们没有使用只能进行随意广播的 fanout 交换机,而是使用了 direct 交换机,从而有能实现有选择性地接收消息。
在这里插入图片描述
尽管使用 direct 交换机改进了我们的系统,但是它仍然存在局限性——比方说我们想实现人类既可以收到男人的消息也可以收到女人的消息,那这个时候direct 就办不到了,这个时候就只能使用 topic 类型。

1.在pom文件中导入依赖

2.配置ymal文件

3.代码部分:

生产者代码:

package com.cainiao.producer.topic;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.UUID;
import static com.cainiao.producer.topic.TopicRabbitConfig.EXCHANGE_NAME;
@Component
public class TopicPeopleService {
    @Resource
    private RabbitTemplate rabbitTemplate;
    //创建订单方法
    public void topicPeopleOrder(Long userId) {
        //生成订单编号
        String orderNumer = UUID.randomUUID().toString();
        System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
        // 发送订单信息给RabbitMQ fanout
        //消息传入交换机后通过routingKey匹配到不同的队列,给予不同消费者消费
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "human.woman",orderNumer);
//        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "human.man",orderNumer);
//        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "human",orderNumer);
//        rabbitTemplate.convertAndSend(EXCHANGE_NAME, "*.woman",orderNumer);
}
}

绑定:

package com.cainiao.producer.topic;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicRabbitConfig {
    public final static String EXCHANGE_NAME = "topic_order_exchange";  //自定义交换机名
    //自定义队列名
    public final static String MAN_QUEUE = "man.topic.queue";           //男人队列
    public final static String WOMAN_QUEUE = "woman.topic.queue";       //女人队列
    public final static String HUMAN_QUEUE = "human.topic.queue";       //人类队列
    @Bean
    public Queue topicManQueue() {
//        参数解释:
//         durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:            当前连接有效
//         exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于            durable
//         autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
//        一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue(MAN_QUEUE, true,false,false);
    }
    @Bean
    public Queue topicWomanQueue() {
        return new Queue(WOMAN_QUEUE, true,false,false);
    }
    @Bean
    public Queue topicHumanQueue() {
        return new Queue(HUMAN_QUEUE, true,false,false);
    }
    @Bean
    public TopicExchange topicOrderExchange() {
        return new TopicExchange(EXCHANGE_NAME, true, false);
    }
    //绑定  将队列和交换机绑定
    @Bean
    public Binding bindingTopic1() {
        return BindingBuilder.bind(topicManQueue()).to(topicOrderExchange()).with("");

    }
    @Bean
    public Binding bindingTopic2() {
        return BindingBuilder.bind(topicWomanQueue()).to(topicOrderExchange()).with("");
    }
    @Bean
    public Binding bindingTopic3() {
        return BindingBuilder.bind(topicHumanQueue()).to(topicOrderExchange()).with("");
    }
}

消费者:

TopicHumanService

package com.cainiao.consumer.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "human.topic.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "topic_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.TOPIC),
        key = "human.#"  //类型区分队列的关键
))
@Component
public class TopicHumanService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("人类-------------->" + message);
    }
}

TopicManService

package com.cainiao.consumer.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "man.topic.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "topic_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.TOPIC),
        key = "*.man.#"
))
@Component
public class TopicManService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("男人-------------->" + message);
    }
}

TopicWomanService

package com.cainiao.consumer.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
        value = @Queue(value = "woman.topic.queue",autoDelete = "false"),
        // order.fanout 交换机的名字 必须和生产者保持一致
        exchange = @Exchange(value = "topic_order_exchange",
                // 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
                type = ExchangeTypes.TOPIC),
        key = "*.woman.#"
))
@Component
public class TopicWomanService {
    // @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
    @RabbitHandler
    public void messageService(String message){
        // 此处省略发邮件的逻辑
        System.out.println("女人-------------->" + message);
    }
}

测试:

 @Resource
    TopicPeopleService topicPeopleService;
    @Test
    public void TopicExchangeTest() throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            Long userId = 0L + i;
            topicPeopleService.topicPeopleOrder(userId);
        }
    }

四.死信队列

死信: 顾名思义就是无法被消费的消息,字面意思可以这样理 解,一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息 进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中。还有比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效。

产生原因

  • 消息 TTL 过期 :TTL是Time To Live的缩写, 也就是生存时间
  • 队列达到最大长度:队列满了,无法再添加数据到 mq 中
  • 消息被拒绝:(basic.reject 或 basic.nack) 并且 requeue=false.

在这里插入图片描述
代码部分:

死信队列没有使用springboot因为那个绑定参数我没弄明白,见谅!!

1.导入依赖

         <!--rabbitmq 依赖客户端-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.8.0</version>
        </dependency>
        <!--操作文件流的一个依赖-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

2.构建链接工具类

package utils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitMqUtils {
    //得到一个连接的 channel
    public static Channel getChannel() throws Exception{
        //创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //我是本地用Windows跑的所以是localhost 如果是云服务器需要使用云服务器ip,链接不上打开安全组开放端口
        factory.setHost("localhost"); 
        //factory.setPort(5672);     //端口号不用设置也可以
        factory.setUsername("root");  //账户密码根据自己本机的账户密码指定
        factory.setPassword("root");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        return channel;
    }
}

3.生产者代码:

package dead;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import utils.RabbitMqUtils;

public class producer {
    //交换机名称
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    public static void main(String[] argv) throws Exception {
        //通过工具类获取链接
        Channel channel = RabbitMqUtils.getChannel();
        //发布交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //设置消息的 TTL 时间 10s
        AMQP.BasicProperties properties = new                      AMQP.BasicProperties().builder().expiration("10000").build();
        //该信息是用作演示队列个数限制
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            Thread.sleep(1000);
            channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", properties, message.getBytes());
            System.out.println("生产者发送消息:" + message);
        }

    }
}

4.正常队列消费者代码:

package dead;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import utils.RabbitMqUtils;
import java.util.HashMap;
import java.util.Map;

public class Consumer01  {
    //普通交换机名称
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机名称
    private static final String DEAD_EXCHANGE = "dead_exchange";
    public static void main(String[] args) throws Exception {
        //获取链接
        Channel channel = RabbitMqUtils.getChannel();
        //声明死信和普通交换机 类型为 direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue, false, false, false, null);
        //死信队列绑定:队列、交换机、路由键(routingKey)
        channel.queueBind(deadQueue, DEAD_EXCHANGE, "lisi");
        //正常队列绑定死信队列信息
        Map<String, Object> params = new HashMap<>();
        //正常队列设置死信交换机 参数 key 是固定值
        params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //正常队列设置死信 routing-key 参数 key 是固定值
        params.put("x-dead-letter-routing-key", "lisi");
        //params.put("",)
        //正常队列
        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue, false, false, false, params);
        channel.queueBind(normalQueue, NORMAL_EXCHANGE, "zhangsan");
        System.out.println("等待接收消息........... ");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("Consumer01 接收到消息" + message);
        };
        channel.basicConsume(normalQueue, true, deliverCallback, consumerTag -> {
        });
    }

}

5.死信队列消费者代码:

package dead;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import utils.RabbitMqUtils;

public class Consumer02 {
    //死信交换机名称
    private static final String DEAD_EXCHANGE = "dead_exchange";
    public static void main(String[] args) throws Exception {
        //获取链接
        Channel channel = RabbitMqUtils.getChannel();
        //声明交换机
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue, false, false, false, null);
        channel.queueBind(deadQueue, DEAD_EXCHANGE, "lisi");
        System.out.println("等待接收死信消息........... ");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("Consumer02 接收到消息" + message);
        };
        channel.basicConsume(deadQueue, true, deliverCallback, consumerTag -> {
        });
    }
}

使用流程:

1.先开启生产者代码,生产消息,我们在web页面就可以看到
在这里插入图片描述

normal队列是生产者产生的消息,因为我设置了消息10秒过期的策略,他会自动加载到绑定的死信队列当中
此时我们开启消费者2就可以将死信队列中的消息消费掉。
如果我们开启生产者后开启消费者1就是直接消费掉不会进入死信队列,但是我们可以添加参数让normal队列有长度限制,超出我们设置的最大值的消息会进入死信队列
我么只需要在
在这里插入图片描述

消费者1此图位置中添加

    params.put("x-max-length",5);

注意直接启动会报错,需要进入web页面将队列删除,队列已经存在再次添加参数就会报错,但是在生产环境不建议删掉,可以新建一个队列,改一下队列名字,或者在web页面添加绑定。
启动顺序:
1.先启动消费者1生成队列,然后停止运行,不然生产者生产一条就消费掉一条看不到死信队列的效果,
2.然后启动消费者2,生产者。
我们可以看到生产者生产五条消息后,剩下五条消息进入死信队列,过了10s后之前生产的五条消息也会进入死信队列被消费掉。这是控制台观测到的,如果想看web页面效果建议可以关掉消费者2,这样死信队列的消息不会被消费掉。

ps:其他的方式暂时还没学,打算学习springcloud中用到的部分再更新

RabbitMQ 常见面试题

1.为什么消息中间件不直接使用http协议呢?**

1: 因为http请求报文头和响应报文头是比较复杂的,包含了cookie,数据的加密解密,状态码,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速。
2:大部分情况下http大部分都是短链接,在实际的交互过程中,一个请求到响应很有可能会中断,中断以后就不会就行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取消息的过程,出现问题和故障要对数据或消息就行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行。

2. RabbitMQ 的使用场景有哪些?

①. 跨系统的异步通信,所有需要异步交互的地方都可以使用消息队列。就像我们除了打电话(同步)以外,还需要发短信,发电子邮件(异步)的通讯方式。

②. 多个应用之间的耦合,由于消息是平台无关和语言无关的,而且语义上也不再是函数调用,因此更适合作为多个应用之间的松耦合的接口。基于消息队列的耦合,不需要发送方和接收方同时在线。在企业应用集成(EAI)中,文件传输,共享数据库,消息队列,远程过程调用都可以作为集成的方法。

③. 应用内的同步变异步,比如订单处理,就可以由前端应用将订单信息放到队列,后端应用从队列里依次获得消息处理,高峰时的大量订单可以积压在队列里慢慢处理掉。由于同步通常意味着阻塞,而大量线程的阻塞会降低计算机的性能。

④. 消息驱动的架构(EDA),系统分解为消息队列,和消息制造者和消息消费者,一个处理流程可以根据需要拆成多个阶段(Stage),阶段之间用队列连接起来,前一个阶段处理的结果放入队列,后一个阶段从队列中获取消息继续处理。

⑤. 应用需要更灵活的耦合方式,如发布订阅,比如可以指定路由规则。

⑥. 跨局域网,甚至跨城市的通讯(CDN行业),比如北京机房与广州机房的应用程序的通信。

3. RabbitMQ 有哪些重要的角色?

RabbitMQ 中重要的角色有:生产者、消费者和代理:

  • 生产者:消息的创建者,负责创建和推送数据到消息服务器;
  • 消费者:消息的接收方,用于处理数据和确认消息;
  • 代理:就是 RabbitMQ 本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。
4. RabbitMQ 有哪些重要的组件?
  • ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用。
  • Channel(信道):消息推送使用的通道。
  • Exchange(交换器):用于接受、分配消息。
  • Queue(队列):用于存储生产者的消息。
  • RoutingKey(路由键):用于把生成者的数据分配到交换器上。
  • BindingKey(绑定键):用于把交换器的消息绑定到队列上。
5. RabbitMQ 中 vhost 的作用是什么?

vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。

6. RabbitMQ 的消息是怎么发送的?

首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息,客户端和 rabbit server 之间会创建一个 tcp 连接,一旦 tcp 打开并通过了认证(认证就是你发送给 rabbit 服务器的用户名和密码),你的客户端和 RabbitMQ 就创建了一条 amqp 信道(channel),信道是创建在“真实” tcp 上的虚拟连接,amqp 命令都是通过信道发送出去的,每个信道都会有一个唯一的 id,不论是发布消息,订阅队列都是通过这个信道完成的。

7. RabbitMQ 怎么保证消息的稳定性?
  • 提供了事务的功能。
  • 通过将 channel 设置为 confirm(确认)模式。
8. RabbitMQ 怎么避免消息丢失?
  1. 消息持久化

  2. ACK确认机制

  3. 设置集群镜像模式

  4. 消息补偿机制

  5. RabbitMQ 要保证消息持久化成功的条件有哪些?**
  6. 声明队列必须设置持久化 durable 设置为 true.

  7. 消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。

  8. 消息已经到达持久化交换器。

  9. 消息已经到达持久化队列。

以上四个条件都满足才能保证消息持久化成功。

9. RabbitMQ 持久化有什么缺点?

持久化的缺地就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题。

10. RabbitMQ 有几种广播类型?

三种广播模式:

  1. fanout: 所有bind到此exchange的queue都可以接收消息(纯广播,绑定到RabbitMQ的接受者都能收到消息);
  2. direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息;
  3. topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息;
11.RabbitMQ 怎么实现延迟消息队列?**
  1. 通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
  2. 使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。
12. RabbitMQ 集群有什么用?

集群主要有以下两个用途:

  • 高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用;
  • 高容量:集群可以承载更多的消息量。
13. RabbitMQ 节点的类型有哪些?
  • 磁盘节点:消息会存储到磁盘。
  • 内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型。
14. RabbitMQ 集群搭建需要注意哪些问题?
  • 各节点之间使用“–link”连接,此属性不能忽略。
  • 各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证。
  • 整个集群中必须包含一个磁盘节点。
15. RabbitMQ 每个节点是其他节点的完整拷贝吗?为什么?

不是,原因有以下两个:

  1. 存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;
  2. 性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。
16. RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况?

如果唯一磁盘的磁盘节点崩溃了,不能进行以下操作:

  • 不能创建队列
  • 不能创建交换器
  • 不能创建绑定
  • 不能添加用户
  • 不能更改权限
  • 不能添加和删除集群节点

唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西。

17. RabbitMQ 对集群节点停止顺序有要求吗?

RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。

gKey所bind的queue可以接收消息;

11.RabbitMQ 怎么实现延迟消息队列?**
  1. 通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
  2. 使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。
12. RabbitMQ 集群有什么用?

集群主要有以下两个用途:

  • 高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用;
  • 高容量:集群可以承载更多的消息量。
13. RabbitMQ 节点的类型有哪些?
  • 磁盘节点:消息会存储到磁盘。
  • 内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型。
14. RabbitMQ 集群搭建需要注意哪些问题?
  • 各节点之间使用“–link”连接,此属性不能忽略。
  • 各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证。
  • 整个集群中必须包含一个磁盘节点。
15. RabbitMQ 每个节点是其他节点的完整拷贝吗?为什么?

不是,原因有以下两个:

  1. 存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;
  2. 性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。
16. RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况?

如果唯一磁盘的磁盘节点崩溃了,不能进行以下操作:

  • 不能创建队列
  • 不能创建交换器
  • 不能创建绑定
  • 不能添加用户
  • 不能更改权限
  • 不能添加和删除集群节点

唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西。

17. RabbitMQ 对集群节点停止顺序有要求吗?

RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值