分布式消息通信ActiveMQ
消息中间件
消息中间件是指利用高效可靠的消息传递机制进行平台无关的数据交流,并且基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,可以在分布式架构下扩展进程之间的通信。
消息中间件能做什么
消息中间件主要解决分布式系统之间消息的传递问题 ,能够屏蔽各种平台以及协议之间的特性,实现应用之间的协同。
示例:
电商平台中的注册功能,用户注册不单是向数据库insert,可能还需要赠送积分,发送邮件,发送短信等系列操作。
假如:每个操作都耗时1s,那么注册过程就需要耗时4s才能响应给用户。从注册这个服务可以看出,每个子操作都是独立的,同时,基于领域划分以后,它们都属于不同的子域。所以我们可以对这些子操作实现异步化操作。类似多线程并行处理。
如何实现异步化?用多线程能实现吗?多线程当然可以实现,只是,消息的持久化、消息的重发这些条件,多线程 并不能满足.所以需要借助一些开源的消息中间件来解决。 而分布式消息队列就是一个很好的解决办法。通过引入分布式队列,大大提升程序的处理效率,并且还解决了各个模块之间的耦合问题。
分布式消息队列解决的场景:
引入消息中间件后(异步处理),电商平台中的注册架构图变为
电商中的秒杀:
用户提交过来的请求,先写入消息队列。消息队列是有长度的,如果消息队列超过指定长度,直接抛弃。
秒杀的 具体核心处理业务,接收消息队列中消息进行处理。这里的消息处理能力取决于消费端本身的吞吐量。
解耦、异步化、流量整形、数据的最终一致性(最大化的重试完成数据一致性)
ActiveMQ 简介
ActiveMQ
ActiveMQ 是完全基于JMS 规范实现的一个消息中间件产品,是Apache 开源基金会研发的消息中间件。ActiveMQ 主要应用在分布式系统架构中,帮助构建高可用、高性能、可伸缩的企业级面向服务的系统。
ActiveMQ 特性
-
多语言和协议编写客户端
-
语言:Java、C、C++、C#、Ruby、Perl、Python、PHP
-
协议:openwire、stomp、REST、ws、notification、xmpp、AMQP
-
-
完全支持JMS1.1和J2EE1.4规范
-
对Spring的支持,ActiveMQ可以很容易的嵌入到spring模块中
ActiveMQ 下载安装启动
下载地址
http://activemq.apache.org/activemq-5158-release.html
解压
tar -zxvf apache-activemq-5.15.8-bin.tar.gz
启动服务
-
cd apache-activemq-5.15.8/bin
sh activemq start
-
启动并带指定日志文件 sh activemq start > /tmp/activemqlog
关闭服务
-
sh activemq stop
监控地址
http://192.168.15.134:8161/admin/ admin admin
ActiveMQ 的端口61616
-
默认为61616
-
检查是否成功启动ActiveMQ
-
netstat -an|grep 61616
-
JMS 基本概念和模型
JMS的定义
JMS(Java Message Service) :面向消息中间件的API
MOM(Message Oriented Middleware):面向消息中间件
Java 消息服务是Java平台中关于面向消息中间件的API,用于两个程序 之间,或者分布式系统中发送消息,进行异步通信。
JMS 是一个与具体平台无关的API,绝大多数MOM 提供商都对JMS提供了支持。ActiveMQ就是其中的一个实现。
MOM
MOM 是面向消息的中间件,使用消息传送提供者来协调消息传送操作。 MOM 需要提供API和管理工具。客户端使用API调用,把消息发送到由提供者管理的目的地。在发送消息后,客户端会继续执行其他工作,并且在接收方收到这个消息确认之前,提供者一直保留该消息。
MOM 的特点
-
消息异步接收,发送者不需要等待消息接受者响应
-
消息可靠接收,确保消息中间件可靠保存。只有接收方收到消息后才删除消息
开源JMS提供商
JbossMQ(jboss4)、Jboss messaging(jboss5)、joram、ubermq、mantamq、openjms ...
JMS 规范
JMS 规范的目的是为了使得Java 应用程序能够访问现有MOM(消息中间件)系统,形成一套统一的标准规范,解决不同消息中间件之间的协作问题。
-
不同消息的传递域,点对点消息传送和发布/订阅消息传送
-
提供接收同步和异步消息的工具
-
对可靠消息传送的支持
-
常见消息格式,例如流、文本和字节
JMS 的体系结构
JMS 的基本功能
JMS 的基本功能是用于和面向消息中间件相互通信的应用程序的接口
消息传递域
-
p2p(point-2-point) 点对点消息传递域
-
每个消息只能有一个消费者(离线存储)
-
类似QQ聊天的私聊
-
-
生产者和消费者之间没有时间上的相关性,无论消费者在生产者发送消息的时候是否处于运行状态,都可以提取消息
-
如果session关闭时,有一些消息已经被收到,但是没有被签收,消费者下一次连接到相同对列时,这些消息仍然会被接收
-
如果用户在receive 方法中设定了消息的选择条件(消息过滤)
-
如果是持久化消息,消息会被持久化保存,直到消息被签收
-
-
发布订阅(publish/subscribe)消息传递域
-
每个消息有多个消费者
-
类似QQ群聊
-
-
生产者和消费者有时间上的相关性
-
订阅一个主题的消费者只能消费自它订阅之后发布的消息。
-
JMS 规范允许客户创建持久订阅,一定程度上降低了时间的相关性要求
-
持久订阅允许消费者消费它在未处于激活状态时发送的消息
-
-
持久化订阅和非持久化订阅
-
在非持久化订阅的前提下,不能恢复或者重新指派一个未签收的消息;
-
如果所有消息必须要签收,则使用持久订阅
-
消息的组成
消息头(Header)
消息头包含消息的识别信息和路由信息
消息头包含一些标准的属性:
-
JMSDestination
-
消息发送的目的地,queue或者topic
-
-
JMSDeliveryMode
-
传送模式,持久化模式和非持久模式
-
-
JMSPrority
-
消息优先级(优先级分为10个级别,从0最低-9最高)
-
如果不设定优先级,默认级别4,需要注意的是,JMS Provider 并不一定保证按照优先级的顺序提交
-
-
JMSMessageID
-
唯一识别每个消息的标识
-
消息体
就是我们需要传递的消息的内容
JMS API定义了5种消息体格式:
-
TextMessage
-
java.lang.String 对象,如xml文件内容
-
-
MapMessage
-
名/值对的集合,名是String 对象,值可以是Java 任何基本类型
-
-
BytesMessage
-
字节流
-
-
StreamMessage
-
Java 中的输入输出流
-
-
ObjectMessage
-
Java 中的可序列化对象
-
-
Message
-
没有消息体,只有消息头和属性
-
消息的属性
按类型分为:
-
应用设置的属性
-
Message.setStringProperty(key,value);
-
-
标准属性
-
使用“JMSX” 作为属性名的前缀
-
-
消息中间件定义的属性
-
JMS Provider 特定的属性
-
JMS 的可靠机制
消息的确认方式
消息的处理阶段:
-
客户端接收消息
-
客户端处理消息
-
消息被确认
会话存在两种机制:
-
事务性会话
-
createSession(boolean transacted, int acknowledgeMode)
-
Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
-
-
session.commit() //消息被确认 事务提交意味着生产的所有消息被发送,消费的所有消息被确认
-
session.rollback(); //重新处理 消息没有被提交,没有被处理,消费端的所有消息被恢复,并且重新被提交, 表示一个事务结束, 另一个事务会开始。事务回滚意味着生产的所有消息被销毁,消费的所有消息 被恢复并重新提交,除非它们已经过期
-
通过session.commit() //完成事务的签收
-
-
非事务性会话
-
transacted 设置为FALSE
-
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
-
客户端签收模型
-
Session session = connection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);
-
那么需要手动签收
-
textMessage.acknowledge();
-
客户端延迟确认,消息可能重复消费
-
Session session = connection.createSession(Boolean.FALSE, DUPS_OK_ACKNOWLEDGE);
-
-
事务性的自动确认
非事务性的自动确认和手动确认
消息的持久化存储
持久化(存储在数据库或磁盘)
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
对于持久消息,消息提供者会使用存储-转发机制,先将消息存储到稳定的介质中,等消息发送成功后再删除。如果JMS Provider 宕机,那么这些未送达的消息则不会丢失,JMS Provider 恢复正常后,会重新读取这些消息,并传送给对应的消费者。
非持久化(存储在内存中)
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
对于非持久化消息,JMS Provider 不会将它存到文件、数据库等稳定介质中。也就是说非持久消息,存储在内存中,如果JMS Provider 宕机,那么非持久化消息会丢失。
持久订阅
-
持久订阅者和非持久订阅者针对的Domain 是Pub/Sub,而不是P2P
-
当Broker 发送消息给订阅者时,如果订阅者处于未激活状态,持久订阅者可以收到消息,而非持久订阅者则收不到消息。
-
当持久订阅者处于未激活状态时,Broker 需要为持久订阅者保存消息,如果持久订阅者订阅的消息太多则会溢出。
-
持久订阅时,客户端向JMS 服务器注册一个自己身份的ID, 当这个客户端处于离线时,JMS Provider 会为这个ID 保存所有发送到主题的消息,当客户再次连接到 JMS Provider时,会根据自己的ID得到所有当自己处于离线时发送到主题的消息。
-
持久订阅的方式(消费端)
-
connection.setClientID("test");
-
Topic destination=session.createTopic("myTopic");
-
MessageConsumer consumer=session.createDurableSubscriber(destination,"test");
案例架构图
<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.8</version> </dependency>
生产端
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class JMSQueueProducer { public static void main(String args[]) { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.15.134:61616"); Connection connection = null; try { connection = connectionFactory.createConnection(); connection.start(); Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); //创建目的地 Destination destination = session.createQueue("myQueue"); //创建发送者 MessageProducer producer = session.createProducer(destination); //持久化 producer.setDeliveryMode(DeliveryMode.PERSISTENT); TextMessage textMessage = session.createTextMessage("Hello,World"); producer.send(textMessage); session.commit(); session.close(); } catch (JMSException e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } } }
消费端
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import javax.xml.soap.Text; public class JMSQueueConsumer { public static void main(String args[]) { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.15.134:61616"); Connection connection = null; try { connection = connectionFactory.createConnection(); connection.start(); Session session = connection.createSession(Boolean.TRUE,Session.AUTO_ACKNOWLEDGE); //创建目的地 Destination destination = session.createQueue("myQueue"); //创建接收者 MessageConsumer consumer = session.createConsumer(destination); //接收消息 阻塞方式监听消息 TextMessage textMessage =(TextMessage) consumer.receive(); System.out.println(textMessage.getText()); session.commit(); //表示消息被自动确认 session.close(); } catch (JMSException e) { e.printStackTrace(); }finally { if(connection!=null) { try { connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } } }