JMS规范

本文深入介绍了JMS(Java消息服务)规范,涵盖了相关概念、消息模式(队列和主题)、JMS模型及编程接口。讨论了消息的持久性、确认机制及其不同确认模式,并提供了消息优化策略,如消息压缩、连接管理等,以提升系统性能。

1 JMS 规范

Java官方提供的一种要求各OMO厂商遵守的一个规范(API),以便开发者能够用一套API使用所有厂商的MQ。

1.1 JMS相关概念

提供者:实现JMS规范的消息中间件服务器。
客户端:发送或者接受消息的应用程序。
生产者/发布者:创建并发送消息客户端。
消费者/订阅者:接收并处理消息的客户端。
消息:应用程序之间传递数据内容。
消息模式:在客户端之间传递消息的方式,JMS中定义了主题和队列两种模式。

1.2 JMS消息模式

  • 1 队列模型

客户端包括生产者和消费者
队列中的消息只能被一个消费者消费。
消费者可以随时消费队列中的消息。
队列模型中,消费者的每个连接会依次接收JMS队列中的消息,每个连接接收到的是不同的消息。
在这里插入图片描述

  • 2 主题模式

客户端包括发布者和订阅者
主题中的消息被所有订阅者消费

消费者不能消费订阅之前就发送到主题中的消息,每个消费者收到的是全部的消息。
在这里插入图片描述

1.3 JMS模型

  • 1.3.1 编码接口:

ConnectionFactory:用于创建连接到消息中间件的连接工厂.
Connection:代表了应用程序和消息服务之间的通讯链路.
Destination:目的地,指消息发布和接收的地点,包括队列和主题.
Session:表示一个单线程的上下文,用于发送和接收消息.
MessageConsumer:由会话创建,用于接收发送到目标的主题和消息.
MessageProducer:由会话创建,用于发送消息到目标.
Message:是消息体,是在生产者和消费者之间传递的对象,由消息头(必须存在),消息属性,消息体组成.

  • 1.3.2 编程模型:

在这里插入图片描述

2 案例

Queue

MySender

    import java.io.BufferedReader;  
    import java.io.InputStreamReader;  
    import javax.naming.*;  
    import javax.jms.*;  
      
    public class MySender {  
        public static void main(String[] args) {  
            try  
            {   //Create and start connection  
                InitialContext ctx=new InitialContext();  
                QueueConnectionFactory f=(QueueConnectionFactory)ctx.lookup("myQueueConnectionFactory");  
                QueueConnection con=f.createQueueConnection();  
                con.start();  
                //2) create queue session  
                QueueSession ses=con.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);  
                //3) get the Queue object  
                Queue t=(Queue)ctx.lookup("myQueue");  
                //4)create QueueSender object         
                QueueSender sender=ses.createSender(t);  
                //5) create TextMessage object  
                TextMessage msg=ses.createTextMessage();  
                  
                //6) write message  
                BufferedReader b=new BufferedReader(new InputStreamReader(System.in));  
                while(true)  
                {  
                    System.out.println("Enter Msg, end to terminate:");  
                    String s=b.readLine();  
                    if (s.equals("end"))  
                        break;  
                    msg.setText(s);  
                    //7) send message  
                    sender.send(msg);  
                    System.out.println("Message successfully sent.");  
                }  
                //8) connection close  
                con.close();  
            }catch(Exception e){System.out.println(e);}  
        }  
    }  

MyReceiver

import javax.jms.*;  
import javax.naming.InitialContext;  
  
public class MyReceiver {  
    public static void main(String[] args) {  
        try{  
            //1) Create and start connection  
            InitialContext ctx=new InitialContext();  
            QueueConnectionFactory f=(QueueConnectionFactory)ctx.lookup("myQueueConnectionFactory");  
            QueueConnection con=f.createQueueConnection();  
            con.start();  
            //2) create Queue session  
            QueueSession ses=con.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);  
            //3) get the Queue object  
            Queue t=(Queue)ctx.lookup("myQueue");  
            //4)create QueueReceiver  
            QueueReceiver receiver=ses.createReceiver(t);  
              
            //5) create listener object  
            MyListener listener=new MyListener();  
              
            //6) register the listener object with receiver  
            receiver.setMessageListener(listener);  
              
            System.out.println("Receiver1 is ready, waiting for messages...");  
            System.out.println("press Ctrl+c to shutdown...");  
            while(true){                  
                Thread.sleep(1000);  
            }  
        }catch(Exception e){System.out.println(e);}  
    }  
  
} 

MyListener

    import javax.jms.*;  
    public class MyListener implements MessageListener {  
      
        public void onMessage(Message m) {  
            try{  
            TextMessage msg=(TextMessage)m;  
          
            System.out.println("following message is received:"+msg.getText());  
            }catch(JMSException e){System.out.println(e);}  
        }  
    }  
Topic

MySender

    import java.io.BufferedReader;  
    import java.io.InputStreamReader;  
    import javax.naming.*;  
    import javax.jms.*;  
      
    public class MySender {  
        public static void main(String[] args) {  
            try  
            {   //Create and start connection  
                InitialContext ctx=new InitialContext();  
                TopicConnectionFactory f=(TopicConnectionFactory)ctx.lookup("myTopicConnectionFactory");  
                TopicConnection con=f.createTopicConnection();  
                con.start();  
                //2) create queue session  
                TopicSession ses=con.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);  
                //3) get the Topic object  
                Topic t=(Topic)ctx.lookup("myTopic");  
                //4)create TopicPublisher object          
                TopicPublisher publisher=ses.createPublisher(t);  
                //5) create TextMessage object  
                TextMessage msg=ses.createTextMessage();  
                  
                //6) write message  
                BufferedReader b=new BufferedReader(new InputStreamReader(System.in));  
                while(true)  
                {  
                    System.out.println("Enter Msg, end to terminate:");  
                    String s=b.readLine();  
                    if (s.equals("end"))  
                        break;  
                    msg.setText(s);  
                    //7) send message  
                    publisher.publish(msg);  
                    System.out.println("Message successfully sent.");  
                }  
                //8) connection close  
                con.close();  
            }catch(Exception e){System.out.println(e);}  
        }  
    }  

MyReceiver

    import javax.jms.*;  
    import javax.naming.InitialContext;  
      
    public class MyReceiver {  
        public static void main(String[] args) {  
            try {  
                //1) Create and start connection  
                InitialContext ctx=new InitialContext();  
                TopicConnectionFactory f=(TopicConnectionFactory)ctx.lookup("myTopicConnectionFactory");  
                TopicConnection con=f.createTopicConnection();  
                con.start();  
                //2) create topic session  
                TopicSession ses=con.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);  
                //3) get the Topic object  
                Topic t=(Topic)ctx.lookup("myTopic");  
                //4)create TopicSubscriber  
                TopicSubscriber receiver=ses.createSubscriber(t);  
                  
                //5) create listener object  
                MyListener listener=new MyListener();  
                  
                //6) register the listener object with subscriber  
                receiver.setMessageListener(listener);  
                              
                System.out.println("Subscriber1 is ready, waiting for messages...");  
                System.out.println("press Ctrl+c to shutdown...");  
                while(true){                  
                    Thread.sleep(1000);  
                }  
            }catch(Exception e){System.out.println(e);}  
        }  
      
    }  

MyReceiver

import javax.jms.*;  
public class MyListener implements MessageListener {  
  
    public void onMessage(Message m) {  
        try{  
        TextMessage msg=(TextMessage)m;  
      
        System.out.println("following message is received:"+msg.getText());  
        }catch(JMSException e){System.out.println(e);}  
    }  
} 

3 消息分类

在传送过程中,系统处理JMS消息分为以下两类:
■ 有效负荷消息,由生成方发送给使用方的消息。
■ 控制消息,代理与客户端运行时环境之间传送的私有消息,用于确保有效负荷消息成功传送和控制跨连接的消息流。

详细流程如下:
消息生成
1. 客户端运行时环境通过连接将消息从消息生成方传送到代理。
消息处理和路由
2. 代理从连接中读取消息并将此消息放入相应的目的地中。
3. 代理将(持久性)消息放入数据存储库中。
4. 代理向消息生成方的客户端运行时环境确认已收到消息。
5. 代理确定消息的路由。
6. 代理将消息从目的地写入适当的连接,并使用使用方的唯一标识符标记该消息。
消息使用
7. 消息使用方的客户端运行时环境将消息从连接传送到消息使用方。
8. 消息使用方的客户端运行时环境向代理确认消息已使用。
消息生命周期结束
9. 代理处理客户端确认,并在收到所有确认后删除(持久性)消息。
10. 代理向使用方的客户端运行时环境确认,告知客户端确认已得到处理。
如果管理员删除目的地中的消息,或者管理员删除或重新定义长期订阅,导致主题目的地中的消息未被传送即被删除,则代理可以在消息被使用前将它丢弃。在其他情况下,您可能希望代理将消息存储在称为停用消息队列的特殊目的地中,而不是将它们丢弃。在以下情况,消息会被放入停用消息队列中:消息过期时、消息因内存限制而被删除时,以及因客户端引发异常而导致传送失败时。通过将消息存储在停用消息队列中,您可以解决系统问题并在某些情况下恢复消息。

4.消息持久性

JMS 支持以下两种消息提交模式:

  • 1 DeliveryMode.PERSISTENT 指示JMS provider持久保存消息,以保证消息不会因为JMS provider的失败而丢失。 消息持久化在硬盘中,ActiveMQ持久化有三种方式:AMQ、KahaDB、JDBC。

  • 2 DeliveryMode.NON_PERSISTENT 不要求JMS provider持久保存消息,消息存放在内存中,读写速度快,在JMS服务停止后消息会消失,没有持久化到硬盘。

5.JMS消息确认机制

5.1 消费消费过程

在确认JMS消息之前,不会认为它已被成功使用。消息的成功消费通常分三个阶段进行。

  • 1 客户端收到消息。
  • 2 客户端处理消息。
  • 3 该消息已得到确认。确认由JMS提供程序或客户端启动,具体取决于会话确认模式。
5.3 应答模式(acknowledgement mode)

在事务性会话中,当一个事务被提交的时候,确认自动发生。在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)。
该参数有以下三个可选值:

  • 1 “sessionAcknowledgeMode”设置为“AUTO_ACKNOWLEDGE”(默认值):在侦听器执行之前自动确认消息; 如果抛出异常,则无法重新发送。
    当客户成功的从receive方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息。

  • 2 “sessionAcknowledgeMode”设置为“CLIENT_ACKNOWLEDGE”:成功侦听器执行后自动确认消息; 如果抛出异常,则无法重新发送。
    客户通过消息的acknowledge方法确认消息。需要注意的是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消 费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认。

  • 3 “sessionAcknowledgeMode”设置为“DUPS_OK_ACKNOWLEDGE”:在侦听器执行期间或之后的延迟消息确认; 在例外抛出的情况下潜在的重新发送。
    该选择只是会话迟钝第确认消息的提交。如果JMS provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS provider必须把消息头的JMSRedelivered字段设置为true。

5.3 Session事物

“sessionTransacted”设置为“true” : 成功监听器执行后的事务确认; 如果抛出异常,保证重新发送。

6 消息优化策略

针对JMS应用中的一些优化策略:

1.根据需要设置收发消息的属性
在接收端和发送端可以设置消息发送和接收的属性,对于消息发送时还需要注意客户端的消息确认模式一共有3种客户机确认模式:
■ 在AUTO_ACKNOWLEDGE 模式下开销最大,可以保证消息逐条传送的可靠性,会话自动确认客户端使用的每条消息。会话线程会被阻止,以等待代理确认它已处理了每个已使用消息的客户端确认;

■ 在CLIENT_ACKNOWLEDGE 模式下,在一条或多条消息被使用后,客户端通过调用消息对象的acknowledge() 方法来显式确认。这样该会话就确认了自上次调用该方法后使用的所有消息。会话线程会被阻止,以等待代理确认它已处理了客户端确认。Message Queue 提供使客户端可仅仅确认收到一条消息的方法,从而扩展了该模式。因此需要的带宽开销较小;

■ 在DUPS_OK_ACKNOWLEDGE 模式下,会话在使用了十条消息后进行确认。会话线程不会因等待代理确认而被阻止,因为在该模式下代理确认不是必需的。虽然该模式可保证不会丢失消息,但并不能保证不会收到重复的消息,因此名称为:DUPS_OK。

对于更关心性能而不是可靠性的客户端,Message Queue 服务通过提供NO_ACKNOWLEDGE模式来扩展JMS API。在该模式下,代理不跟踪客户端确认,所以不保证使用方客户端已成功处理了消息。对于发送至非长期订户的非持久性消息,选择该模式可提高性能。

如果你有大量的消息需要进行发送,可以采用DUPS_OK_ACKNOWLEDGE模式,因为他是最快的。

代码示例

connection = connectionFactory.createTopicConnection();
session=connection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE);

2.对消息进行压缩
消息的大小对消息的效率是有影响的,跟Http/Gzip的道理一样,减少网络的负载,但是并不能提升你的运行效率,反而会将你的接收响应时间延时。
以下是消息压缩的代码示例:

1.发送压缩消息的代码示例:
for (int i=0;i<10000;i++){ //循环1W次发送消息
Javabloger_Msg msg=new Javabloger_Msg (); // 自定义的对象,进行实例化
msg.setTime( System.currentTimeMillis() ); //赋值
msg.setChat(“This is JavablogerMsg Test msg.复制300次”); //赋值,此处省略1000个字符
myTextMsg.setObject( msg ); //放入消息对象
myTextMsg.setBooleanProperty(“JMS_SUN_COMPRESS”, true); //设置是否进行压缩属性
myMsgProducer.send(myTextMsg); // 发送消息
}

2.接收端会自己进行解压缩,无需人工干预,但是可以查看出消息经过压缩前后的大小:
// 获得压缩前的消息大小。
int uncompressed=bytesMessage.getIntProperty(“JMS_SUN_UNCOMPRESSED_SIZE”);
//获得压缩后的消息大小。
int compressed=bytesMessage.getIntProperty(“JMS_SUN_COMPRESSED_SIZE”);

经过测试1000个字符 压缩以后只有95个字符位的大小。
1000个字符的消息发送1W个没有经过压缩, 收发在300毫秒以内完成,
1000个字符的消息发送1W个经过压缩后, 收发在1600毫秒以内完成,也就是1秒多以内完成。

3.取消对消息收/发的验证
如果在发送端或者在接收端加上用户和密码的验证会大大降低系统运行效率的,所以请慎用Sun OpenMQ中的身份验证功能。

4.对服务器连接的开关
客户端对JMS服务器的开关连接对服务器的运行性能有很大的影响,所以:
1我们可以采用容器提供的连接池,让容器来完成我们的连接资源管理。
2如果使用非容器下的运行状态,那么每次发送完毕一个消息后下面还有消息的话,关闭session就可以了,无需关闭对JMS服务器的连接。

5.调整JVM虚拟机运行参数
100个线程同时发送100个消息,一共10w个消息,运行到一半的时候出现
[29/Mar/2019:03:34:07 EDT] [B1089]: In low memory condition, Broker is attempting to free up resources
[29/Mar/2019:03:34:07 EDT] [B1088]: Entering Memory State YELLOW from previous state GREEN – allocated memory is 151214K, 80% of total memory used
错误,显然在说内存不够。经过google以后真实我的想法是正确的,参考文档:
http://docs.sun.com/app/docs/doc/819-4467/6n6k98bq2?l=zh_tw&a=view#aeokn
http://docs.sun.com/app/docs/doc/819-4467/6n6k98bqa?l=zh_tw&a=view
加大运行内存,将JMS服务器的内存使用率调整为1G,效果非常明显,命令如下:

nohup imqbrokerd -tty -name myBroker -port 6769 -cluster 192.168.0.1:7676,192.168.0.1:7677 -D"imq.cluster.masterbroker=192.168.0.1:7676" -vmargs "-Xms256m -Xmx1024m" &

6.慎用数据库存储消息
如果对于消息体内容较大,频率稍低的可以采用将消息体放入数据库进行存储。如果收发频率较高,并且消息体不算很大可以使用默认状态在内存中使用。也就是说根据不同的应用场景进行应用,千万不要认为将消息体放入数据库中存储是最好的方案,那样只能提高消息的稳定性和消息的安全性。

7.开启多个队列分载
来看一个测试案例:
TopicA、B 2个队列在同一台机器上 并发线程数 100X2=200个线程,每个线程发1000个消息,2个接收端平均每个接收端40秒以内处理完成。

TopicA、B 2个队列在同一台机器上 并发线程数 100X2=200个线程,每个线程发 500个消息,2个接收端平均每个接收端10秒以内处理完成。

TopicA、B、C、D 4个队列在同一台机器上 并发线程数 50X4=200个线程,每个线程发 250个消息,4个接收端平均每个接收端5秒以内处理完成。

经过以上测试可以表明,10W条消息进行散列以后效率明显提高了,有人会问为什么需要进行散列,因为在消息接收端 onMessage(Message msg)是单线程,对于Topic类型的消息,如果启动多线程也就是启动了多个onMessage触发,并不能提高运行效率。

8.队列的属性

无论是Topic还是quene类型的消息队列,默认状态都设置了上限的状态,如果消息量大的话将会出现这样的信息:
com.sun.messaging.jmq.jmsserver.util.BrokerException: [B4120]: Can not store message 18727-192.168.0.1(8c:56:3e:4f:ba:71)-4100-1269861102326 on destination TopicCase [Topic]. The destination message count limit (maxNumMsgs) of 100000 has been reached.
所以需要注意将队列中的状态,将队列的状态修改为没有限制的 Unlimited 选项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值