消息队列(MQ)

消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。

MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_02

两者间的区别和联系:

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
  • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
  • JMS规定了两种消息模型;而AMQP的消息模型更加丰富

常见mq产品

  • ActiveMQ:基于JMS
  • RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
  • RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
  • Kafka:分布式消息系统,高吞吐量

下载安装

RabbitMQ是基于AMQP的一款消息管理系统

官网: http://www.rabbitmq.com/

官方教程:http://www.rabbitmq.com/getstarted.html

官网下载地址:http://www.rabbitmq.com/download.html

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_学习_03

安装以linux为例

  • RabbitMQ是Erlang语言写出来的,先下载安装Erlang环境
//以上只是例子,自己下载时注意版本
wget --content-disposition https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-22.3.4.12-1.el7.x86_64.rpm/download.rpm

yum localinstall erlang-22.3.4.12-1.el7.x86_64.rpm
  • 安装rabbitmq
wget --content-disposition https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.8.13-1.el7.noarch.rpm/download.rpm

//当你下载完成后,你需要运行下面的命令来将 Key 导入
rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc

yum localinstall rabbitmq-server-3.8.13-1.el7.noarch.rpm

//启动 rabbitmq 服务器,执行命令
systemctl start rabbitmq-server

//设置开机自动启动
systemctl enable rabbitmq-server

//默认情况下,是没有安装web端的客户端插件,需要安装才可以生效
rabbitmq-plugins enable rabbitmq_management

//安装完毕以后,重启服务即可
systemctl restart rabbitmq-server
  • 安装完rabbitmq后,通过ip可进行客户端的访问,端口为15672
  • 安装完rabbitmq后,rabbitmq有一个默认账号密码是: guest,登录成功后如下图
  • 当然你也可以手动添加用户和密码
rabbitmqctl add_user admin admin        //新增用户
rabbitmqctl set_user_tags admin administrator     //授权
//当然添加用户和授权也能在客户端手动进行
  • windows下就直接去官网下载安装包后安装就可以了,这里略过了,记得安装前也需要安装Erlang哦

六种消息模型

rabbitmq提供了6种消息模型

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_04

:生产者–》生产者,一个发送消息的用户应用程序

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_spring_05

:消费者–》消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_spring_06

:队列–》rabbitmq内部类似于邮箱的一个概念。虽然消息流经rabbitmq和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_07

:交换机–》交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。(三种)

Fanout:广播,将消息交给所有绑定到交换机的队列

Direct:定向,把消息交给符合指定routing key 的队列

Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

  • 基本消息模型
  • simple简单模式

就是生产者发送消息到队列,然后消费者监听着队列,一有消息立马接收,一对一关系

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_08

  • work消息模型
  • work工作模式(资源的竞争)
    和简单模式类似,区别就是一个队列有了俩个甚至多个消费者,队列中任务将在他们之间共享,但是一个消息只能被一个消费者获取

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_spring_09

  • 订阅消息模型(3种)
    这是一个大类,分为三种,和work的区别就是有了交换机,然后每个消费者会有自己单独的队列,因交换机有三种,故而分三种订阅模式
  • publish/subscribe发布订阅(共享资源)
  • 可以有多个消费者
  • 每个消费者有自己的queue(队列)
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
  • 交换机把消息发送给绑定过的所有队列
  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_10

  • routing路由模式
  • 有选择性的接收消息
    在订阅模式中,生产者发布消息,所有消费者都可以获取所有消息。
    在路由模式中,我们将添加一个功能 - 我们将只能订阅一部分消息。 例如,我们只能将重要的错误消息引导到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。
    但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
    在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
    消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。
  • topic 主题模式(路由模式的一种)
  • Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
    Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert通配符规则:
`#`:匹配一个或多个词

`*`:匹配不多不少恰好1个词

举例:

`audit.#`:能够匹配`audit.irs.corporate` 或者 `audit.irs`

`audit.*`:只能匹配`audit.irs`

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_spring_11

  • RPC
    略,以后会有rpc的笔记

概念补充

避免消息堆积

1)采用workqueue,多个消费者监听同一队列。

2)接收到消息以后,而是通过线程池,异步消费。

work消息模型的负载均衡策略

默认平均分配,正确的做法应该是消费越快的人,消费的越多,修改方法:

我们可以使用basicQos方法和prefetchCount = 1设置。 这告诉RabbitMQ一次不要向工作人员发送多于一条消息。 或者换句话说,不要向工作人员发送新消息,直到它处理并确认了前一个消息。 相反,它会将其分派给不是仍然忙碌的下一个工作人员。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IkTJYj3t-1665638221923)(C:/Users/gfk/Desktop/xue/乐优/assets/1532765689904.png)]

消息确认机制(ACK)

消息一旦被消费者接收,队列中的消息就会被删除。,RabbitMQ怎么知道消息被接收了呢?

如果消费者领取消息后,还没执行操作就挂掉了呢?或者抛出了异常?消息消费失败,但是RabbitMQ无从得知,这样消息就丢失了!

因此,RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:

  • 自动ACK:消息一旦被接收,消费者自动发送ACK
  • 手动ACK:消息接收后,不会发送ACK,需要手动调用

如何选择这需要看消息的重要性:

  • 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
  • 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。
持久化问题

如何避免消息丢失?

1) 消费者的ACK机制。可以防止消费者丢失消息。

2) 但是,如果在消费者消费之前,MQ就宕机了,消息就没了。

是可以将消息进行持久化呢?

要将消息持久化,前提是:队列、Exchange都持久化

1.交换机持久化

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_12

2.队列持久化

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_spring_13

3.消息持久化

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_持久化_14

Spring AMQP

1.依赖和配置
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
  rabbitmq:
    host: 127.0.0.1
    username: gfk
    password: gfk
    virtual-host: /gfk    #登录客户端中有其设置,目的是实现用户下几个分支这种作用

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_spring_15

2.设置监听者
  • 注意,代码中交换机必须得自己创建,队列等系统运行后没有的话会自动创建
@Component
public class Listener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "spring.test.queue", durable = "true"),
            exchange = @Exchange(
                    value = "spring.test.exchange",
                    ignoreDeclarationExceptions = "true",
                    type = ExchangeTypes.TOPIC
            ),
            key = {"#.#"}))
    public void listen(String msg){
        System.out.println("接收到消息:" + msg);
    }
}
  • @Componet:类上的注解,注册到Spring容器
  • @RabbitListener:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:
  • bindings:指定绑定关系,可以有多个。值是@QueueBinding的数组。@QueueBinding包含下面属性:
  • value:这个消费者关联的队列。值是@Queue,代表一个队列
  • exchange:队列所绑定的交换机,值是@Exchange类型
  • key:队列和交换机绑定的RoutingKey

类似listen这样的方法在一个类中可以写多个,就代表多个消费者。

3.AmqpTemplate

Spring为AMQP提供了统一的消息处理模板:AmqpTemplate,非常方便的发送消息,其发送方法:

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_rabbitmq_16

红框圈起来的是比较常用的3个方法,分别是:

  • 指定交换机、RoutingKey和消息体
  • 指定消息
  • 指定RoutingKey和消息,会向默认的交换机发送消息
4.测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MqDemo {

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Test
    public void testSend() throws InterruptedException {
        String msg = "hello, Spring boot amqp";
        this.amqpTemplate.convertAndSend("spring.test.exchange","a.b", msg);
        // 等待10秒后再结束
        Thread.sleep(10000);
    }
}

运行后查看日志:

Java rabbitmq消息订阅配置多个vhost rabbitmq与其他消息队列_持久化_17