Java中间件学习之RabbitMQ
什么是MQ
消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。
MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。
AMQP:
AMQP是一个提供统一消息服务的应用层标准协议,是应用层协议的一个开放标准,为面向消息的中间件设计。它是一个二进制级别的协议,直接定义网络交换的数据格式,而不从API层进行限定。AMQP支持跨语言、跨平台,任何遵守此数据格式的工具都能与其他兼容工具进行互操作。
JMS:
JMS是Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间或分布式系统中发送消息,进行异步通信。它定义了统一的接口来对消息操作进行统一,但限定了必须使用Java语言。
两者间的区别和联系:
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
- JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模型;而AMQP的消息模型更加丰富
补充:
- ActiveMQ:基于JMS
- RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
- RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
- Kafka:分布式消息系统,高吞吐量
MQ的好处
- 流量消锋
- 应用解耦
- 异步处理
MQ的对比和选择
RabbitMQ的组成部分
- 生产者Producer:产生数据发送消息的程序是生产者。
- 交换机Exchange:交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定。
- 队列Queue:队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式。
- 消费者Consumer:消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。
MQ的工作模式
- Hello Wold 简单模式:简单的“生产者-消息队列-消费者”的工作模式。
- Work queues工作 队列模式:一个消息队列对应多个消费者,由多个消费者竞争消费信息。
- Publish/Subscribe发布订阅模式:引入交换机Exchange,类型为Fanout,特点是将消息分发到绑定的多个消息队列中。
- Routing 路由模式:这种模式下的交换机类型为Direct,特点是可以根据指定的Routing Key去匹配BindingKey相同的消息队列。
- Topics 主题模式:这种工作模式的交换机类型为Topic,特点是RoutingKey和BindingKey中引入通配符,然后两者通过模糊匹配相关联。
- Publisher Confirms 发布确认模式(RPC):这种工作模式实际应用不广泛暂不学习。
补充:生产者(Producter)和交换机(Exchange)之间通过RoutingKey相关联,交换机(Exchange)和消息队列(Queue)之间通过BindingKey来绑定。
RabbitMQ如何避免消息丢失
生产者:在生产者端开启comfirm 确认模式,生产消息发到RabbitMQ后,会给你回传一个ack消息,告诉你说这个消息成功写入了。
消息队列:消息队列设置为持久化队列,将内存数据持久化到磁盘中。
消费者:消费者把自动ACK关闭,设置为手动ACK,等消息被消费以后再返回ACK信息。
RabbitMQ如何保证顺序性消费
方案一:拆分多个Queue,每个Queue对应一个Consumer。(性能慢,需要增加Consumer的数量,风险小一点)
方案二:Producter和Consumer根据实际业务制定好规则,然后Consumer增加本地缓存或者队列,将消息按规则拍好顺序后消费,如果觉得效率慢可以引入多线程去处理。(不需要增加Consumer,性能快,如果用多线程风险会高一些)
RabbitMQ如何幂等性消费信息(不重复消费信息)
每条消息绑定一个不重复的Id作为标识,消费方在消费的时候通过Id去判断是否已经被消费。例如消费方可以把信息先存到数据库中,把Id作为主键,如果主键冲突说明此条消息已被消费过,然后再根据实际业务场景选择由插入数据改为更新,或者直接舍弃掉。