消息中间件
消息中间件(Message Queue Middleware,简称MQ):利用高效的可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传和消息排队模型,它可以在分布式环境下扩展进程间的通信。
消息中间件的作用
解耦
只需要项目之间都遵循同样的接口约束,可以独立的修改和扩展消息中间件”两头“的业务,”两头“处理过程中都要实现这一接口约束。
冗余
在很多情况下,高并发的情况下”直接系统“处理不过来前端的请求,那么就需要一个”容器“先把数据存储起来”冗余“起来,然后直接系统再一个一个去”容器“取数据做业务,这个容器就是消息队列,可以先把消息放到队列里面,再慢慢消费处理,消息队列会保存数据一直到你使用完毕为止,不会造成数据的丢失。
削峰
在访问量突然增加的时候,要系统支持最高峰值去投入资源是很不划算的,但是最高峰的习题仍需要正常运行,就是发生峰值的次数少,峰值高,但需要系统正常运行,又不能以最高峰去预估资源,这个时候就用消息队列削峰,支持突发的访问压力,不会因为突发的超负荷请求而系统崩溃。
可恢复性
当系统一部分组件失效时,不会影响到整个系统 消息中间件降低了进程间的耦合度,即使个别处理消息的进程挂掉,加入消息中间件中的消息仍然可以在系统恢复后进行处理。
顺序性
在很多情况下,数据处理需要保证数据处理的先后顺序,把先后顺序的业务数据放到一个队列去处理,在一定程度上课一保存数据的顺序性。
异步处理
在很多情况,消息不需要及时立即处理,消息中间件提供了异步处理机制,允许消息把消息放入队列异步处理。
RabbitMQ
RabbitMQ简介
RabbitMQ 是采用 Erlang 语言实现 AMQP (Advanced Message Queuing Protocol ,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。RabbitMQ是由RabbitMQ Technologies Ltd开发并且提供商业支持的,Rabbit名字是因为兔子行动非常迅速且繁殖起来非常疯狂,RabbitMQ的开创者以此命名。
RabbitMQ特点
- 可靠性:RabbitMQ的持久性、传输和发布的确认等。
- 路由灵活:在消息进入队列之前,通过交换机路由消息,再将消息放入交换机绑定的队列,针对更复杂的路由功能,可以将多个交换器绑定在一起,可以通过插件机制来实现自己的交换器。
- 扩展性:多个RabbitMQ节点可以组成集群,集群可以根据实际业务扩展。
- 高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题时队列仍然可用。
- 多种协议:RabbitMQ除支持AMQP协议,还支持还支持 STOMP MQ 等多种消息中间件协议。
- 管理界面:RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
RabbitMQ模型
RabbitMQ整体上是一个生产者,消费者模型,主要负责是接收消息,路由消息到队列,存储消息,转发和消费消息,可以把消息传递想象成快递运输的过程,寄邮件(RabbitMQ发起端)、邮局送邮件(RabbitMQ路由和放入队列)、收邮件(RabbitMQ消费)。从计算机层面,RabbitMQ更像一种交换机模型。
- 生产者(Producer):就是投递消息的一方,生产者投递消息发布到RabbitMQ中,比如投递消息为一个JSON字符串,消息通过生产者-到交换机-到队列-然后到这个队列的消费者消费消息。
- 消费者(Consumer):就是接收消息的一方,消费者连接到对应的RabbitMQ服务器,并订阅到对应的队列上,消费生产者投递的消息。
- 交换机(Exchange):可以理解为在生产者和队列之间的通道服务,一个交换机可以绑定一个队列或者绑定多个队列。
- 队列(Queue):RabbitMQ存储消息的内部对象,有先进先出的特点,同时一个队列可以多个消费者订阅,消息会被轮询(Round-Robin)到不同的消费者,而不是每个消费者都收到所有的消息处理。
- 路由键(RountingKey):生产者将消息发送给交换机的时候,一般会指定一个RountingKey,用来指定这个消息的路由规则,而这个RountingKey需要和BindingKey联合使用。
- 绑定(Binding):理解为一个BindingKey和RountingKey绑定在一起,RountingKey可以理解生产者和交换机路由规则的key,BindingKey是交换机和队列通过BindingKey绑定在一起的key,这样消息通过RountingKey和BindingKey匹配在一起,这样RabbitMQ就知道如何将消息正确路由到队列。BindingKey并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视BindingKey。而是将消息路由到所有绑定到该交换器的队列中。
代码
- 引入RabbitMQ配置,主要是地址,端口,用户名,密码配置。
## mq 基本配置
mq.rabbit.send.exchange=default
mq.rabbit.send.router=default
spring.rabbitmq.addresses=172.0.0.1:5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=15000
## mq消费者配置
spring.rabbitmq.listener.concurrency=5
spring.rabbitmq.listener.max-concurrency=10
- 初始化RabbitMQ队列,不初始化的话,需要手动登录RabbitMQ去创建队列,交换机绑定等。
/**
* 定义队列
**/
@Bean
public Queue noticeQueue() {
return new Queue("cn.lch.wxcenter.noticeQueue");
}
/**
* 定义Exchange
**/
@Bean
DirectExchange noticeExchange() {
return new DirectExchange("noticeExchange");
}
/**
* 队列和交换机绑定
*
* @param
* @return
*/
@Bean
Binding bindingExchangeMessageNotice(Queue noticeQueue, DirectExchange noticeExchange) {
return BindingBuilder.bind(noticeQueue).to(noticeExchange).with("notice");
}
- 生产者内容推送
/**
* 生产者推送内容
*
* @param jsonObject
*/
public void sendNotice(String jsonObject) {
amqpTemplate.convertAndSend("noticeExchange", "notice", jsonObject);
}
- 消费者消费消息
@Component
@RabbitListener(queues = "cn.lch.wxcenter.noticeQueue")
public class WxAddUserBusiness {
private Logger logger = LoggerFactory.getLogger(WxAddUserBusiness.class);
@RabbitHandler
public void receive(String json, Channel channel, Message message) throws IOException {
try {
System.out.println("收到的对消消息:" + json);
} catch (Exception e) {
logger.info("消息失败:" + e);
e.getStackTrace();
//消息确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
交换机类型
- RabbitMQ常用的交换器类型有fanout direct topic headers四种,还要另外两种System和自定义在此不做讨论,
- fanout:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。
- direct:它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。在发送消息的时候设置路由键去精确匹配。如图发送时设置侧路由键是“person”消息会路由到队列1,路由键是“student”消息会路由到队列2。
- topic:它也是匹配只不过是模糊匹配BindingKey和RoutingKey,RoutingKey可以由"."分割的字符传,比如:lch.com.cn。BindingKey也是由“.”号分割只不过加了“*” 和“#”模糊匹配,星号用于匹配一个单词,“#”可用于匹配多个单词(可以是零个)。
- headers:不用管用的很少,这玩意是通过发送的消息内容中headers属性进行匹配的。效率低性能不高。
RabbitMQ工作流程
- 生产者:
- 生产者连接到RabbitMQ Broker建立一个连接(Connecttion)开启一个信道(Channel)。
- 生产者声明一个交换机,并设置相关属性,比如交换机类型,是否持久化。
- 生产者申明一个队列,并设置相关属性,比如是否排他,是否持久化,是否自动删除。
- 生产者通过路由键将交换机和队列绑定起来。
- 生产者发送消息到RabbitMQ Broker,其中包含路由键和交换机信息。
- 交换机根据生产者发送的路由键查找匹配相应的队列。如果找到相应的队列,则将消息存入该队列。如果没找到则根据生产者配置的属相选择丢弃或者回退给生产者。
- 关闭信道。
- 关闭连接。
- 消费者
- 消费者连接到RabbitMQ Broker建立一个连接(Connecttion)开启一个信道(Channel)。
- 消费者向RabbitMQ Broker请求消费相应队列当中的消息,可能设置相应的回调函数,以及其他的准备工作。
- 等待RabbitMQ Broker回应并投递相应队列的消息,消费者消费消息。
- 消费者确认(ack)接收到的消息。
- RabbitMQ从队列中删除相应已经确认的消息。
- 关闭信道。
- 关闭连接。
小结
讲了RabbitMQ的入门知识,介绍了生产者(Producer)、消费者(Consumer)、队列(Queue)、交换器(Exchange)、路由键(RoutingKey)、绑定(Binding)等基本术语 ,介绍了交换器的类型fanout direct,topic,headers和RabbitMQ的工作流程。
备注
- 本文参考资料 朱忠华《RabbitMQ实战指南》