分布式消息通讯RabbitMQ工作模型

本章重点:

1.三种主要的交换机介绍

2.SpringBoot整合RabbitMQ三种交换机

3.死信队列

4.优先级队列和消息

5.服务端流控

6.消费端限流

RabbitMQ的特性 
RabbitMQ使用Erlang语言编写,使用Mnesia数据库存储消息。

1.可靠性(Reliability) RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。 
2.灵活的路由(Flexible Routing) 在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功 能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在 一起,也通过插件机制实现自己的 Exchange 。
3.消息集群(Clustering) 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。 
4.高可用(Highly Available Queues) 队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下 队列仍然可用。
5.多种协议(Multi-protocol) RabbitMQ 支持多种消息队列协议,比如 AMQP、STOMP、MQTT 等等。
6.多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby、PHP、C#、 JavaScript 等等。 
7.管理界面(Management UI) RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群 中的节点。
8.插件机制(Plugin System) RabbitMQ提供了许多插件,以实现从多方面扩展,当然也可以编写自己的插件。

工作模型

efe41350c5f79b0513ad4562fc7a77cad8f.jpg

RabbitMQ的术语介绍

Broker:即RabbitMQ的实体服务器。提供一种传输服务,维护一条从生产者到消费者的传输线路, 保证消息数据能按照指定的方式传输。
Exchange:消息交换机。指定消息按照什么规则路由到哪个队列Queue。
Queue:消息队列。消息的载体,每条消息都会被投送到一个或多个队列中。
Binding:绑定。作用就是将Exchange和Queue按照某种路由规则绑定起来。
Routing Key:路由关键字。Exchange根据Routing Key进行消息投递。定义绑定时指定的关键字称为 Binding Key。
Vhost:虚拟主机。一个Broker可以有多个虚拟主机,用作不同用户的权限分离。一个虚拟主机持有 一组Exchange、Queue和Binding。
Producer:消息生产者。主要将消息投递到对应的Exchange上面。一般是独立的程序。
Consumer:消息消费者。消息的接收者,一般是独立的程序。
Connection: Producer 和 Consumer 与Broker之间的TCP长连接。
Channel:消息通道,也称信道。在客户端的每个连接里可以建立多个Channel,每个Channel代表一 个会话任务。在RabbitMQ Java Client API中,channel上定义了大量的编程接口。

三种主要的交换机

  1. Direct Exchange 直连交换机 
  2. Topic Exchange 主题交换机 
  3. Fanout Exchange 广播交换机 

RabbitMQ基本使用:

本文直接使用SpringBoot来进行开发.

消息消费者:

项目结构:

bff9a719a890540cced60c9aed99cf115cd.jpg

添加pom依赖

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

添加连接配置  application.properties

spring.application.name=spring-boot-rabbitmq
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

创建配置类:RabbitConfig

在这里创建了三种类型的交换机,四个队列

package com.zbb.rabbitmq_consumer.config;
 
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * 配置类,需要使用configuration注解
 */
@Configuration
public class RabbitConfig {
    //定义三个交换机
 
    /**
     * 直连交换机
     * @return
     */
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("DIRECT_EXCHANGE");
    }
 
    /**
     * 主题交换机
     * @return
     */
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("TOPIC_EXCHANGE");
    }
 
    /**
     * 广播交换机
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("FANOUT_EXCHANGE");
    }
    //定义四个队列
    @Bean
    public Queue firsQueue(){
        return new Queue("FIST_QUEUE");
    }
 
    @Bean
    public Queue secondQueue(){
        return new Queue("SECOND_QUEUE");
    }
 
    @Bean
    public Queue thirdQueue(){
        return new Queue("THIRD_QUEUE");
    }
 
    @Bean
    public Queue fourthQueue(){
        return new Queue("FOURTH_QUEUE");
    }
 
    //定义四个绑定关系
    @Bean
    public Binding bindFirst(@Qualifier("firsQueue") Queue queue,
                             @Qualifier("directExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with("zbb.test");
    }
 
    @Bean
    public Binding bindSecond(@Qualifier("secondQueue") Queue queue,
                              @Qualifier("topicExchange") TopicExchange topicExchange) {
        return BindingBuilder.bind(queue).to(topicExchange).with("*.zbb.*");
    }
 
    @Bean
    public Binding bindThird(@Qualifier("thirdQueue") Queue queue,
                             @Qualifier("fanoutExchange") FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }
 
    @Bean
    public Binding bindFourth(@Qualifier("fourthQueue") Queue queue,
                              @Qualifier("fanoutExchange") FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }
 
}

创建消费者,四个,分别监听4个队列

1、FirstConsumer
 

package com.zbb.rabbitmq_consumer.consumer;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
 
@Component
@RabbitListener(queues = "FIST_QUEUE")
public class FirstConsumer {
    @RabbitHandler
    public void process(String msg) {
        System.out.println("消费者一收到消息:"+msg);
    }
}

2、SecondConsumer
 

package com.zbb.rabbitmq_consumer.consumer;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
 
@Component
@RabbitListener(queues = "SECOND_QUEUE")
public class SecondConsumer {
    @RabbitHandler
    public void process(String msg) {
        System.out.println("消费者二收到消息:"+msg);
    }
}

3、ThirdConsumer

package com.zbb.rabbitmq_consumer.consumer;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
 
@Component
@RabbitListener(queues = "THIRD_QUEUE")
public class ThirdConsumer {
    @RabbitHandler
    public void process(String msg) {
        System.out.println("消费者三收到消息:"+msg);
    }
}

4、FourthConsumer
 

package com.zbb.rabbitmq_consumer.consumer;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
 
@Component
@RabbitListener(queues = "FOURTH_QUEUE")
public class FourthConsumer {
    @RabbitHandler
    public void process(String msg) {
        System.out.println("消费者四收到消息:"+msg);
    }
}

消息生产者:

引入pom依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

创建生产者类,给三个交换机发送消息

package com.zbb.rabbitmq_producer.producer;
 
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component
public class MyProducer {
 
    @Autowired
    RabbitTemplate rabbitTemplate;
 
    public void send(){
        //直连
        rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "zbb.test",
                "this is a direct msg");
        //主题
        rabbitTemplate.convertAndSend("TOPIC_EXCHANGE","kaifa.zbb.IT", 
                "this is a Topic msg");
 
        //广播
        rabbitTemplate.convertAndSend("FANOUT_EXCHANGE", "",
                "this is a fanout msg");
    }
}

修改测试类:

package com.zbb.rabbitmq_producer;
 
import com.zbb.rabbitmq_producer.producer.MyProducer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqProducerApplicationTests {
 
    @Autowired
    MyProducer myProducer;
 
    @Test
    public void contextLoads() {
        myProducer.send();
    }
 
}
 
  1. 我们先启动消息生产者,打开管理界面可以看到四个队列里都有一条消息堆积

    2.现在去启动消息消费者之后会发现控制台打印出消息,并且管理界面发生改变:

5cc72cced61c4b444e750b3bd97f26eeedc.jpg

56b297fd39a199ea32a81f687ae2b8e14d0.jpg

这个就是RabbitMQ在java中的简单使用,具体在项目中的使用需要结合业务来进行消息的收发。

下面将介绍一下RabbitMQ中一个重要的东西:交换机Exchange

这里只介绍三种主要的交换机。

Direct Exchange 直连交换机

直连类型的交换机与一个队列绑定时,需要指定一个明确的绑定关键字(binding key)。
路由规则:发送消息到直连类型的交换机时,只有路由关键字(routing key)跟绑定关键字(binding key)完全匹配时,绑定的队列才能收到消息。

队列:

@Bean
public Queue firsQueue(){
    return new Queue("FIST_QUEUE");
}
交换机:

@Bean
public DirectExchange directExchange(){
    return new DirectExchange("DIRECT_EXCHANGE");
}
绑定:

@Bean
public Binding bindFirst(@Qualifier("firsQueue") Queue queue,
                         @Qualifier("directExchange") DirectExchange directExchange){
    return BindingBuilder.bind(queue).to(directExchange).with("zbb.test");
}
发送消息:

rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "zbb.test",
                "this is a direct msg");


Topic Exchange 主题交换机

定义:主题类型的交换机与一个队列绑定时,可以指定按模式匹配的routing key。
通配符有两个,*代表匹配一个单词。#代表匹配零个或者多个单词。单词与单词之间用 . 隔开。
路由规则:发送消息到主题类型的交换机时,routing key符合binding key的模式时,绑定的队列才能收到消息

队列:

@Bean
public Queue secondQueue(){
    return new Queue("SECOND_QUEUE");
}
交换机:

@Bean
public TopicExchange topicExchange(){
    return new TopicExchange("TOPIC_EXCHANGE");
}
绑定:

@Bean
public Binding bindSecond(@Qualifier("secondQueue") Queue queue,
                          @Qualifier("topicExchange") TopicExchange topicExchange) {
    return BindingBuilder.bind(queue).to(topicExchange).with("*.zbb.*");
}
发送消息:

//主题
rabbitTemplate.convertAndSend("TOPIC_EXCHANGE","kaifa.zbb.IT",
                              "this is a Topic msg");


Fanout Exchange 广播交换机,绑定两个队列thirdQueue   fourthQueue

定义:广播类型的交换机与一个队列绑定时,不需要指定binding key。
路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。

队列:

@Bean
public Queue thirdQueue(){
    return new Queue("THIRD_QUEUE");
}
@Bean
public Queue fourthQueue(){
    return new Queue("FOURTH_QUEUE");
}
交换机:

@Bean
public FanoutExchange fanoutExchange(){
    return new FanoutExchange("FANOUT_EXCHANGE");
}
发送消息:

//广播
rabbitTemplate.convertAndSend("FANOUT_EXCHANGE", "",
                "this is a fanout msg");

进阶知识 1、TTL(Time To Live) a、消息的过期时间 
有两种设置方式:
通过队列属性设置消息过期时间:

Map<String, Object> argss = new HashMap<String, Object>(); 
                argss.put("x-message-ttl",6000);
 
                channel.queueDeclare("TEST_TTL_QUEUE", false, false, false, argss);

设置单条消息的过期时间:

AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                                 // 持久化消息  
                                 .deliveryMode
                                 .contentEncoding("UTF-8")       
                                 .expiration("10000") // TTL        
                                 .build();
channel.basicPublish("", "TEST_TTL_QUEUE", properties, msg.getBytes());

b、队列的过期时间:

Map<String, Object> argss = new HashMap<String, Object>(); 
argss.put("x-message-ttl",6000);
 
channel.queueDeclare("TEST_TTL_QUEUE", false, false, false, argss);

队列的过期时间决定了在没有任何消费者以后,队列可以存活多久。

2、死信队列 
有三种情况消息会进入DLX(Dead Letter Exchange)死信交换机。

  1. (NACK || Reject ) && requeue == false
  2. 消息过期
  3. 队列达到最大长度(先入队的消息会被发送到DLX)

可以设置一个死信队列(Dead Letter Queue)与DLX绑定,即可以存储Dead Letter,消费者可以监听这个队列取走消息。

Map<String,Object> arguments = new HashMap<String,Object>(); 
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE"); 
// 指定了这个队列的死信交换机 
channel.queueDeclare("TEST_DLX_QUEUE", false, false, false, arguments); 
// 声明死信交换机 
channel.exchangeDeclare("DLX_EXCHANGE","topic", false, false, false, null); 
// 声明死信队列 
channel.queueDeclare("DLX_QUEUE", false, false, false, null); 
// 绑定 
channel.queueBind("DLX_QUEUE","DLX_EXCHANGE","#");

3、优先级队列 
设置一个队列的最大优先级:

Map<String, Object> argss = new HashMap<String, Object>(); 
argss.put("x-max-priority",10);  
// 队列最大优先级
channel.queueDeclare("ORIGIN_QUEUE", false, false, false, argss);

发送消息时指定消息当前的优先级:

AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()        
.priority(5)  
// 消息优先级        
.build();
channel.basicPublish("", "ORIGIN_QUEUE", properties, msg.getBytes());

优先级高的消息可以优先被消费,但是:只有消息堆积(消息的发送速度大于消费者的消费速度)的情况下优先级 才有意义。
4、延迟队列 
RabbitMQ本身不支持延迟队列。可以使用TTL结合DLX的方式来实现消息的延迟投递,即把DLX跟某个队列绑定, 到了指定时间,消息过期后,就会从DLX路由到这个队列,消费者可以从这个队列取走消息。 另一种方式是使用rabbitmq-delayed-message-exchange插件。
当然,将需要发送的信息保存在数据库,使用任务调度系统扫描然后发送也是可以实现的。

5、RPC 
RabbitMQ实现RPC的原理:服务端处理消息后,把响应消息发送到一个响应队列,客户端再从响应队列取到结 果。
其中的问题:Client收到消息后,怎么知道应答消息是回复哪一条消息的?所以必须有一个唯一ID来关联,就是 correlationId。

e0516e4d152482c01138a488eefcf6bc725.jpg
6、服务端流控(Flow Control) 
 

RabbitMQ 会在启动时检测机器的物理内存数值。默认当 MQ 占用 40% 以上内存时,MQ 会主动抛出一个内存警告并阻塞所有连接(Connections)。

可以通过修改 rabbitmq.config 文件来调整内存阈值,默认值是 0.4,如下 所示:   [{rabbit, [{vm_memory_high_watermark, 0.4}]}]. 默认情况,如果剩余磁盘空间在 1GB 以下,RabbitMQ 主动阻塞所有的生产者。这个阈值也是可调的。
注意队列长度只在消息堆积的情况下有意义,而且会删除先入队的消息,不能实现服务端限流。

7、消费端限流 
在AutoACK为false的情况下,如果一定数目的消息(通过基于consumer或者channel设置Qos的值)未被确认 前,不进行消费新的消息。
 

channel.basicQos(2); 
// 如果超过2条消息没有发送ACK,当前消费者不再接受队列消息 
channel.basicConsume(QUEUE_NAME, false, consumer);

UI管理界面的使用 
管理插件提供了更简单的管理方式。 启用管理插件 
Windows启用管理插件 

cd C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.6\sbin 
rabbitmq-plugins.bat enable rabbitmq_management

Linux启用管理插件 

cd /usr/lib/rabbitmq/bin 
./rabbitmq-plugins enable rabbitmq_management

管理界面访问端口:

默认端口是15672,默认用户guest,密码guest。guest用户默认只能在本机访问。 
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

猜你喜欢

转载自blog.csdn.net/qq_38357267/article/details/89397579