如果项目中涉及消息的推送、发布/订阅、异步处理、工作队列等,可以通过消息中间件实现。rabbitmq作为一个消息系统的媒介,可以为你的应用提供一个通用的消息发送和接受平台,并保证消息在传输过程中的安全。消息队列三剑客中,rabbitmq适合处理吞吐量通常较低,适合中小规模的应用场景。通常能够处理万级到十万级的消息量级。它主要侧重于消息的可靠性传递和消息的持久化,对于高吞吐量的需求可能需要进行优化和调整。
AMQP协议
AMQP(高级消息队列协议)是应用层协议的一个开发标准,为面向消息的中间件设计。经典模型如下图所示:
消息(message)被发布者(publisher)发送给交换机(exchange),交换机将收到的消息根据路由规则分发给绑定的队列(queue),最后AMQP代理将此消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
发布者发布消息的时候可以给消息指定各种消息属性。考虑到网络的不可靠性,接收消息的应用有可能在处理消息的时候失败,因此,AMQP模块中包含了一个消息确认的概念(当一个消息从队列中投递给消费者后(consumer),消费者会通知一下消息代理(broker)。当消息代理被启用的时候,消息代理不会完全将消息从队列中删除,直到收到来自消费者的确认回执。
当一个消息无法被成功路由时,消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。
交换机
由于AMQP是一个可编程协议,应用程序可以声明AMQP实体,定义需要的路由方案或者删除不再需要的AMQP实体。交换机是用来发送消息的AMQP实体。交换机拿到一个消息之后将它路由给一个或零个队列。其状态分为两种:持久、暂存。持久化的交换机会在消息代理(broker)重启后依旧存在,而暂存的交换机则不会(它们需要在代理再次上线后重新被声明)。交换机类型分为以下四种:
1.直连交换机(Direct exchange)
直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的。直连交换机用来处理消息的单播路由(也可以处理多播路由)。工作流程:
(1)将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)
(2)当一个携带着路由键为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。
直连交换机经常用来循环分发任务给多个工作者(workers)。消息的负载均衡是发生在消费者(consumer)之间的,而不是队列(queue)之间。
2.扇形交换机(Fanout exchange)
扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列,而不理会绑定的路由键。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的拷贝分别发送给这所有的N个队列。扇型用来交换机处理消息的广播路由(broadcast routing)。其应用案例有:大规模多用户在线(MMO)游戏可以使用它来处理排行榜更新等全局事件、体育新闻网站可以用它来近乎实时地将比分更新分发给移动客户端、在群聊的时候,它被用来分发消息给参与群聊的用户。如下图所示:
、
3.主题交换机(Topic exchange)
主题交换机(topic exchanges)通过对消息的路由键和队列到交换机的绑定模式之间的匹配,将消息路由给一个或多个队列。主题交换机经常用来实现各种分发/订阅模式及其变种。主题交换机通常用来实现消息的多播路由(multicast routing)。当一个问题涉及到那些想要有针对性的选择需要接收消息的 多消费者/多应用的时候,主题交换机都可以被列入考虑范围。例如:涉及到分类或者标签的新闻更新、由多个工作者完成的后台任务,每个工作者负责处理某些特定的任务。
4.头交换机(Headers exchange)
有时消息的路由操作会涉及到多个属性,此时使用消息头就比用路由键更容易表达,头交换机就是为此而生的。头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。
头交换机可以视为直连交换机的另一种表现形式。头交换机能够像直连交换机一样工作,不同之处在于头交换机的路由规则是建立在头属性值之上,而不是路由键。路由键必须是一个字符串,而头属性值则没有这个约束,它们甚至可以是整数或者哈希值(字典)等。
队列
AMQP中的队列(queue)跟其他消息队列或任务队列中的队列是很相似的:它们存储着即将被应用消费掉的消息。队列在声明(declare)后才能被使用。如果一个队列尚不存在,声明一个队列会创建它。如果声明的队列已经存在,并且属性完全相同,那么此次声明不会对原有队列产生任何影响。如果声明中的属性与已存在队列的属性有差异,那么一个错误代码为406的通道级异常就会被抛出。
持久化队列(Durable queues)会被存储在磁盘上,当消息代理(broker)重启的时候,它依旧存在。没有被持久化的队列称作暂存队列(Transient queues)。并不是所有的场景和案例都需要将队列持久化。持久化的队列并不会使得路由到它的消息也具有持久性。倘若消息代理挂掉了,重新启动,那么在重启的过程中持久化队列会被重新声明,总之,只有经过持久化的消息才能被重新恢复。
绑定
绑定(Binding)是交换机(exchange)将消息(message)路由给队列(queue)所需遵循的规则。如果要指示交换机“E”将消息路由给队列“Q”,那么“Q”就需要与“E”进行绑定。绑定操作需要定义一个可选的路由键(routing key)属性给某些类型的交换机。路由键的意义在于从发送给交换机的众多消息中选择出某些消息,将其路由给绑定的队列。如果AMQP的消息无法路由到队列(例如,发送到的交换机没有绑定队列),消息会被就地销毁或者返还给发布者。如何处理取决于发布者设置的消息属性。
消费者
一个队列可以注册多个消费者,也可以注册一个独享的消费者(当独享消费者存在时,其他消费者即被排除在外)。每个消费者(订阅者)都有一个叫做消费者标签的标识符。它可以被用来退订消息。消费者标签实际上是一个字符串。
消息确认
消费者应用难免会出现一些特殊场景造成消息丢失,那么AMQP何时删除消息十分值得考虑。AMQP提供了两种策略:
(1)自动确认模式:当消息代理(broker)将消息发送给应用后立即删除。
(2)显式确认模式:待应用(application)发送一个确认回执(acknowledgement)后再删除消息。
在显式模式下,由消费者应用来选择什么时候发送确认回执。应用可以在收到消息后立即发送,或将未处理的消息存储后发送,或等到消息被处理完毕后再发送确认回执(例如,成功获取一个网页内容并将其存储之后)。
拒绝消息
当一个消费者接收到某条消息后,处理过程有可能成功,有可能失败。应用可以向消息代理表明,本条消息由于“拒绝消息”的原因处理失败了(或者未能在此时完成)。当拒绝某条消息时,应用可以告诉消息代理如何处理这条消息——销毁它或者重新放入队列。当此队列只有一个消费者时,请确认不要由于拒绝消息并且选择了重新放入队列的行为而引起消息在同一个消费者身上无限循环的情况发生。
预取消息
在多个消费者共享一个队列的案例中,明确指定在收到下一个确认回执前每个消费者一次可以接受多少条消息是非常有用的。这可以在试图批量发布消息的时候起到简单的负载均衡和提高消息吞吐量的作用。
连接
AMQP连接通常是长连接。AMQP是一个使用TCP提供可靠投递的应用层协议。AMQP使用认证机制并且提供TLS(SSL)保护。当一个应用不再需要连接到AMQP代理的时候,需要优雅的释放掉AMQP连接,而不是直接将TCP连接关闭。
Springboot整合Rabbtimq
Springboot为了方便开发者使用Rabbitmq推出了spring-boot-starter-amqp依赖。
第一步:服务器上部署Rabbitmq
我的项目中,将rabbitmq部署到服务器上,遇到的坑还是很多,需要安装socat、erlang、rabbitmq,推荐一位博主的博客,具体安装步骤可以参与他的这篇博客: RabbitMQ保姆级安装:Linux Centos8系统
注:如果有用华为云或者阿里云服务器的选手,记得更改安全组端口。
第二步:导入rabbitmq依赖
第三步:application.yml
如果部署在服务器上,把host换为你的服务器ip即可。
第四步:创建消费者、生产者
创建一个Producer模块作为生产者
RabbitMQConfig.java
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "My_exchange";
public static final String QUEUE = "My_queue";
@Bean
public Exchange orderExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
@Bean
public Queue orderQueue(){
return QueueBuilder.durable(QUEUE).build();
}
/**
* 交换机和队列关系绑定 指定路由key
*/
@Bean
public Binding orderBinding(Queue queue, Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("order.#").noargs();
}
}
RabbitTest
@SpringBootTest
@RunWith(SpringRunner.class)
public class RabbitTest {
@Autowired(required = false)
private RabbitTemplate template;
@Test
public void test() throws JSONException {
for(int i=0;i<5;i++){
String s=String.valueOf(i);
template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"order.new",s);
}
}
}
创建一个Listener模块作为消费者
OrderMQListener
@Component
public class OrderMQListener {
@RabbitListener(queues = "order_queue")
public void messageHandler(Message message){
System.out.println("xxxx.xxxx.xxxx收到的消息内容为:\n" + JSON.toJSONString(message));
Object parse = JSONObject.parse(message.getBody());
System.out.println("消息内容: " + JSON.toJSONString(parse));
}
}
总结
以上就是rabbitmq简单示范,推荐几篇适合入门学习的博客:
RabbitMQ实战详解
RabbitMQ 中文文档