RabbitMQ 基础

RabbitMQ 基础

持续更新…


MQ(消息队列)

消息队列的作用

  • 解耦:在项目启动之初是很难预测未来会遇到什么困难的,消息中间件在处理过程中插入了一个隐含的,基于数据的接口层,两边都实现这个接口,这样就允许独立的修改或者扩展两边的处理过程,只要两边遵守相同的接口约束即可。
  • 冗余存储:在某些情况下处理数据的过程中会失败,消息中间件允许把数据持久化直到它们完全被处理。
  • 削峰:在访问量剧增的情况下,而应用仍然需要发挥作用,虽然这样的突发流量并不常见,但是使用消息中间件采用队列的形式可以减少突发访问压力,不会因为突发的超时负荷要求而崩溃。
  • 扩展性:消息中间件解耦了应用的过程,所以提供消息入队和处理的效率是很容易的,只需要增加处理流程就可以了。
  • 可恢复性:当系统一部分组件失效时,不会影响到整个系统。消息中间件降低了进程间的耦合性,当一个处理消息的进程挂掉后,加入消息中间件的消息仍然可以在系统恢复后重新处理。
  • 缓冲:消息中间件通过一个缓冲层来帮助任务最高效率的执行。
  • 异步通信:通过把把消息发送给消息中间件队列,消息中间件并不立即处理它,后续再慢慢处理。
  • 顺序保证:在大多数场景下,处理数据的顺序也很重要,大部分消息中间件支持一定的顺序性。

AMQP简述

AMQP(Advanced Message Queuing Protocol,高级消息队列协议),是个线路层的协议规范,而不是API规范,由于AMQP是一个线路层协议规范,因此它天然就是跨平台的,就像SMTP、HTTP等协议一样,只要开发者按照规范格式发送数据,任何平台都可以通过AMQP进行消息交互。像目前流行的StormMQ、RabbitMQ等都实现了AMQP。

什么是RabbitMQ?
RabbitMQ是一个实现了AMQP的Apache开源消息中间件,使用高性能的Erlang编写。RabbitMQ有可靠性、支持多种协议、高可用、支持消息集群、多语言客户端(如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等),路由(包括点对点和发布/订阅)等特点,在分布式系统中存储转发信息,具有极佳的性能。

RabbitMQ的主要特点

  • 可靠性: RabbitMQ使用一些机制来保证可靠性,如持久化、传输确认及发布确认等。
  • 灵活的路由: 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。
  • 扩展性: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
  • 高可用性: 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队仍然可用。
  • 多种协议: RabbitMQ除了原生支持AMQP协议,还支持STOMP,MQTT等多种消息中间件协议。
  • 多语言客户端: RabbitMQ几乎支持所有常用语言,比如Jav a、Python、Ruby、PHP、C#、JavaScript等。
  • 管理界面: RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
  • 插件机制: RabbitMQ提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。

如下图,每一个服务可对应一个微服务,通过MQ系统可以实现异步机制并对每个服务模块解耦化,而不必像传统的同步机制:

在这里插入图片描述

消息队列角色

  • Provider:消息生产者,生产消息,发送消息。
  • Consumer:消息消费者:接受消息,使用消息。
  • Queue:有序存储消息。类似仓库、中转站。队列可以存储很多的消息,因为它基本上是一个无限制的缓冲区,前提是你的机器有足够的存储空间。多个生产者可以将消息发送到同一个队列中,多个消费者也可以只从同一个队列接收数据。

Docker安装RabbitMQ

sudo docker  run  -itd /
-e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=root /
 -p 15555:15672 -p 5555:5672 rabbitmq:management

打开相应网址:

在这里插入图片描述

在这里插入图片描述
Linux安装RabbitMQ

首先安装Erlang,然后安装rabbitMQ

sudo apt-get install erlang-nox
sudo apt-get install rabbitmq-server

在这里插入图片描述

sudo rabbitmqctl add_user root root # 添加用户root,密码为root
sudo rabbitmqctl set_user_tags root administrator # 赋予administrator权限
sudo rabbitmqctl set_permissions -p / root '.*' '.*' '.*' # 赋予virtual host中所有资源的配置、写、读权限以便管理其中的资源

启用RabbitMQ图形化管理界面插件

sudo  rabbitmq-plugins enable rabbitmq_management

此时同一局域网内主机均可以访问该管理网站…

简单案例

通过SpringBoot使用RabbitMQ

在这里插入图片描述

主要依赖:

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

添加application.yml文件:

server:
  port: 11111
spring:
  rabbitmq:
    host: 192.168.50.248 # rabbitMQ主机名
    port: 5672 # rabbitMQ端口
    username: root # rabbitMQ用户名
    password: root # rabbitMQ密码

# 自定义队列名称
rabbitMQ:
  queue:
    name: Queue-1

添加队列:

package cn.wu.config;

import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueueConfig {
    @Value("${rabbitMQ.queue.name}")
    private String queueName;

    @Bean // 注入IoC管理
    public Queue createQueue(){
        return new Queue(queueName);
    }
}

添加消息消费者:

package cn.wu.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
@Slf4j
public class Receiver {

    // 接收消息并处理
    @RabbitListener(queues = {"${rabbitMQ.queue.name}"})
    public void handler(String msg){
        // 处理消息
        log.info("接收到的消息为: "+msg);
    }
}

添加消息生产者:

package cn.wu.provider;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("senderBean")
public class Sender {
    private AmqpTemplate amqpTemplate;
    @Autowired
    public void setAmqpTemplate(AmqpTemplate amqpTemplate) {
        this.amqpTemplate = amqpTemplate;
    }

    @Value("${rabbitMQ.queue.name}")
    private String queueName;

    public void sendMessage(String msg){
        // 向名称为queueName的队列转化并发送消息msg
        amqpTemplate.convertAndSend(this.queueName,msg);
    }
}

新建控制层:

package cn.wu.controller;

import cn.wu.provider.Sender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    Sender sender;
    @Autowired
    @Qualifier("senderBean")
    public void setSender(Sender sender) {
        this.sender = sender;
    }

    @GetMapping("/test/{msg}")
    public String test(@PathVariable("msg") String msg){
        sender.sendMessage(msg);
        return "";
    }
}

此时启动后,可以得到连接信息如下:

在这里插入图片描述

访问相应URL(http://localhost:11111/test/Hello World):

在这里插入图片描述

RabbitMQ原理图

在这里插入图片描述

交换策略

  • Direct(发布与订阅完全匹配)
  • Fanout(广播)
  • Topic(主题,规则匹配)
Direct方式(注解方式)

修改yml文件:

# 自定义队列名称
rabbitMQ:
  queueName-1: queue-1 # 队列1的名称
  queueName-2: queue-2 # 队列2的名称
  exchangeName: exchangeName # 交换器名称
  routingKeyName-1: routingKeyName1 # 路由键1的名称 用于绑定队列1 发送者根据该路由键来选择队列
  routingKeyName-2: routingKeyName2 # 路由键2的名称 用于绑定队列2 发送者根据该路由键来选择队列

修改消费者(添加绑定配置… ):

package cn.wu.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;


@Component
@RabbitListener(bindings = {@QueueBinding(
        value = @Queue(value = "${rabbitMQ.queueName-1}", durable = "true"), // 设置绑定的队列名称,持久存储
        exchange = @Exchange(name = "${rabbitMQ.exchangeName}", // 交换器的名称
                type = ExchangeTypes.DIRECT), // 交换器执行的策略
        key = "${rabbitMQ.routingKeyName-1}" // 路由键,绑定交换器和队列
)}
)
@Slf4j
public class ReceiverOne {

    // 接收消息并处理
     @RabbitHandler // 配合@RabbitListener使用,根据接受的参数类型进入具体的方法
    public void handler(String msg){
        // 处理业务流程
        // 事件处理
         log.info("消息消费者I 接收到的消息为: "+msg);
    }
}

package cn.wu.consumer;


import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@RabbitListener(bindings = {@QueueBinding(
        value = @Queue(value = "${rabbitMQ.queueName-2}", durable = "true"), // 设置绑定的队列名称,以及作为可删除的临时队列
        exchange = @Exchange(name = "${rabbitMQ.exchangeName}", // 交换器的名称
                type = ExchangeTypes.DIRECT), // 交换器执行的策略
        key = "${rabbitMQ.routingKeyName-2}" // 路由键,绑定交换器和队列
    )}
)
public class ReceiverTwo {

    // 接收消息并处理
    @RabbitHandler
    public void handler(String msg){
        // 处理业务流程
        log.info("消息消费者II 接收到的消息为: "+msg);
    }
}

修改生产者:

package cn.wu.provider;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("senderBean")
public class Sender {
    private AmqpTemplate amqpTemplate;
    @Autowired
    public void setAmqpTemplate(AmqpTemplate amqpTemplate) {
        this.amqpTemplate = amqpTemplate;
    }

    @Value("${rabbitMQ.exchangeName}")
    private String exchangeName;
    @Value("${rabbitMQ.routingKeyName-1}")
    private String routingKeyName1;
    @Value("${rabbitMQ.routingKeyName-2}")
    private String routingKeyName2;

    public void sendMessage_1(String msg){
        // 向名路由键routingKeyName1绑定的队列转化并发送消息msg
        // 第一个参数为交换器名称,第二个参数为路由键名称,第三个为发送的参数
        amqpTemplate.convertAndSend(this.exchangeName,this.routingKeyName1,msg);
    }
    public void sendMessage_2(String msg){
        // 向名路由键routingKeyName2绑定的队列转化并发送消息msg
        // 第一个参数为交换器名称,第二个参数为路由键名称,第三个为发送的参数
        amqpTemplate.convertAndSend(this.exchangeName,this.routingKeyName2,msg);
    }
}

控制层:

package cn.wu.controller;

import cn.wu.provider.Sender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    Sender sender;
    @Autowired
    @Qualifier("senderBean")
    public void setSender(Sender sender) {
        this.sender = sender;
    }

    @GetMapping("/test/{receive}/{msg}")
    public String test(@PathVariable("receive") Integer receive ,@PathVariable("msg") String msg){
        if(receive == 1){
            sender.sendMessage_1(msg);
        }else if(receive == 2){
            sender.sendMessage_2(msg);
        }
        return "";
    }
}

结果(分别访问 http://localhost:11111/test/1/你好,世界!,分别访问http://localhost:11111/test/2/你好,世界!):

在这里插入图片描述

大致结构:

在这里插入图片描述

Fanout方式

以前面代码作为基础

不需要改变application.yml文件

修改消息消费者类@RabbitListener注解:

@RabbitListener(bindings = {@QueueBinding(
        value = @Queue(value = "${rabbitMQ.queueName-1}", durable = "true"), // 设置绑定的队列名称,持久存储
        exchange = @Exchange(name = "${rabbitMQ.exchangeName}", // 交换器的名称
                type = ExchangeTypes.FANOUT) // 交换器执行的策略: 广播形式
        // 此时是不需要绑定routing key的…
    )}
)

修改消息生产者类:

public void sendMessage_1(String msg){
        // 向名路由键routingKeyName1绑定的队列转化并发送消息msg
        // 第一个参数为交换器名称,第二个参数为路由键名称,第三个为发送的参数
        // 由于是广播的形式,所以路由键可以不需要再指定
        amqpTemplate.convertAndSend(this.exchangeName,"",msg);

    }
    public void sendMessage_2(String msg){
        // 向名路由键routingKeyName2绑定的队列转化并发送消息msg
        // 第一个参数为交换器名称,第二个参数为路由键名称,第三个为发送的参数
        // 由于是广播的形式,所以路由键可以不需要再指定
        amqpTemplate.convertAndSend(this.exchangeName,"",msg);
    }

不管访问哪一个URL,,两个消息接收者都会接收到:

在这里插入图片描述

Topic方式
  • * 代表只匹配单个字符
  • # 代表匹配至少一个字符
  • 路由键以符号 . 来连接

基本结构:
在这里插入图片描述

修改applcation.yml:

rabbitMQ:
  queueName-1: queue-1 # 队列1的名称
  queueName-2: queue-2 # 队列2的名称
  exchangeName: exchangeName # 交换器名称
  routingKeyName-1: route.students.1 # 路由键1的名称 用于绑定队列1 发送者根据该路由键来选择队列
  routingKeyName-2: route.books.a # 路由键2的名称 用于绑定队列2 发送者根据该路由键来选择队列

修改消息生产者:

package cn.wu.provider;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("senderBean")
public class Sender {
    private AmqpTemplate amqpTemplate;
    @Autowired
    public void setAmqpTemplate(AmqpTemplate amqpTemplate) {
        this.amqpTemplate = amqpTemplate;
    }

    @Value("${rabbitMQ.exchangeName}")
    private String exchangeName;
    @Value("${rabbitMQ.routingKeyName-1}")
    private String routingKeyName1;
    @Value("${rabbitMQ.routingKeyName-2}")
    private String routingKeyName2;

    public void sendMessage_1(String msg){
        // 向名路由键routingKeyName1绑定的队列转化并发送消息msg
        // 第一个参数为交换器名称,第二个参数为路由键名称,第三个为发送的参数
        // 由于是广播的形式,所以路由键可以不需要再指定
        amqpTemplate.convertAndSend(this.exchangeName,this.routingKeyName1,msg);

    }
    public void sendMessage_2(String msg){
        // 向名路由键routingKeyName2绑定的队列转化并发送消息msg
        // 第一个参数为交换器名称,第二个参数为路由键名称,第三个为发送的参数
        // 由于是广播的形式,所以路由键可以不需要再指定
        amqpTemplate.convertAndSend(this.exchangeName,this.routingKeyName2,msg);
    }
}

修改消息消费者:

package cn.wu.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;


@Component
@RabbitListener(bindings = {@QueueBinding(
        value = @Queue(value = "${rabbitMQ.queueName-1}", autoDelete = "true"), // 设置绑定的队列名称,临时连接
        exchange = @Exchange(name = "${rabbitMQ.exchangeName}", // 交换器的名称
                type = ExchangeTypes.TOPIC), // 交换器执行的策略: 广播形式
        key = "#.1" // 匹配所有以-1结尾的所有路由键
    )}
)
@Slf4j
public class ReceiverOne {
    // 接收消息并处理
     @RabbitHandler // 配合@RabbitListener使用,根据接受的参数类型进入具体的方法
    public void handler(String msg){
        // 处理业务流程
        // 事件处理
         log.info("消息消费者I 接收到的消息为: "+msg);
    }
}
package cn.wu.consumer;


import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@RabbitListener(bindings = {@QueueBinding(
        value = @Queue(name = "${rabbitMQ.queueName-2}", autoDelete = "true"), // 设置绑定的队列名称,以及作为可删除的临时队列
        exchange = @Exchange(name = "${rabbitMQ.exchangeName}", // 交换器的名称
                type = ExchangeTypes.TOPIC), // 交换器执行的策略
        key = "#.a" // 匹配所有以.a结尾的所有路由键
    )}
)
public class ReceiverTwo {

    // 接收消息并处理
    @RabbitHandler
    public void handler(String msg){
        // 处理业务流程
        log.info("消息消费者II 接收到的消息为: "+msg);
    }
}

package cn.wu.consumer;


import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

@Component
@RabbitListener(bindings = {@QueueBinding(
        value = @Queue(value = "${rabbitMQ.queueName-1}", autoDelete = "true"), // 设置绑定的队列名称,临时连接
        exchange = @Exchange(name = "${rabbitMQ.exchangeName}", // 交换器的名称
                type = ExchangeTypes.TOPIC), // 交换器执行的策略: 广播形式
        key = "route.#" // 匹配所有以route.开头的路由键
         )}
)
@Slf4j
public class ReceiverThree {
    AtomicInteger  count = new AtomicInteger(0);
    @RabbitHandler
    public void handler(String msg){
        count.incrementAndGet();
        log.info("消息接收者III第"+count.get()+"次接收到的消息为: "+msg);
    }
}

修改控制层:

package cn.wu.controller;

import cn.wu.provider.Sender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    Sender sender;
    @Autowired
    @Qualifier("senderBean")
    public void setSender(Sender sender) {
        this.sender = sender;
    }

    @GetMapping("/test/{receive}/{msg}")
    public String test(@PathVariable("receive") Integer receive ,@PathVariable("msg") String msg){
        if(receive == 1){
            sender.sendMessage_1(msg);
        }else if(receive == 2){
            sender.sendMessage_2(msg);
        }
        return "";
    }
}

多次点击相同URL,多次生产消息,此时,由于第一个消息消费者和第三个消息消费者共用一个队列(第三个消息消费者也和第二个消费者共用同一个队列),默认轮流获取队列元素,因此最终结果为:

在这里插入图片描述



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值