queue
中, 然后消息消费者从 queue
中取出并且消费消息。queue
中不再有存储, 所以消息消费者不可能消费到已经被消费的消息。Queue
支持存在多个消费者, 但是对一个消息而言, 只会有一个消费者可以消费、 其它的则不能消费此消息了。
<dependency>
<groupId>org.apache.activemqgroupId>
<artifactId>activemq-coreartifactId>
<version>5.7.0version>
dependency>
<dependency>
<groupId>javax.jmsgroupId>
<artifactId>jmsartifactId>
<version>1.1version>
dependency>
public class ConsumerPTP {
public String consumer() {
//连接工厂
ConnectionFactory factory = null;
//连接
Connection connection = null;
//目的地
Destination destination = null;
//会话
Session session = null;
//消息发送者
MessageConsumer consumer = null;
//消息对象
Message message = null;
String resultCode = null;
try {
//创建连接工厂
//创建工厂三个参数:用户名,密码,连接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通过工厂创建连接对象
//创建连接的方法有重载,其中有createConnection(String username,String password)
//可以在创建连接时,只传递地址 ,不传递用户信息
connection = factory.createConnection();
//建议启动连接,消息的发送者不是必须启动连接,但是消息的消费者必须启动连接
connection.start();
//两个参数:boolean transacted 是否支持事物, int acknowledgeMode如何确认消息
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地。参数名称是目的地是唯一标记
destination=session.createQueue("first-activemq");
//通过会话,创建消息的发送者producer
//创建的消息发送者,发送的消息一定到指定的目的地中
consumer = session.createConsumer(destination);
//获取队列中的消息。receive方法是一个主动获取消息的方法。执行一次拉取一个消息
message = consumer.receive();
resultCode = ((TextMessage)message).getText();
System.out.println("========消息已经发送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(consumer != null) {//回收消息发送者
try {
consumer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收会话对象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收连接对象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
return resultCode;
}
public static void main(String[] args) {
ConsumerPTP ptp = new ConsumerPTP();
System.out.println("========="+ptp.consumer()+"=========");
}
}
public class ProviderPTP {
/**
* 把消息发送到activemq中,具体消息内容为参数信息
* 开发jms相关代码过程中,都在javax.jms包下的类型
* @param datas - 消息内容
*/
public void provider(String datas) {
//连接工厂
ConnectionFactory factory = null;
//连接
Connection connection = null;
//目的地
Destination destination = null;
//会话
Session session = null;
//消息发送者
MessageProducer producer = null;
//消息对象
Message message = null;
try {
//创建连接工厂
//创建工厂三个参数:用户名,密码,连接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通过工厂创建连接对象
//创建连接的方法有重载,其中有createConnection(String username,String password)
//可以在创建连接时,只传递地址 ,不传递用户信息
connection = factory.createConnection();
//建议启动连接,消息的发送者不是必须启动连接,但是消息的消费者必须启动连接
connection.start();
//连个参数:boolean transacted 是否支持事物, int acknowledgeMode如何确认消息
/**
* transacted:是否支持事物,
* true:支持事物,那么第二个参数默认无效建议是Session.SESSION_TRANSACTED
* false:不支持事物,常用参数。那么第二个参数必须传,且有效
* acknowledgeMode:如何确认消息的处理
* AUTO_ACKNOWLEDGE--自动确认消息,消息的消费者处理消息后,自动确认,商业开发不推荐
* CLIENT_ACKNOWLEDGE--客户端手动确认,消息的消费者处理后,必须手工确认
* DUPS_OK_ACKNOWLEDGE--有副本的客户端手动确认,一个消息可以多次处理,可以降低session消耗,可以容忍重复消息时使用(不推荐)
*/
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//创建目的地。参数名称是目的地是唯一标记
destination=session.createQueue("first-activemq");
//通过会话,创建消息的发送者producer
//创建的消息发送者,发送的消息一定到指定的目的地中
producer = session.createProducer(destination);
//创建文本消息对象,作为具体数据内容的载体
message= session.createTextMessage(datas);
//使用producer,发送消息到activemq中的目的地,若失败则抛出异常
producer.send(message);
System.out.println("========消息已经发送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(producer != null) {//回收消息发送者
try {
producer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收会话对象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收连接对象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ProviderPTP ptp = new ProviderPTP();
ptp.provider("测试activemq");
}
}
topic
中, 同时有多个消息消费者(订阅) 消费该消息。topic
的消息会被所有订阅者消费。不会保存
消息
<dependency>
<groupId>org.apache.activemqgroupId>
<artifactId>activemq-coreartifactId>
<version>5.7.0version>
dependency>
<dependency>
<groupId>javax.jmsgroupId>
<artifactId>jmsartifactId>
<version>1.1version>
dependency>
public class ConsumerTopic {
public String consumer() {
//连接工厂
ConnectionFactory factory = null;
//连接
Connection connection = null;
//目的地
Destination destination = null;
//会话
Session session = null;
//消息发送者
MessageConsumer consumer = null;
//消息对象
Message message = null;
String resultCode = null;
try {
//创建连接工厂
//创建工厂三个参数:用户名,密码,连接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通过工厂创建连接对象
//创建连接的方法有重载,其中有createConnection(String username,String password)
//可以在创建连接时,只传递地址 ,不传递用户信息
connection = factory.createConnection();
//建议启动连接,消息的发送者不是必须启动连接,但是消息的消费者必须启动连接
connection.start();
//两个参数:boolean transacted 是否支持事物, int acknowledgeMode如何确认消息
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地。参数名称是目的地是唯一标记
destination=session.createTopic("test-topicmq");
//通过会话,创建消息的发送者producer
//创建的消息发送者,发送的消息一定到指定的目的地中
consumer = session.createConsumer(destination);
//获取队列中的消息。receive方法是一个主动获取消息的方法。执行一次拉取一个消息
message = consumer.receive();
resultCode = ((TextMessage)message).getText();
System.out.println("========消息已经发送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(consumer != null) {//回收消息发送者
try {
consumer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收会话对象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收连接对象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
return resultCode;
}
public static void main(String[] args) {
ConsumerTopic ptp = new ConsumerTopic();
System.out.println("========="+ptp.consumer()+"=========");
}
}
public class ProviderTopic {
public static void main(String[] args) {
ProviderTopic ptp = new ProviderTopic();
ptp.provider("测试topic activemq");
}
public void provider(String datas) {
// 连接工厂
ConnectionFactory factory = null;
// 连接
Connection connection = null;
// 目的地
Destination destination = null;
// 会话
Session session = null;
// 消息发送者
MessageProducer producer = null;
// 消息对象
Message message = null;
try {
//创建连接工厂
//创建工厂三个参数:用户名,密码,连接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通过工厂创建连接对象
//创建连接的方法有重载,其中有createConnection(String username,String password)
//可以在创建连接时,只传递地址 ,不传递用户信息
connection = factory.createConnection();
//建议启动连接,消息的发送者不是必须启动连接,但是消息的消费者必须启动连接
connection.start();
//连个参数:boolean transacted 是否支持事物, int acknowledgeMode如何确认消息
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//创建目的地。参数名称是目的地是唯一标记
//destination=session.createQueue("first-activemq");
session.createTopic("test-topicmq");
//通过会话,创建消息的发送者producer
//创建的消息发送者,发送的消息一定到指定的目的地中
producer = session.createProducer(destination);
//创建文本消息对象,作为具体数据内容的载体
message= session.createTextMessage(datas);
//使用producer,发送消息到activemq中的目的地,若失败则抛出异常
producer.send(message);
System.out.println("========消息已经发送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(producer != null) {//回收消息发送者
try {
producer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收会话对象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收连接对象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
}
}
Topic | Queue | |
---|---|---|
概要 | Publish Subscribe messaging 发布订阅消息 |
Point-to-Point 点对点 |
有无状态 | topic 数据默认不落地, 是无状态的 |
Queue 数据默认会在mq 服务器上以文件形式保存, 比如 Active MQ 一 般 保 存 在$AMQ_HOME\data\kahadb 下面。也可以配置成 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 里的消息采取删除或其他操作 |
MessageProducer
send(Message message)
;发送消息到默认目的地, 就是创建 Producer
时指定的目的地。send(Destination destination, Message message)
; 发送消息到指定目的地, Producer
不建议绑定目的地。也就是创建 Producer
的时候,不绑定目的地。这样写法:session.createProducer(null)
send(Message message, int deliveryMode, int priority, long timeToLive)
;发送消息到默认目的地, 且设置相关参数。deliveryMode:
持久化方式(DeliveryMode.PERSISTENT|DeliveryMode.NON_PERSISTENT
)。priority:
优先级。 timeToLive:
消息有效期(单位毫秒)。send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive)
发送消息到指定目的地, 且设置相关参数。消息过期后, 默认会将失效消息保存到死信队列(ActiveMQ.DLQ)
。
不持久化的消息, 在超时后直接丢弃, 不会保存到死信队列中。
死信队列名称可配置, 死信队列中的消息不能恢复
。
死信队列是在activemq.xml
中配置的。
可以在发送消息时, 指定消息的权重,broker
可以建议权重较高的消息将会优先发送给 Consumer
。 在某些场景下, 我们通常希望权重较高的消息优先传送; 不过因为各种原因, priority
并不能决定消息传送的严格顺序(order
)。
JMS
标准中约定 priority
可以为0~9
的整数数值, 值越大表示权重越高,默认值为 4
。
activeMQ
中各个存储器对 priority
的支持并非完全一样。 比如 JDBC
存储器可以支持0~9
,因为JDBC
存储器可以基于 priority
对消息进行排序和索引化;但是对于 kahadb/levelDB
等这种基于日志文件的存储器而言, priority
支持相对较弱, 只能识别三种优先级(LOW: <4,NORMAL: =4,HIGH: > 4)
。
在 broker
端, 默认是不存储 priority
信息的, 我们需要手动开启, 修改 activemq.xml
配置文件, 在 broker
标签的子标签 policyEntries
中增加下述配置:
<policyEntry queue=">" prioritizedMessages="true"/>
不过对于“非持久化”类型的消息(如果没有被 swap
到临时文件), 它们被保存在内存中,它们不存在从文件Paged in
到内存的过程,因为可以保证优先级较高的消息,总是在prefetch
的时候被优先获取, 这也是“非持久化”消息可以担保消息发送顺序的优点。
Broker
在收到Producer
的消息之后, 将会把消息cache
到内存, 如果消息需要持久化,那么同时也会把消息写入文件; 如果通道中 Consumer
的消费速度足够快(即积压的消息很少, 尚未超过内存限制, 通过上文能够知道, 每个通道都可以有一定的内存用来 cache
消息), 那么消息几乎不需要从存储文件中Paged In
, 直接就能从内存的cache
中获取即可,这种情况下, priority
可以担保“全局顺序”; 不过, 如果消费者滞后太多,cache
已满, 就会触发新接收的消息直接保存在磁盘中, 那么此时,priority
就没有那么有效了。
在 Queue
中, prefetch
的消息列表默认将会采用轮询
的方式(roundRobin
, 注意并不是roundRobinDispatch
)[备注:因为 Queue
不支持任何 DispatchPolicy
],依次添加到每个 consumer
的 pending buffer
中, 比如有 m1-m2-m3-m4
四条消息, 有 C1-C2
两个消费者, 那么 :m1->C1,m2->C2,m3->C1,m4->C2
。 这种轮序方式, 会对基于权重的消息发送有些额外的影响, 假如四条消息的权重都不同, 但是(m1,m3)->C1
, 事实上m2
的权重>m3
,对于 C1
而言,它似乎丢失了“顺序性”。
<policyEntry queue=">" strictOrderDispatch="true"/>
strictOrderDispatch
“严格顺序转发”, 这是区别于“轮询”的一种消息转发手段; 不过不要误解它为“全局严格顺序”, 它只不过是将prefetch
的消息依次填满每个consumer
的pending buffer
。 比 如 上 述 例 子 中 , 如 果C1-C2
两 个 消 费 者 的buffer
尺 寸 为 3 , 那 么(m1,m2,m3)->C1,(m4)->C2
;当 C1
填充完毕之后, 才会填充 C2
。由此这种策略可以保证 buffer
中所有的消息都是“权重临近的”、 有序的。 (需要注意: strictOrderDispatch
并非是解决priority
消息顺序的问题而生, 只是在使用 priority
时需要关注它)。
<policyEntry queue=">" prioritizedMessages="true" useCache="false"
expireMessagesPeriod="0" queuePrefetch="1"/>
useCache=false
来关闭内存, 强制将所有的消息都立即写入文件(索引化, 但是会降低消息的转发效率);queuePrefetch=1
来约束每个consumer
任何时刻只有一个消息正在处理, 那些消息消费之后, 将会从文件中重新获取, 这大大增加了消息文件操作的次数, 不过每次读取肯定都是 priority
最高的消息
Consumer
拉取消息后, 如果没有做确认 acknowledge
, 此消息不会从 MQ
中删除。
如果消息拉去到consumer
后, 未确认, 那么消息被锁定。 如果 consumer
关闭的时候仍旧没有确认消息, 则释放消息锁定信息。 消息将发送给其他的consumer
处理。
消息一旦处理, 应该必须确认。 类似数据库中的事务管理机制。
例:consumer
确认方法(Session.CLIENT_ACKNOWLEDGE
客户端手动确认时才需要如下,若是AUTO_ACKNOWLEDGE
这不需要了)
message.acknowledge()
对消息消费者处理的消息数据进行过滤。 这种处理可以明确消费者的角色, 细分消费者的功能。
设置过滤:
Session.createConsumer(Destination destination, String messageSelector);
过滤信息为字符串, 语法类似 SQL92
中的 where
子句条件信息。 可以使用诸如AND、OR、 IN、 NOT IN
等关键字。 详细内容可以查看javax.jms.Message
的帮助文档。
注意:
消息的生产者在发送消息的的时候, 必须设置可过滤的属性信息
, 所有的属性信息设置方法格式为:setXxxxProperty(String name, T value)
。 其中方法名中的Xxxx
是类型,如 setObjectProperty/setStringProperty
等。