RabbitMQ快速入门和使用

文章目录

  • 1. 基础理论
    • 1.1. 同步调用与异步调用
    • 1.2. RabbitMQ 安装与运行
      • 1.2.1. 常见消息模型
  • 2. 基本消息队列的应用
    • 2.1. 消息发送流程
    • 2.2. 消息接收流程
  • 3. SpringAMQP的基础理论与应用(想快速应用看这里)
    • 3.1. 基础理论
    • 3.2. 【案例一】实现HelloWorld中的基础消息队列功能
    • 3.3. 【案例二】 Work Queue 工作队列
    • 3.4. 发布订阅模式
      • 3.4.1. 【案例三】广播(Fanout)
      • 3.4.2. 【案例四】路由(Direct)
      • 3.4.3. 【案例五】主题(Topic)
    • 3.5 消息转换器

1. 基础理论

1.1. 同步调用与异步调用

同步调用

  • 优点:时效性强,可以立即得到结果
  • 问题:
    • 耦合度高:每次加入新的需求,都要修改原来的代码
    • 性能和吞吐能力下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用时间之和
    • 资源浪费,有额外的资源消耗:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,在高并发场景下会极度浪费系统资源
    • 级联失败:如果服务提供者出现问题,所有调用方都会跟着出问题,迅速导致整个微服务群故障

异步调用
常见实现是事件驱动模式

  • 优势
    • 服务解耦
    • 性能提升,吞吐量提高
    • 服务没有强依赖,不担心级联失败问题
    • 流量削峰
  • 缺点
    • 依赖于Broker的可靠性、安全性、吞吐能力
    • 架构复杂了,业务没有明显的流程线,不好追踪管理

1.2. RabbitMQ 安装与运行

MQ(MessageQueue):消息队列,存放消息的队列,事件驱动框架中的Broker

RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址:https://www.rabbitmq.com

安装MQ(docker形式)

docker pull rabbitmq:3-management

运行MQ(docker形式)

docker run \
-e RABBITMQ_DEFAULT_USER=用户名 \
-e RABBITMQ_DEFAULT_PASS=密码 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management

概念
channel:操作MQ的工具
exchange:路由消息到队列中
queue:缓存消息
virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组

RabbitMQ快速入门和使用_第1张图片

1.2.1. 常见消息模型

① 基本消息队列(BasicQueue)
publisher:消息发布者,将消息发送到队列queue
queue:消息队列,负责接受并缓存消息
consumer:消息订阅者
请添加图片描述

② 工作消息队列(WorkQueue)RabbitMQ快速入门和使用_第2张图片

③ 发布订阅(Publish/Subscribe),有根据交换机类型不同分为三种:
Fanout Exchange:广播
RabbitMQ快速入门和使用_第3张图片

Direct Exchange:路由
RabbitMQ快速入门和使用_第4张图片

Topic Exchange:主题
RabbitMQ快速入门和使用_第5张图片

2. 基本消息队列的应用

依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>

2.1. 消息发送流程

1.简历connection
2.创建channel
3.利用channel声明队列
4.利用channel向队列发送消息

代码

    @Test
    public void testSendMessage() throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("");//ip
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("");//账号
        factory.setPassword("");//密码
        
        // 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();
    }

2.2. 消息接收流程

1.简历connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery
5.利用channel将消费者与队列绑定

代码

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("");//ip
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("");//账号
        factory.setPassword("");//密码
        // 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("等待接收消息。。。。");
    }

3. SpringAMQP的基础理论与应用(想快速应用看这里)

官方地址:https://spring.io/projects/spring-amqp

3.1. 基础理论

AMQP(Advanced Message Queuing Protocol):高级消息队列协议,是用于在应用程序或之间传递业务消息的开放标准。该协议与语言和平台无关,更服务微服务中独立型的要求。

Spring AMQP:是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两个部分,其中spring-amqp是抽象基础,spring-rabbit是底层的默认实现。

Spring AMQP 特点
① 侦听器容器,用于异步处理入站消息
② 用于发送和接收消息的RabbitTemplate
③ RabbitAdmin用于自动声明队列,交换和绑定

3.2. 【案例一】实现HelloWorld中的基础消息队列功能

请添加图片描述
流程:
① 在父工程中引入spring-amqp的依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>

② 在application.yml中配置rabbitmq的信息

spring:
  rabbitmq:
    host: ip
    port: 5672
    virtual-host: /
    username: 账号
    password: 密码

③ 在publisher服务中利用RabbitTemplate发送消息到simple.queue队列

@RunWith(SpringRunner.class)
@SpringBootTest
public class PublisherTest {

	@Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue(){
        String queueName = "simple.queue";
        String message = "hello, spring amqp";
        rabbitTemplate.convertAndSend(queueName,message);
    }
}

④ 在consumer服务中编写消费逻辑,绑定simple.queue队列

@Component
public class SpringRabbitListener {
    
    //@RabbitListener(queues = "simple.queue")
    @RabbitListener(queuesToDeclare = { @Queue("simple.queue")})//如果队列不存在就创建队列
    public void listenSimpleQueueMessage(String msg)throws InterruptedException{
        System.out.println("spring 消费者接收到消息:【"+msg+"】");
    }
   
}

3.3. 【案例二】 Work Queue 工作队列

Work Queue,工作队列,可以提高消息处理速度,避免消息堆积
RabbitMQ快速入门和使用_第6张图片

流程:
① 在 publisher 服务中定义测试方法,每秒产生50条消息,发送至simple.queue

	@Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testWorkQueue() throws Exception{
        String queueName = "simple.queue";
        String message = "hello, spring amqp";
        for (int i=0;i<50;i++){
            rabbitTemplate.convertAndSend(queueName,message+i);
            Thread.sleep(20);
        }
    }

② 在 consumer 服务中定义两个消息监听者,都监听simple.queue
③ 消费者1每秒处理50条数据,消费者2每秒处理5条数据

@Component
public class SpringRabbitListener {

    @RabbitListener(queuesToDeclare = { @Queue("simple.queue")})
    public void listenWorkQueueMessage(String msg)throws InterruptedException{
        System.out.println("spring 消费者【1】接收到消息:【"+msg+"】" + LocalTime.now());
        Thread.sleep(20);
    }
    @RabbitListener(queuesToDeclare = { @Queue("simple.queue")})
    public void listenWorkQueueMessage2(String msg)throws InterruptedException{
        System.out.println("spring 消费者【2】接收到消息:【"+msg+"】" + LocalTime.now());
        Thread.sleep(200);
    }
}

④ 因为存在消息预取机制,队列中的消息会平均分配给两个队列,修改配置文件,空值预取消息的上限制

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

3.4. 发布订阅模式

发布订阅模式与之前案例的区别是,允许将同一消息发送给多个消费者,实现方式是加入了exchange(交换机)。
常见交换机类型包括:广播(Fanout)、路由(Direct)、话题(Topic)

3.4.1. 【案例三】广播(Fanout)

Fanout Exchange 会将接收到的消息路由到每一个跟其绑定的queue
RabbitMQ快速入门和使用_第7张图片
流程:
① 在 consumer 服务中,创建配置类,声明队列、交换机,并将两者绑定(创建绑定关系)

@Configuration
public class FanoutConfig {
    //声明Fanout交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("itcast.fanout");
    }
    //声明第一个队列
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }
    //绑定队列1和交换机
    @Bean
    public Binding bingingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }
    //声明第二个队列
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue1");
    }
    //绑定队列2和交换机
    @Bean
    public Binding bingingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

② 在 consumer 服务中,编写两个消费者方法,分别监听 fanout.queue1 和 fanout.queue2

@Component
public class SpringRabbitListener {
    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1Message(String msg){
        System.out.println("spring 消费者 1 接收到消息:【"+msg+"】");
    }
    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2Message(String msg){
        System.out.println("spring 消费者 2 接收到消息:【"+msg+"】");
    }
}

③ 在 publisher 中编写测试方法,向 itcast.fanout交换机发送消息

	@Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testSendFanoutExchange(){
        //交换机名称
        String exchangeName = "itcast.fanout";
        //消息
        String msg = "hello,every one";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName,"",msg);
    }

交换机的作用:

① 接收publisher发送的消息
② 将消息按照规则路由到与之绑定的队列
③ 不能缓存消息,路由失败,消息丢失
④ FanoutExchange会将消息路由到每个绑定的队列

3.4.2. 【案例四】路由(Direct)

Direct Exchange会将接收到的消息根据规则路由到指定的Queue。
每一个Queue都与Exchange设置一个BindingKey
发布者发送消息时,指定消息的RoutingKey
Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
RabbitMQ快速入门和使用_第8张图片
流程:
① 在consumer 服务中 利用@RabbitListener声明Exchange、Queue、RoutingKey

@Component
public class SpringRabbitListener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
            key = {"red","blue"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("spring 消费者 1 接收到 direct.queue1 的消息:【"+msg+"】");
    }
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
            key = {"red","yellow"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("spring 消费者 2 接收到 direct.queue2 的消息:【"+msg+"】");
    }
}

② 在 publish 服务发送消息到DirectExchange

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendDirectExchange(){
        //交换机名称
        String exchangeName = "itcast.direct";
        //消息
        String msg = "hello,blue";
        //routingKey
        String routingKey = "blue";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName,routingKey,msg);
    }

Direct交换机与Fanout交换机的差异:
Fanout减缓及将消息路由给每一个与之绑定的队列,Direct交换机根据RoutingKey判断路由给哪个队列。如果多个队列具有相同的routingKey,则与Fanout功能类似。

3.4.3. 【案例五】主题(Topic)

Topic Exchange 与 Direct Exchange类似,区别在于routingKey必须是多个单词的列表,并且以.分割。
Queue与Exchange指定BingingKey时可以使用通配符:#代表0或多个单词,*代表一个单词
RabbitMQ快速入门和使用_第9张图片

流程:
① 利用 @RabbitListener 声明 Echange、Queue、RoutingKey
② 在 consumer 服务中,编写两个消费者方法,分别监听 topic.queue1 和 topic.queue2

@Component
public class SpringRabbitListener {

    @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("spring 消费者 1 接收到 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("spring 消费者 2 接收到 topic.queue2 的消息:【"+msg+"】");
    }

③ 在 publisher 中编写测试方法,向 itcast.topic 发送消息

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testSendTopicExchange(){
        //交换机名称
        String exchangeName = "itcast.topic";
        //消息
        String msg = "天气还行";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName,"china.weather",msg);
    }

3.5 消息转换器

在 SpringAMQP 的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。

Spring的对消息的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter类型的Bean即可。推荐使用JSON方式序列化。

步骤(两端需要):
① 引入依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformatgroupId>
    <artifactId>jackson-dataformat-xmlartifactId>
    <version>2.11.4version>
dependency>

② 声明 MessageConverter

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

你可能感兴趣的:(中间件,rabbitmq,spring,boot)