消息中间件
在何种场景下需要使用消息中间件
双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口。但是这么做会造成很严重的影响,比如说如果库存系统垮了,那么订单系统也随之崩溃,如果引入了消息中间件就不会产生这种问题。
为什么要使用消息中间件
解耦、削峰、异步。
系统之间直接调用产生的问题
- 系统间耦合比较严重
- 流量大的时候容易被冲垮
- 等待同步存在性能问题
消息中间件定义
MQ能够很好的解决以上问题,是指利用高效可靠的消息传递机制与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型在分布式环境下提供应用解耦,弹性伸缩,冗余存储、流量削峰,异步通信,数据同步等功能。
MQ主要功能
发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题topic中,在合适的时候,消息服务器会将消息转发给接受者。在这个过程中,发送和接收是异步的,也就是发送无需等待,而且发送者和接受者的生命周期也没有必然的关系;尤其在发布pub/订阅sub模式下,也可以完成一对多的通信,即让一个消息有多个接受者。
MQ的优势
- 实现高可用、高性能、可伸缩、易用和安全的企业级面向消息的服务
- 异步消息的消费和处理
- 控制消息的消费顺序
- 可以很好的和 spring springboot 进行整合
- 很方便的配置MQ集群环境
ActiveMQ
- ActiveMQ采用61616端口提供JMS服务
- ActiveMQ采用8161端口提供可视化界面控制台
- ActiveMQ的两大特性:队列、主题
JSM规范
JMS是Java消息服务 Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。
Java代码实现ActiveMQ
- 和我们以前写的JDBC代码很相似
//创建给定的activeMQ工厂,并按照url连接activeMQ
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory("tcp://192.168.18.128:61616");
//通过连接工厂获取Connection,并且启动访问
Connection connection = activeMQConnectionFactory.createConnection();
//创建会话session
//两个参数,第一个是事务,第二个是签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地,具体是queue还是topic
Topic topic = session.createTopic("topic");
//Queue queue = session.creatQueue("queue");
//创建消息的生产者或者消息的消费者
MessageProducer messageProducer = session.createProducer(topic);
//MessageConsumer messageConsumer = session.createConsumer(queue);
connection.start();
//通过session生产3条消息发送到MQ中
for (int i = 1; i <= 3; i++) {
//生产3条消息
TextMessage textMessage = session.createTextMessage("message:" + i);
//通过生产者发送到MQ
messageProducer.send(textMessage);
}
//通过session消费3条消息
messageConsumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
if (message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("成功获取消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
//关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("成功发送消息到MQ");
}
-
这是最基本的使用Java原生代码实现ActiveMQ,可以根据具体的需求以及高级特性对session、Connection、MessageProducer进行配置,我们平常直接使用这种写法的可能性很低,大部分是整合spring进行开发。
-
ActiveMQ整合Spring
-
通过applicatiomContext.xml配置activeMQ的属性和JMS规范
<bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.spring.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.18.128:61616"/>
</bean>
</property>
<property name="maxConnections" value="100"/>
</bean>
<!--这个是队列目的地,点对点的Queue-->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 通过构造注入Queue名 -->
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<!--这个是队列目的地,发布订阅的主题Topic-->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic"/>
</bean>
<!-- Spring提供的JMS工具类,他可以进行消息发送,接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 传入连接工厂 -->
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 传入目的地 -->
<!--需要传递什么,就传入什么目的地,代码不用改变-->
<property name="defaultDestination" ref="destinationQueue"/>
<!-- 消息自动转换器 -->
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
<!-- 配置Jms消息监听器 -->
<!--配置了监听器不需要启动消费者,只要生产者发送消息,就会自动接受,相当于Spring默认帮你启动消费者-->
<bean id="defaultMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--Jms连接的工厂-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--设置默认的监听目的地-->
<property name="destination" ref="destinationTopic"/>
<!--指定自己实现了MessageListener的类-->
<property name="messageListener" ref="myMessageListener"/>
</bean>
- 使用方式很简单,启动spring注入JmsTemplate
@Service
public class SpringProducer {
//注入jmsTemplate这个类,在配置文件配置完成的类,可以直接创建出生产者
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicaitonContext.xml");
SpringProducer springProducer = (SpringProducer) applicationContext.getBean("SpringProducer");
springProducer.producer();
}
public void producer() {
jmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage("Spring+activeMQ:");
return textMessage;
}
});
System.out.println("成功发送activeMQ消息");
}
//消费端消费消息
TextMessage textMessage = (TextMessage) springConsumer.jmsTemplae.receive();
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
- 具体的使用方式不需要记住,只需要在使用的时候能够通过官网进行配置即可。
- 使用springboot更简单,通过配置使用 JmsMessagingTemplate 即可。
两种消费方式
- 同步阻塞方式(receive) 订阅者或接收者抵用MessageConsumer的receive()方法来接收消息,receive方法在能接收到消息之前(或超时之前)将一直阻塞。
- 异步非阻塞方式(监听器onMessage())订阅者或接收者通过MessageConsumer的setMessageListener(MessageListener listener)注册一个消息监听器,当消息到达之后,系统会自动调用监听器MessageListener的onMessage(Message message)方法。
ActiveMQ两种模式
主题:Queue模式会把所有Producer提供的信息,根据Cousumer请求数进行均摊,
队列:Topic模式会返回给每个Consumer所有Producer提供的服务,你需要什么就用什么。
ActiveMQ的持久化
持久化消息主要指的是: MQ所在服务器宕机了消息不会丢试的机制。
持久化机制演变的过程: 从最初的AMQ Message Store方案到ActiveMQ V4版本退出的High Performance Journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案。ActiveMQ5.3版本又推出了对KahaDB的支持(5.4版本后被作为默认的持久化方案),后来ActiveMQ 5.8版本开始支持LevelDB,到现在5.9提供了标准的Zookeeper+LevelDB集群化方案。
ActiveMQ消息持久化机制有:
AMQ 基于日志文件
KahaDB 基于日志文件,从ActiveMQ5.4开始默认使用
JDBC 基于第三方数据库
Replicated LevelDB Store 从5.9开始提供了LevelDB和Zookeeper的数据复制方法,用于Master-slave方式的首选数据复制方案。
ActiveMQ高级特性
- 异步投递
ActiveMQ支持两种消息投递方式:同步投递和异步投递。消息投递指的是消息生产者端将消息发送到消息服务器(即Broker)的过程,若采用同步投递的方式,则生产者端每次向消息服务器发送消息时都需要同步地等待消息服务器给予消息发送成功与否的回执,这一定程度上可能会引起消息生产者的阻塞,影响消息发送的效率;采用异步投递的方式则不会有这个问题,因此ActiveMQ默认采用异步投递的方式。
使用异步投递的方式怎么确定消息投递成功与否呢?ActiveMQ为我们提供了回调机制,在我们向消息服务器发送消息时可以同时传递一个异步回调方法,无论消息是否投递成功消息服务器都会调用这个回调方法用于通知消息生产者这个消息投递的状态。
- 延时投递和定时投递
ActiveMQ还支持消息的延迟投递和定时投递,这也是针对于消息生产者的。ActiveMQ为我们提供的 ScheduledMessage 类描述了延迟投递和定时投递的四种属性:
- 消息重试机制
在消息的消费过程中,如果消息未被签收或者签收失败,是会导致消息重复消费的,但如果消息一直签收失败,那是不是就会被无限次的消费呢?答案是否定的。一条消息签收不成功,消息服务器就会认为该消费者没有消费过这条消息,就会再次将这条消息传送给该消费者供它消费。默认重试6次如果超过了则加入死信队列
- 死信队列
在一条消息被重复发送给消息消费者端多次(默认为6次)后,若一直签收不成功,则ActiveMQ会将这条消息移入到“死信队列”。开发时可以开启一个后台线程监听这个队列(默认死信队列的名称为ActiveMQ.DLQ)中的消息,进行人工干预,也就是说死信队列的作用主要是处理签收失败的消息。
- 防止重复调用引发的问题
ActiveMQ中的消息有时是会被重复消费的,而我们消费消息时大都会在拿到消息后去调用其他的方法,比如说将消息的内容解析为一个对象保存到数据库中。一旦发生消息的重复消费时就会重复保存,这是有问题的,因此我们需要考虑如何防止重复调用。其实我们是没有办法防止重复调用的,只能在重复调用时进行消息是否重复消费的校验,当然对于幂等性接口也可以不进行校验。
那如何进行校验呢?我们可通过 CAS 的思想,比如说我们将消费过的消息的messageId保存到数据库,每次消费消息前先到数据库中查一下该消息是否已被消费。在分布式系统中,也可以将消费过的消息放入redis中,以messageId作为key,message对象作为value(其实value不重要,当然也要看需求本身),在消费消息时先从redis中查找该消息是否已被消费。