ActiveMQ是Apache开源基金会研发的消息中间件,是完全支持 JMS 和 J2EE 规范的 JMS provider实现
ActiveMQ是主要应用在分布式系统架构当中的,帮助构建高性能,高可用性,可伸缩的企业级面向消息服务的系统。
ActiveMQ可以应用在流量消除峰值,数据流量特别大的情况下,如阿里双十一。
像那种不是和正常的业务逻辑特别相关联的处理场景,这个时候,我们可以对问题进行解耦合操作,比如说在操作订单的系统之后,需要对库存也进行操作,我们就需要对流量进行消峰处理。
首先我们在 activeMQ的官方网站下载 activeMQ的linux版本(tar.gz结尾的文件),http://activemq.apache.org/activemq-5153-release.html
然后通过 sftp 服务传送到 linux服务器上,然后解压缩文件,来到bin目录下启动activeMQ sh activemq start
ps -ef|grep activemq检查启动状态,记得关闭centos的防火墙 systemctl stop firewalld.service
查看 activemq启动日志可以使用下面的命令:
tail -100f ../data/activemq.log
然后在另外一台机器上访问 http://ipaddr:8161/ 出现下面的界面则说明 activeMQ安装并且启动成功,注意 activeMQ的默认端口号是 8161
conf目录下有一个文件 jetty.xml 看到里面有 <bean id="jettyPort"> ,然后可以更改里面的端口号,变成自己想要的
进入 Manage ActiveMQ broker输入账号和密码:默认账号和密码都是 admin
JMS的基本概念:
概念:
java消息服务(Java Message Service),是java平台当中关于面向消息中间件的api,用于在两个应用程序之间或者分布式系统中发送消息,进行异步通信,JMS是一个与pigtail无关的API,支持 MOM(Message Oriented Middleware)(面向消息中间件),提供商都对JMS提供了支持。
JMS定义了两种消息传递域:
点对点:
1.每个消息只能有一个消费者
2.消息产生者和消息消费者之间没有时间上的相关性,无论消费者在生产者发送消息的时候是否处于运行状态,都可以提取消息。
3.我们可以在 activeMQ的控制台查看消息的消费,查看被哪些线程消费了,怎么消费的。
发布订阅:
一个消息发送端发送了消息,很多个消息消费端进行消息的消费.
1.每个消息可以有多个消费者
2.消费的生产者和消费者之间存在时间上的相关性,订阅一个主题的消费者只能消费自它订阅之后发布的消息,而它往期的消息却无法收到,JMS规范允许客户端
创建持久订阅.
3.JMS API,
ConnectionFactory 连接工厂
Connection 封装客户端与JMS Provider之间的一个虚拟连接
Session 生产和消费消息的一个单线程上下文,用于创建 producer、consumer、message、queue
Destination 消息发送或者接收的目的地。
MessageProducer/Message consumer 消息生产者
JMS的消息只有被确认之后,才会认为是被成功消费,消息消费包含三个阶段:客户端接收消息,客户端处理消息,消息被确认
事务性会话:
Session session = connection.createSession( Boolean.TRUE,Session.AUTO_AcknowLEDGE ); //当第一个参数设置成 true的时候,消息会在session.commit之后自动签收,只要没有签收,快递消息就一直存在。
非事务性会话
Session session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
在该模式下,消息何时被确认取决于创建会话的时候的应答模式
AUTO_ACKNOWLEDGE
当客户端成功从 receive方法中返回后,或者[MessageListener.onMessage]方法成功返回后,会话会自动确认该消息
注意一下,在消费消息的过程当中, receive方法和 MessageListener 这两种方法不能够同时工作。
CLIENT_ACKNOWLEDGE
客户端通过调用消息的 textMessage.acknowledge();确认消息
在这种模式中如果一个消息消费者消费一共是10个消息,那么消费了5个消息,然后在第5个消息通过 textMessage.acknowledge(),那么之前的消息全部都会被确认
DPUS_OK_ACKNOWLEDGE 延迟确认
activemq中的queue和topic的对比和区别:
类型 |
Topic | Queue |
概要 |
Publish Subscribe messaging 发布订阅消息 |
Point-to-Point 点对点 |
有无状态 |
topic数据默认不落地,是无状态的。 |
Queue数据默认会在mq服务器上以文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面。也可以配置成DB存储。 |
完整性保障 |
并不保证publisher发布的每条数据,Subscriber都能接受到。 |
Queue保证每条数据都能被receiver接收。 |
消息是否会丢失 |
一般来说publisher发布消息到某一个topic时,只有正在监听该topic地址的sub能够接收到消息;如果没有sub在监听,该topic就丢失了。 |
Sender发送消息到目标Queue,receiver可以异步接收这个Queue上的消息。Queue上的消息如果暂时没有receiver来取,也不会丢失。 |
消息发布接收策略 |
一对多的消息发布接收策略,监听同一个topic地址的多个sub都能收到publisher发送的消息。Sub接收完通知mq服务器 |
一对一的消息发布接收策略,一个sender发送的消息,只能有一个receiver接收。receiver接收完后,通知mq服务器已接收,mq服务器对queue里的消息采取删除或其他操作。 |
topic的使用案例如下:
一个发送端topicsender:
https://github.com/q907364606/activemq_learning/blob/master/provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsTopicSender.java
两个接收端的 topicreceiver
https://github.com/q907364606/activemq_receiver/blob/master/order-provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsTopicReceiver.java
https://github.com/q907364606/activemq_receiver/blob/master/order-provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsTopicReceiver2.java
运行 TopicReceiver和TopicReceiver2的主方法,线程会阻塞在 consumer.receive() 这个方法上,等待消息进来。
在运行 TopicSender 的主方法,看到两个receiver接收到消息。
如果消息接收端是在 发送端发送完消息之后再去监听,那么接收端是接收不到消息的。
activeMQ的持久订阅:
持久订阅设置 connection中相同的连接id,ClientId 以及相同的订阅名
持久订阅的使用场景是在 消费端当在某些情况下不可用了当机了,但是中间的消息不能丢失,接下来后来启动的消息仍然可以消费的。
持久订阅发送端topicsender:
https://github.com/q907364606/activemq_learning/blob/master/provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsTopicSender.java
持久订阅接收端的 topicreceiver
https://github.com/q907364606/activemq_receiver/blob/master/order-provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsTopicPersistenteReceiver.java
持久订阅要先发起一个订阅
在receiver当中设置了 ClientId("dubbo-order"); 和 session.DurableSubscriber(topic,"DUBBO-ORDER");
然后在消息发送端发送topic,不管消息接受端什么时候启动,都可以接收到消息,之前通过消息发送端发送的消息也都可以接受得到。
JMS
PUB/SUB模型
1.订阅分为非持久订阅和持久订阅。
2.当所有的消息必须接收的时候,则需要用到持久订阅,则用非持久订阅。
P2P模型
特点:
1.如果session关闭时,有一些消息已经被收到,但是还没有被签收,那么当消费者下次连接到相同的队列时,消息还会被签收。
2.如果用户在receiver方法中设定了消息选择条件,那么不符合条件的消息会留在队列中不会被接收。
3.队列可以长久保存消息直到消息被消费者签收,消费者不需要担心因为消息丢失而时刻与jms proivider保持连接状态。
Broker:
其实启动的一个 activeMq就是一个 Broker的实例,可以将activeMq的 请求端和 服务端都连接到 Broker 服务上面。
Broker代码如下:
https://github.com/q907364606/activemq_learning/blob/master/provider/src/main/java/com/gupao/vip/mic/dubbo/jms/DefineBrokerServer.java
然后将sender 和 receiver都注册过来:
sender:
https://github.com/q907364606/activemq_learning/blob/master/provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsSender.java
receiver:
https://github.com/q907364606/activemq_receiver/blob/master/order-provider/src/main/java/com/gupao/vip/mic/dubbo/jms/JmsReceiver.java
ActiveMQ当中支持的传输协议有下面的这些:
client端的 broker 端的通讯协议
TCP、UDP、NIO、SSL、Http、VM
ActiveMQ的持久化存储:
有四种持久化存储方式: KahaDB Message Store(默认), AMQ Message Store、使用Jdbc来持久化消息、MemoryMessage Store 基于内存的默认存储方式
在conf目录下的 activemq.xml 文件中 <persistenceAdapter></persistenceAdapter>中可以看出默认的存储策略是 kahaDB
如果要配置成 AMQ的方式需要这样配置: AMQ的方式写入速度快,容易恢复
<amqPersistenceAdapter></amqPersistenceAdapter drrectory="" maxFileLength=""> <!--消息存储的directory路径 maxFileLength指定文件大小默认32m-->
JDBC基于数据库的存储
在 <persistenceAdapter></persistenceAdapter> 中加上这样的配置.
<jdbcPersistenceAdapter dataSource="#mysqlDataSource" createTablesOnStartup="true" />
<!-- createTablesOnStartup="true" 这个属性的目的是是否在加载activeMQ的时候初始化加载数据库
dataSource="#mysqlDataSource" 注意这里必须要加上 # 这个参数
-->
然后再在 activemq.xml文件中配置这样的内容
<bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
然后在 activemq中的lib目录下添加下面的几个 jar 包,因为要用来连接 mysql 数据库所以要添加驱动包
commons-dbcp-1.4.jar,commons-pool-1.6.jar,mysql-connector-java-5.1.35.jar
然后到bin目录下重新启动 activeMQ , sh activemq restart
启动的时候报错可以到 activemq下的 data目录下的 activemq.log 这个文件中查看启动的出错日志
配置了 mysql的数据库连接之后 我们看到生成了三张表
ACTIVEMQ_ACKS 存储持久订阅的信息
ACTIVEMQ_LOCK 锁表(用来做集群的时候,实现master选举的表)
ACTIVE_MSGS 消息表
JDBC Message store with activeMQ journal
利用 activemq进行集群的操作,
首先我们在 activemq当中将 持久化的策略改成 kahadb的方式进行持久化
在另外一台机器上也安装 activemq ,并且将activemq开启,在 activemq.xml 的配置文件中增加下面的配置
<networkConnectors>
<networkConnector uri="static://(tcp://192.168.159.136:61616,tcp://192.168.159.135:61616)" />
</networkConnectors>
然后再重新启动两台 activemq,便可以实现集群
测试集群:
发送端 JmsSender的地址改成这个
"tcp://192.168.159.136:61616"
接收端 JmsReceiver 的地址改成这个
"tcp://192.168.159.135:61616"
然后运行两个端,可以看到从 136 发送过来的数据 135可以消费消息
activemq当中丢失的消息,需要使用消息回流来解决、
在这种情形下消息的状态其实是A服务器中的activemq当中没有消息,而B服务器中的activemq当中有2条消息,所以当 A下面的消费
者想要消费 消息的时候这个时候是消费不到的,因为消息丢失,
为了解决这个问题,我们需要使用消息回流
为了能够让消息回流,需要在activemq.xml当中进行配置,在 <policyEntries></policyEntries>当中需要增加一个配置
<!-- 设置消息回流 -->
<policyEntry queue=">" enableAudit="false" ><!-- 设置成false之后它认为消息可以被继续消费 -->
<networkBridgeFilterFactory>
<conditionalNetworkBridgeFilterFactory replayWhenNoConsumers="true" />
</networkBridgeFilterFactory>
</policyEntry>
配置了消息回流之后,发布出去的消息,不管怎样都可以在两边的消费者之间都可以消费
消息的发送策略:
持久化消息
默认情况下,生产者发送的消息是持久化的,消息发送到 broker之后,producer会等待broker对这条消息处理情况的反馈
可以设置消息发送端持久化消息的异步模式。
connection.setUserAsyncSend(true);
回执窗口大小设置:
connectionFactory.setProducerWindowSize(); //表示的当消息发送者发送的消息累积到某个大小以后,必须要等待服务器消息的回执,回执完消息递减,
非持久化消息
textMessage.setJMSDeliveryMode( DeliveryMode.NON_PERSISTENT);//设置为非持久化消息
非持久化消息模式下,默认就是异步的发送过程( producer发送消息不需要等待 broker 的返回值 )
connectionFactory.setAlwaysSyncSend();
默认情况下,mq服务器(broker)采用异步方式向客户端主动推送消息(push),也就是说broker在向某个消费者会话推送消息后,不会等待消费者响应消息,指导消费者处理消息,完成后,主动向broker返回处理结果。
prefetchsize:
预取消息数量:
broker端一旦有消息,就主动按照默认的设置规则推送给当前活动的消费者,每次推送都有一定的数量限制,而这个数量就是prefetchSize,持久化消息的Queue
持久化消息的 Queue,默认的perfetchSize="1000", 非持久化消息 1000
topic 持久化消息 100 非持久化消息 32766
假如 prefetchSize=0
“预取消息数量”
此时对于 consumer来说就是一个pull模式
prefetchSize在这个地方设置 Destination destination = session.createQueue("first-queue?customer.prefetchSize=100");
optimizeAcknowledge 开启优化 ACK,批量提交
默认情况下 prefetchSize*0.65 的时候执行以下批量的确认
optimizeAcknowledge 可以在创建activemq连接的时候进行设置如下语句
ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.159.138:61616?jms.optimizeAcknowledge=true&jms.optimizeAcknowledgeTimeOut=10000");
prefetchSize 和 optimizeAcknowledge 两者协同工作可以达到批量确认消息和批量获取消息。
一方面能够减少客户端在获取消息的阻塞次数,另外一方面还能减少获取消息时候的网络通信开销.
消息确认:
ACK_TYPE,消费端和broker交换ack指令的时候,还需要告知 broker ACK_TYPE
ACK_TYPE 表示确认指令的类型,broker可以根据不同的 ACK_TYPE,去针对当前消息做不同的应对策略。
REDELIVERED_ACK_TYPE (broker会重新发送该消息)重发策略。
DELIVERED_ACK_TYPE,消息已经接收,但是尚未处理结束。
STANDARD_ACK_TYPE 消息处理成功