ActiveMQ入门
1.1 JMS
JMS
即Java
消息服务(Java Message Service
)应用程序接口,是一个Java
平台中关于面向消息中间件(MOM)的API(只是接口,没有具体实现),用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。实现JMS接口的消息中间件称为JMS Provider
。
常用的有ActiveMQ
(基本满足大多数应用场景,小规模场景还不错),RabbitMQ
(性能不错,数据高可靠,支持集群),kafka
(性能、吞吐量都非常高,通过replicate方式保证高可用,可能出现少量数据丢失)。ActiveMQ
是Apache下的开源项目,完全支持JMS1.1和J2EE1.4规范的JMS Provider实现
面向消息的中间件。 发送者将信息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接受者。这种模式下,发送和接收是异步的,发送者无需等待;二者的生命周期未必相同;一对多通信:对于一个消息可以有多个接受者。
1.2 JMS相关术语
名称 | 描述 |
---|---|
Provider(MessageProvider) | 生产者 |
Consumer(MessageConsumer) | 消费者 |
PTP(Point to Point) | 即点对点的消息模型 |
Pub/Sub | Publish/Subscribe,即发布/订阅的消息模型 |
Queue | 队列目标 |
Topic | 主题目标 |
ConnectionFactory | 连接工厂,JMS用它创建连接 |
Connection | JMS客户端到JMS Provider的链接 |
Destination | 消息的目的地 |
Session | 会话,一个发送或接受消息的线程 |
1.2.1 JSM消息格式
JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性
StreamMessage
Java原始值的数据流MapMessage
一套名称-值对TextMessage
一个字符串对象ObjectMessage
一个序列化的 Java对象BytesMessage
一个字节的数据流
二、ActiveMQ入门案例
2.1 下载安装
- http://activemq.apache.org/
- 运行bin 目录下的
activemq.bat
- 输入
http://127.0.0.1:8161/admin
- 用户名和密码
admin / admin
- 通过控制台可以获取信息。
2.2 案例
生产者源码
package com.uzong.demo;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.util.concurrent.TimeUnit;
/**
* 消息队列的发送者
*/
public class Sender {
public static void main(String[] args) throws Exception {
//1. 创建默认的ConnectionFactory工厂对象,参数:用户、密码 和 URL. 默认端口为:tcp://localhost:61616
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
"tcp://localhost:61616"
);
//2. 通过ConnectionFactory创建connection连接,并使用.start() 开启连接。
Connection connection = connectionFactory.createConnection();
connection.start();//默认是关闭状态
//3.通过connection 创建session会话。第一个参数:是否开启事务,第二个参数:签收模式,默认自动
Session session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
//4.通过Session创建Destination对象,指的是一个客户端用来指定生产消息目标和消费信息来源的对象,
// 在PTP模式中,Destination被称作Queue即队列;在Pub/Sub模式,Destination被称作Topic即主题。
// 在程序中可以使用多个Queue和Topic。
Destination destination = session.createQueue("FIRST_QUEUE");
//5.:我们需要通过Session对象创建消息的发送和接收对象(生产者和消费者)MessageProducer/MessageConsumer。
MessageProducer messageProducer = session.createProducer(null);
//6:我们可以使用MessageProducer的setDeliveryMode方法为其设置持久化特性和非持久化特性(DeliveryMode)
//messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 7:最后我们使用JMS规范的TextMessage形式创建数据(通过Session对象),并用MessageProducer的send方法发送数据。
// 同理,客户端使用receive方法进行接收数据,最后需要关闭Connection连接。
for (int i = 0; i < 100; i++) {
TextMessage textMessage = session.createTextMessage();
textMessage.setText("我是消息内容......" + i);
messageProducer.send(destination, textMessage);
System.out.println("生产者:" + textMessage.getText());
TimeUnit.SECONDS.sleep(1); //暂停1s
}
//生产完成以后,需要手动关闭连接,释放资源。
if (connection != null) {
connection.close();
}
}
}
消费者源码
package com.uzong.demo;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
/**
* 消息队列的接受者
*/
public class Consumer {
public static void main(String[] args) throws Exception{
// 第一步:建立ConnectionFactory工厂对象,需要填入用户名、密码、以及要连接的地址,均使用默认即可,
// 默认端口为:tcp://localhost:61616
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
"tcp://localhost:61616");
// 第二步:通过ConnectionFactory工厂对象我们创建一个Connection链接,
// 并且调用ConnectionFactory的start方法开启链接,Connection默认是关闭的
Connection connection = connectionFactory.createConnection();
connection.start();
// 第三步:通过Connection对象创建Session会话(上下文环境对象),
// 用于接收消息,参数配置1为是否启用事务,参数配置2为签收模式,一般我们设置为自动签收
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 第四步:通过Session创建Destination对象,指的是一个客户端用来指定生产消息目标
// 和消费信息来源的对象,在PTP模式中,Destination被称作Queue即队列;
// 在Pub/Sub模式,Destination被称作Topic即主题。在程序中可以使用多个Queue和Topic。
Destination destination = session.createQueue("FIRST_QUEUE");
// 第五步:我们需要通过Session对象创建消息的发送和接收对象(生产者和消费者)
// MessageProducer/MessageConsumer。
MessageConsumer messageConsumer = session.createConsumer(destination);
while (true) {
TextMessage msg = (TextMessage) messageConsumer.receive();
if(msg == null){
break;
}
System.out.println("接收到的内容:" + msg.getText());
}
if (connection != null) {
connection.close();
}
}
}
三、安全
3.1 管理界面
activemq
的web管理界面: http://127.0.0.1:8161/adminactivemq
管控台使用jetty部署,所以需要修改密码则需要到相应的配置文件apache-activemq-5.11.1\conf\jetty-realm.properties
修改。如下:
# 下面的账号密码是管控台的认证
# Defines users that can access the web (console, demo, etc.)
# username: password [,rolename ...]
admin: admin, admin
user: user, user
- 只有符合认证的用户才能进行发送和获取消息,在
activemq.xml
里去添加安全验证配置。如下:
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">
……
<plugins>
<simpleAuthenticationPlugin>
<users>
<!-- 在java代码中的连接认证信息 -->
<authenticationUser username="root" password="123456" groups="users,admins"/>
</users>
</simpleAuthenticationPlugin>
</plugins>
</broker>
四、持久化数据源
activeMQ 持久化,可以切换不同的存储技术(默认是kahadb,leveldb,mysql,oracle)
4.1 使用mysql 作为持久化
- 修改activemq.xml文件
<!--
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
-->
<!-- 修改持久化方式 -->
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
<!-- MySql DataSource Sample Setup -->
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
- 重新启动activemq时,如果报错,发现类没有找到,则缺少相关jar包,下载相关jar到lib目录下。
commons-pool-1.6.jar
、commons-dbcp-1.4.jar
、commons-pool-1.6.jar
- 重新运行 activemq.bat
4.2 生成表
配置完成,重新运行代码后,会自动生成三张表:
activemq_acks
:ActiveMQ的签收信息。activemq_lock
: ActiveMQ的锁信息。activemq_msgs
: ActiveMQ的消息的信息
五、API详细
5.1 connection
当一个Connection
被创建时,它的传输默认是关闭的,必须使用start方法开启。一个Connection可以建立一个或多个的Session。
当一个程序执行完成后,必须关闭之前创建的Connection,否则ActiveMq不能释放资源,关闭一个Connection同样也关闭了Session,MessageProducer和MessageConsumer
5.2 SESSION
Session方法使用:
一旦从ConnectionFactory
中获得一个Connection
,必须从Connection
中创建一个或者多个Session
。Session
是一个发送或者接收消息的线程,可以使用Session
创建MessageProducer
,MessageConsumer
和Message
Session
可以被事务化,也可以不被事务化,通常,可以通过向Connection
上的适当创建方法传递一个布尔参数对此进行设置。
Session createSession(boolean transacted ,int acknowledgeMode)
其中transacted
为使用事务标识,acknowledgeMode
为签收模式。
结束事务有两种方法:提交或者回滚,当一个事务提交,消费被处理、出入事务中有一个步骤失败,事务就回滚,这个事务中的意见执行的动作将被撤销。在发送消息最后也必须要使用session.commit()
方法表示提交事务。
签收模式有三种形式:
-
Session.AUTO_ACKNOWLEDGE
当客户端从receive或onMessage成功返回时,Session自动签收客户端的这条消息的收条。 -
Session.CLIENT_ACKNOWLEDGE
客户端通过调用消息(Message)的acknowledgeMode方法签收消息。这种情况下,签收发生在Session层面:签收一个已消费的消息会自动签收这个Session所有已消费的收条。
//手动签收
msg.acknowledge();
Session.DUPS_OK_ACKNOWLEDGE
此选项指示Session不必确保对传送消息的签收。它可能引起消息的重复,但是降低了Session的开销,所以只有客户端能容忍重复的消息,才可使用
5.3 MessageProducer
MessageProducer
: MessageProducer
是一个由Session
创建的对象,用来向Destination
发送消息。
void send(Destination destination,Message message);
void send(Destination destination,Message message ,int deliveryMode,int priority . long timeToLive);
void send(Message message);
void send(Message message , int deliveryMode,int priority . long timeToLive)
其中deliveryMode
为传送模式,priority
为消息优先级,timeToLive
为消息过期时间。
-
ActiveMQ支持两种消息传送模式:PERSISTEN和NON_PERSISTENT两种。如果不指定传送模式,那么默认是持久化消息,如果容忍消息丢失,那么使用非持久性消息可以改善性能和减少存储的开销。
-
消息优先级从0-9十个级别。0-4是普通消息,5-9是加急消息。如果不指定优先级,则默认为4.JMS不要求严格按照这10个优先级发送消息,但必须保证单次加急消息要先于普通消息到达。
设置优先级需要在activemq.xml中进行配置。 如下:
<policyEntry queue="queuename" prioritizedMessages="true" />
- 当然ActimeMQ还有一种独有消费模式,可以确保消息顺序
ActiveMQ 的独占消费(Exclusive Consumer)
我们经常希望维持队列中的消息,按一定次序转发给消息者。然而当有多个JMS Session和消息消费者实例的从同一个队列中获取消息的时候,就不能保证消息顺序处理。因为消息被多个不同线程并发处理着。
在ActiveMQ4.x中可以采用Exclusive Consumer或者Exclusive Queues,避免这种情况,Broker会从消息队列中,一次发送消息给一个消息消费者来保证顺序。
配置如下:
queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true");
consumer = session.createConsumer(queue);
- 当在接收信息的时候有一个或者多个备份接收消息者和一个独占消息者的同时接收时候,无论两者创建先后,在接收的时候,均为独占消息者接收。
- 当在接收信息的时候,有多个独占消费者的时候,只有一个独占消费者可以接收到消息。
- 当有多个备份消息者和多个独占消费者的时候,当所有的独占消费者均close的时候,只有一个备份消费者接到到消息。
- 默认情况下,消息永不过期,如果消息在特定周期内失去意义,那么可以设置过期时间,时间单位为毫秒。
5.4 MessageConsumer
MessageConsumer
是一个由Session
创建的对象,用来从Destination
接收消息。
MessageConsumer createConsumer(Destination destination)
MessageConsumer createConsumer(Destination destination,String messageSelector)
MessageConsumer createConsumer(Destination destination,String messageSelector,boolean noLocal)
TopicSubscriber createDurableSubscriber(Topic topic,String name);
TopicSubscriber createDurableSubscriber(Topic topic,String name,String messageSelector,boolean noLocal);
其中messageSelector
为消息选择器,noLocal
标志默认为false,当设置为true时限制消费者只能接收和自己相同的连接(Conneciton)所发布的消息。此标志只适用于主推,不适用于队列;name标识订阅主题所对应的订阅名称,持久订阅时需要设置此参数。
public final String SELECTOR ="JMS_TYPE = 'MY_TAG1'"
;该选择器检查了传入消息的JMS_TYPE
属性,并确定了这个属性,并确定了这个属性的值是否等于MY_TAG1
.如果相等,则消息被消费,如果不相等,那么消息会被忽略。
消息的同步和异步接收:
消息的同步接收是指客户端主动去接收消息,客户端可以采用MessageConsumer
的receive方案区接收下一个消息。
Message receive()
Message receive(long timeout)
Message receiveNoWait()
消息的异步接收是指当消息到达时,ActiveMQ主动通知客户端,可以通过注册一个实现MessageListener
接口的对象到MessageConsumer
,MessageListener
只有一个必须实现的方案—onMessage
,它只接收一个参数,即Message
,在为每个发送到Destination
的消息实现onMessage
时,将调用该方法。
5.5 小案例
生产者
public class Producer {
private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
//生成者
private MessageProducer messageProducer;
//构造器中初始化四个对象
public Producer() {
this.connectionFactory = new ActiveMQConnectionFactory(
"root",
"123456",
"tcp://localhost:61616"
);
try {
this.connection = this.connectionFactory.createConnection();
this.connection.start();
this.session = this.connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
this.messageProducer = this.session.createProducer(null);
} catch (JMSException e) {
e.printStackTrace();
}
}
public Session getSession() {
return this.session;
}
public void send() {
try {
Destination destination = this.session.createQueue("FIRST_QUEUE");
MapMessage msg1 = this.session.createMapMessage();
msg1.setStringProperty("name", "小米");
msg1.setIntProperty("age", 25);
msg1.setIntProperty("sal", 8000);
MapMessage msg2 = this.session.createMapMessage();
msg2.setStringProperty("name", "小王");
//必须要使用setStringProperty 才能是选择过滤起作用
msg2.setIntProperty("age", 26);
msg2.setIntProperty("sal", 9000);
MapMessage msg3 = this.session.createMapMessage();
msg3.setStringProperty("name", "小李");
msg3.setIntProperty("age", 26);
msg3.setIntProperty("sal", 7000);
// this.messageProducer.send(destination, msg1, DeliveryMode.PERSISTENT, 3, 1000 * 660 * 10L);
// this.messageProducer.send(destination, msg2, DeliveryMode.PERSISTENT, 6, 1000 * 660 * 10L);
// this.messageProducer.send(destination, msg3, DeliveryMode.PERSISTENT, 9, 1000 * 660 * 10L);
this.messageProducer.send(destination, msg1);
this.messageProducer.send(destination, msg2);
this.messageProducer.send(destination, msg3);
} catch (JMSException e) {
e.printStackTrace();
}finally {
if(null != connection) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Producer producer = new Producer();
producer.send();
}
}
消费者
public class Consumer {
// public final String SELECTOR_0 = "age > 25";
// public final String SELECTOR_1 = "sal > 8500";
private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
//消费者
private MessageConsumer messageConsumer;
private Destination destination;
//构造器中初始化四个对象
public Consumer() {
this.connectionFactory = new ActiveMQConnectionFactory(
"root",
"123456",
"tcp://localhost:61616"
);
try {
this.connection = this.connectionFactory.createConnection();
this.connection.start();
this.session = this.connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
this.destination = this.session.createQueue("FIRST_QUEUE");
//消费者
this.messageConsumer = this.session.createConsumer(this.destination);
} catch (JMSException e) {
e.printStackTrace();
}
}
public void receiver() {
try {
this.messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message instanceof MapMessage){
MapMessage msg = (MapMessage) message;
try {
System.out.println(msg.toString());
System.out.println(msg.getStringProperty("name"));
System.out.println(msg.getIntProperty("age"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
} catch (JMSException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.receiver();
}
}
5.6 模型
- 点对点模型(Queue)
一个生产者向一个特定的队列发布消息,一个消费者从这个队列中依次读取消息
模型特点:只有一个消费者获得消息
- 发布者/订阅者模型(Topic)
0个或多个订阅者可以接受特定主题的消息
模型特点:多个消费者可获得消息
public void sendMessage() {
try {
//创建发布订阅模式。关键API。createTopic()
Destination destination = this.session.createTopic("topic");
TextMessage textMessage = this.session.createTextMessage("我是内容!");
messageProducer.send(destination,textMessage);
} catch (JMSException e) {
e.printStackTrace();
}
}
Topic和Queue的最大区别在于Topic是以广播的形式,通知所有在线监听的客户端有新的消息,没有监听的客户端将收不到消息;而Queue则是以点对点的形式通知多个处于监听状态的客户端中的一个
参考
案例代码地址:https://github.com/uzong/activemq
https://www.cnblogs.com/jaycekon/p/6220200.html
https://blog.csdn.net/fun913510024/article/details/45284379