消息队列(MQ)
消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。
MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。
两者间的区别和联系:
- 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
安装以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种消息模型
:生产者–》生产者,一个发送消息的用户应用程序
:消费者–》消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序
:队列–》rabbitmq内部类似于邮箱的一个概念。虽然消息流经rabbitmq和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。
:交换机–》交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。(三种)
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key 的队列
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
- 基本消息模型
- simple简单模式
就是生产者发送消息到队列,然后消费者监听着队列,一有消息立马接收,一对一关系
- work消息模型
- work工作模式(资源的竞争)
和简单模式类似,区别就是一个队列有了俩个甚至多个消费者,队列中任务将在他们之间共享,但是一个消息只能被一个消费者获取。
- 订阅消息模型(3种)
这是一个大类,分为三种,和work的区别就是有了交换机,然后每个消费者会有自己单独的队列,因交换机有三种,故而分三种订阅模式
- publish/subscribe发布订阅(共享资源)
- 可以有多个消费者
- 每个消费者有自己的queue(队列)
- 每个队列都要绑定到Exchange(交换机)
- 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
- 交换机把消息发送给绑定过的所有队列
- 队列的消费者都能拿到消息。实现一条消息被多个消费者消费
- routing路由模式
- 有选择性的接收消息
在订阅模式中,生产者发布消息,所有消费者都可以获取所有消息。
在路由模式中,我们将添加一个功能 - 我们将只能订阅一部分消息。 例如,我们只能将重要的错误消息引导到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。
但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。
- topic 主题模式(路由模式的一种)
Topic
类型的Exchange
与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!Routingkey
一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如:item.insert
通配符规则:
`#`:匹配一个或多个词
`*`:匹配不多不少恰好1个词
举例:
`audit.#`:能够匹配`audit.irs.corporate` 或者 `audit.irs`
`audit.*`:只能匹配`audit.irs`
- 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.交换机持久化
2.队列持久化
3.消息持久化
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 #登录客户端中有其设置,目的是实现用户下几个分支这种作用
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,非常方便的发送消息,其发送方法:
红框圈起来的是比较常用的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);
}
}
运行后查看日志: