quene(point to point)和topic/subscibe JMS理论知识:
一、点对点模型
通过点对点的消息传递模型,一个应用程序可以向另外一个应用程序发送消息。在此传递模型中,目标类型时队列。消息首先被传送至队列目标,然后从改对垒将消息传送至对此队列进行监听的某个消费者
一个队列可以关联多个队列发送方和接收方,但一条消息仅传递给一个接收方。如果多个接收方正在监听队列上的消息,JMS Provider将根据“先来者优先”的原则确定由哪个消费者接受下一条消息。如果没有接收方在监听队列,消息将保留在队列中,直至接收方连接到队列为止。这种消息传递模型是传统意义上的拉模型或轮询模型。在此列模型中,消息不时自动推动给客户端的,而是要由客户端从队列中请求获得。
二、发布/订阅模型
通过发布/订阅消息传递模型,应用程序能够将一条消息发送到多个接收方。在此传送模型中,目标类型是主题。消息首先被传送至主题目标,然后传送至所有已订阅此主题的或送消费者。
主题目标也支持长期订阅。长期订阅表示消费者已注册了主题目标,但在消息到达目标时改消费者可以处于非活动状态。当消费者再次处于活动状态时,将会接收该消息。如果消费者均没有注册某个主题目标,该主题只保留注册了长期订阅的非活动消费者的消息。与PTP消息传递模型不同,pub/sub消息传递模型允许多个主题订阅者接收同一条消息。JMS一直保留消息,直至所有主题订阅者都接收到消息为止。pub/sub消息传递模型基本上时一个推模型。在该模型中,消息会自动广播,消费者无须通过主动请求或轮询主题的方法来获得新的消息。
上面两种消息传递模型里,我们都需要定义消息生产者和消费者,生产者吧消息发送到JMS Provider的某个目标地址(Destination),消息从该目标地址传送至消费者。消费者可以同步或异步接收消息,一般而言,异步消息消费者的执行和伸缩性都优于同步消息接收者,体现在:
1. 异步消息接收者创建的网络流量比较小。单向对东消息,并使之通过管道进入消息监听器。管道操作支持将多条消息聚合为一个网络调用。
2. 异步消息接收者使用线程比较少。异步消息接收者在不活动期间不使用线程。同步消息接收者在接收调用期间内使用线程,结果线程可能会长时间保持空闲,尤其是如果该调用中指定了阻塞超时。
3.对于服务器上运行的应用程序代码,使用异步消息接收者几乎总是最佳选择,尤其是通过消息驱动Bean。使用异步消息接收者可以防止应用程序代码在服务器上执行阻塞操作。而阻塞操作会是服务器端线程空闲,甚至会导致死锁。阻塞操作使用所有线程时则发生死锁。如果没有空余的线程可以处理阻塞操作自身解锁所需的操作,这该操作永远无法停止阻塞。
Queue与Topic的比较
1. JMS Queue执行load balancer语义
一条消息仅能被一个consumer收到。如果在message发送的时候没有可用的consumer,那么它讲被保存一直到能处理该message的consumer可用。如果一个consumer收到一条message后却不响应它,那么这条消息将被转到另外一个consumer那儿。一个Queue可以有很多consumer,并且在多个可用的consumer中负载均衡。
2. Topic实现publish和subscribe语义
一条消息被publish时,他将发送给所有感兴趣的订阅者,所以零到多个subscriber将接收到消息的一个拷贝。但是在消息代理接收到消息时,只有激活订阅的subscriber能够获得消息的一个拷贝。
3. 分别对应两种消息模式
Point-to-Point(点对点),Publisher/Subscriber Model(发布/订阅者)
其中在Publicher/Subscriber模式下又有Nondurable subscription(非持久化订阅)和durable subscription(持久化订阅)两种消息处理方式。
Point-to-Point(点对点)消息模式开发流程
1. 生产者(producer)开发流程
1.1 创建 Connection// 根据url,user和password创建一个jms Connection。
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory (user, password, url);
connection = connectionFactory.createConnection();
connection.start();
1.2 创建Session
/**在connection的基础上创建一个session,同时设置是否支持事务ACKNOWLEDGE标识。
• AUTO_ACKNOWLEDGE:自动确认模式。一旦接收方应用程序的方法调用从处理消息处返回,会话对象就会确认消息的接收。
• CLIENT_ACKNOWLEDGE:客户端确认模式。会话对象依赖于应用程序对被接收的消息调用一个acknowledge()方法。一旦这个方法被调用,会话会确认最后一次确认之后所有接收到的消息。这种模式允许应用程序以一个调用来接收,处理并确认一批消息。注意:在管理控制台中,如果连接工厂的Acknowledge Policy(确认方针)属性被设置为"Previous"(提前),但是你希望为一个给定的会话确认所有接收到的消息,那么就用最后一条消息来调用acknowledge()方法。
• DUPS_OK_ACKNOWLEDGE:允许副本的确认模式。一旦接收方应用程序的方法调用从处理消息处返回,会话对象就会确认消息的接收;而且允许重复确认。在需要考虑资源使用时,这种模式非常有效。注意:如果你的应用程序无法处理重复的消息的话,你应该避免使用这种模式。如果发送消息的初始化尝试失败,那么重复的消息可以被重新发送。
• SESSION_TRANSACTED**/
Session session = connection.createSession(
transacted, Session.AUTO_ACKNOWLEDGE);
1.3 创建Destination对象
//需指定其对应的主题(subject)名称,producer和consumer将根据subject来发送/接收对应的消息
if (topic) {
destination = session.createTopic(subject);
} else {
destination = session.createQueue(subject);
}
1.4 创建MessageProducer
//根据Destination创建MessageProducer对象,同时设置其持久模式。
MessageProducer producer = session.createProducer(destination);
if (persistent) {
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
} else {
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
}
if (timeToLive != 0) {
producer.setTimeToLive(timeToLive);
}
1.5 发送消息到队列(Queue)
//封装TextMessage消息,使用MessageProducer的send方法将消息发送出去。
TextMessage message = session.createTextMessage("createMessageText");
producer.send(message);
2. 消费者(consumer)开发流程
2.1 实现MessageListener接口
//消费者类必须实现MessageListener接口,然后在onMessage方法中监听消息到达处理。
2.2 创建Connection
//根据url,user和password创建一个jms Connection,如果是durable模式,还需要给connection设置一个clientId。
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, password, url);
Connection connection = connectionFactory.createConnection();
if (durable && clientId != null && clientId.length() > 0 && !"null".equals(clientId)) {
connection.setClientID(clientId);
}
connection.setExceptionListener(this);
connection.start();
2.3 创建Session和Destination
//与生产者类似
2.4 创建replayProducer【可选】
//可以用来将消息处理结果发送给producer。
replyProducer = session.createProducer(null);
replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);<span style="white-space: normal;"> </span>
2.5 创建MessageConsumer
//根据Destination创建MessageConsumer对象。
MessageConsumer consumer = null;
if (durable && topic) {
consumer = session.createDurableSubscriber((Topic)destination, consumerName);
} else {
consumer = session.createConsumer(destination);
}
2.6 消费message
//在onMessage()方法中接收producer发送过来的消息进行处理,并可以通过replyProducer反馈信息给producer
if (message.getJMSReplyTo() != null) {
replyProducer.send(message.getJMSReplyTo(),
session.createTextMessage("Reply: " + message.getJMSMessageID()));
}
Publish/Subscriber(发布/订阅者)消息开发模式
1. 订阅者(Subscriber)开发流程
1.1 实现MessageListener接口
在onMessage()方法中监听发布者发出的消息队列,并做相应处理。
1.2 创建Connection
根据url, user和password创建一个jms connection
1.3 创建Session
在connection的基础上创建一个session,同时设置是否支持和ACKNOWLEDGE标志。
1.4 创建 Topic
创建两个Topic,topictest.message用于接收发布者发出的消息,topictest.control用于向发布者发送消息,实现双方的交互。
1.5 创建consumer和producer对象
根据topictest.message创建consumer,根据topictest.control创建producer
1.6 接收处理消息
在onMessage()方法中,对收到的消息进行处理,可直接简单在本地显示消息,或者根据消息内容不同处理对应的业务逻辑(比如:数据库更新、文件操作等等),并且可以使用 producer对象处理结果返回给发布者。
2. 发布者(Publisher)开发流程
2.1 实现MessageListener接口
在onMessage()方法中接收订阅者的反馈消息。
2.2 创建Connection
根据url, user和password 创建一个 jms Connection。
2.3 创建session
在connection的基础上创建一个session,同时设置是否支持事务和ACKNOWLEDGE标志。
2.4 创建Topic
创建两个Topic,topictest.messages用于向订阅者发布消息,topictest.control用于接收订阅者反馈的消息。这两个Topic与订阅者开发流程中的topic是一一对应的。
2.5 创建consumer和producer对象
根据topictest.message创建publisher;
根据topictest.control穿件consumer,同时监听订阅者反馈的消息。
############################分割线#############################
企业中各项目中相互协作的时候可能用得到消息通知机制。比如有东西更新了,可以通知做索引。
在 Java 里有 JMS 的多个实现。其中 apache 下的 ActiveMQ 就是不错的选择。ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。这里示例下使用 ActiveMQ
用 ActiveMQ 最好还是了解下 JMS
JMS 公共 | 点对点域 | 发布/订阅域 |
ConnectionFactory | QueueConnectionFactory | TopicConnectionFactory |
Connection | QueueConnection | TopicConnection |
Destination | Queue | Topic |
Session | QueueSession | TopicSession |
MessageProducer | QueueSender | TopicPublisher |
MessageConsumer | QueueReceiver | TopicSubscriber |
JMS 定义了两种方式:Quere(点对点);Topic(发布/订阅)。
ConnectionFactory 是连接工厂,负责创建Connection。
Connection 负责创建 Session。
Session 创建 MessageProducer(用来发消息) 和 MessageConsumer(用来接收消息)。
Destination 是消息的目的地。
详细的可以网上找些 JMS 规范(有中文版)。
下载 apache-activemq-5.3.0。http://activemq.apache.org/download.html,解压,然后双击 bin/activemq.bat。运行后,可以在http://localhost:8161/admin 观察。也有 demo,http://localhost:8161/demo。把 activemq-all-5.3.0.jar 加入 classpath。
Jms 发送 代码:
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageProducer producer = session.createProducer(destination);
for(int i=0; i<3; i++) {
MapMessage message = session.createMapMessage();
message.setLong("count", new Date().getTime());
Thread.sleep(1000);
//通过消息生产者发出消息
producer.send(message);
}
session.commit();
session.close();
connection.close();
}
Jms 接收代码:
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
Connection connection = connectionFactory.createConnection();
connection.start();
final Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageConsumer consumer = session.createConsumer(destination);
int i=0;
while(i<3) {
i++;
MapMessage message = (MapMessage) consumer.receive();
session.commit();
//TODO something....
System.out.println("收到消息:" + new Date(message.getLong("count")));
}
session.close();
connection.close();
}
JMS五种消息的发送/接收的例子
1、消息发送
//连接工厂
ConnectionFactory connFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
"tcp://localhost:61616");
//连接到JMS提供者
Connection conn = connFactory.createConnection();
conn.start();
//事务性会话,自动确认消息
Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
//消息的目的地
Destination destination = session.createQueue("queue.hello");
//消息生产者
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); //不持久化
//文本消息
TextMessage textMessage = session.createTextMessage("文本消息");
producer.send(textMessage);
//键值对消息
MapMessage mapMessage = session.createMapMessage();
mapMessage.setLong("age", new Long(32));
mapMessage.setDouble("sarray", new Double(5867.15));
mapMessage.setString("username", "键值对消息");
producer.send(mapMessage);
//流消息
StreamMessage streamMessage = session.createStreamMessage();
streamMessage.writeString("streamMessage流消息");
streamMessage.writeLong(55);
producer.send(streamMessage);
//字节消息
String s = "BytesMessage字节消息";
BytesMessage bytesMessage = session.createBytesMessage();
bytesMessage.writeBytes(s.getBytes());
producer.send(bytesMessage);
//对象消息
User user = new User("cjm", "对象消息"); //User对象必须实现Serializable接口
ObjectMessage objectMessage = session.createObjectMessage();
objectMessage.setObject(user);
producer.send(objectMessage);
session.commit(); //在事务性会话中,只有commit之后,消息才会真正到达目的地
producer.close();
session.close();
conn.close();
2、消息接收:通过消息监听器的方式接收消息
public class Receiver implements MessageListener{
private boolean stop = false;
public void execute() throws Exception {
//连接工厂
ConnectionFactory connFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
"tcp://localhost:61616");
//连接到JMS提供者
Connection conn = connFactory.createConnection();
conn.start();
//事务性会话,自动确认消息
Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
//消息的来源地
Destination destination = session.createQueue("queue.hello");
//消息消费者
MessageConsumer consumer = session.createConsumer(destination);
consumer.setMessageListener(this);
//等待接收消息
while(!stop){
Thread.sleep(5000);
}
session.commit();
consumer.close();
session.close();
conn.close();
}
public void onMessage(Message m) {
try{
if(m instanceof TextMessage){ //接收文本消息
TextMessage message = (TextMessage)m;
System.out.println(message.getText());
}else if(m instanceof MapMessage){ //接收键值对消息
MapMessage message = (MapMessage)m;
System.out.println(message.getLong("age"));
System.out.println(message.getDouble("sarray"));
System.out.println(message.getString("username"));
}else if(m instanceof StreamMessage){ //接收流消息
StreamMessage message = (StreamMessage)m;
System.out.println(message.readString());
System.out.println(message.readLong());
}else if(m instanceof BytesMessage){ //接收字节消息
byte[] b = new byte[1024];
int len = -1;
BytesMessage message = (BytesMessage)m;
while((len=message.readBytes(b))!=-1){
System.out.println(new String(b, 0, len));
}
}else if(m instanceof ObjectMessage){ //接收对象消息
ObjectMessage message = (ObjectMessage)m;
User user = (User)message.getObject();
System.out.println(user.getUsername() + " _ " + user.getPassword());
}else{
System.out.println(m);
}
stop = true;
}catch(JMSException e){
stop = true;
e.printStackTrace();
}
}
}