activemq 使用

1 JMS
1.1 JMS的基本构件
1.1.1 连接工厂
连接工厂是客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQConnectionFactory。
1.1.2 连接
JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。
1.1.3 会话
JMS Session是生产和消费消息的一个单线程上下文。会话用于创建消息生产者(producer)、消息消费者(consumer)和消息(message)等。会话提供了一个事务性的上下文,在这个上下文中,一组发送和接收被组合到了一个原子操作中。
1.1.4 目的地
目的地是客户用来指定它生产的消息的目标和它消费的消息的来源的对象。JMS1.0.2规范中定义了两种消息传递域:点对点(PTP)消息传递域和发布/订阅消息传递域。
点对点消息传递域的特点如下:
• 每个消息只能有一个消费者。
• 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,它都可以提取消息。
发布/订阅消息传递域的特点如下:
• 每个消息可以有多个消费者。
• 生产者和消费者之间有时间上的相关性。订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求。持久订阅允许消费者消费它在未处于激活状态时发送的消息。
在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)。
1.1.5 消息生产者
消息生产者是由会话创建的一个对象,用于把消息发送到一个目的地。
1.1.6 消息消费者
消息消费者是由会话创建的一个对象,它用于接收发送到目的地的消息。消息的消费可以采用以下两种方法之一:
• 同步消费。通过调用消费者的receive方法从目的地中显式提取消息。receive方法可以一直阻塞到消息到达。
• 异步消费。客户可以为消费者注册一个消息监听器,以定义在消息到达时所采取的动作。
1.1.7 消息
JMS消息由以下三部分组成:
• 消息头。每个消息头字段都有相应的getter和setter方法。
• 消息属性。如果需要除消息头字段以外的值,那么可以使用消息属性。
• 消息体。JMS定义的消息类型有TextMessage、MapMessage、BytesMessage、StreamMessage和ObjectMessage。
1.2 JMS的可靠性机制
1.2.1 确认
JMS消息只有在被确认之后,才认为已经被成功地消费了。消息的成功消费通常包含三个阶段:客户接收消息、客户处理消息和消息被确认。
在事务性会话中,当一个事务被提交的时候,确认自动发生。在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)。该参数有以下三个可选值:
• Session.AUTO_ACKNOWLEDGE。当客户成功的从receive方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息。
• Session.CLIENT_ACKNOWLEDGE。客户通过消息的acknowledge方法确认消息。需要注意的是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认。
• Session.DUPS_ACKNOWLEDGE。 该选择只是会话迟钝第确认消息的提交。如果JMS provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS provider必须把消息头的JMSRedelivered字段设置为true。
1.2.2 持久性
JMS 支持以下两种消息提交模式:
• PERSISTENT。指示JMS provider持久保存消息,以保证消息不会因为JMS provider的失败而丢失。
• NON_PERSISTENT。不要求JMS provider持久保存消息。
1.2.3 优先级
可以使用消息优先级来指示JMS provider首先提交紧急的消息。优先级分10个级别,从0(最低)到9(最高)。如果不指定优先级,默认级别是4。需要注意的是,JMS provider并不一定保证按照优先级的顺序提交消息。
1.2.4 消息过期
可以设置消息在一定时间后过期,默认是永不过期。
1.2.5 临时目的地
可以通过会话上的createTemporaryQueue方法和createTemporaryTopic方法来创建临时目的地。它们的存在时间只限于创建它们的连接所保持的时间。只有创建该临时目的地的连接上的消息消费者才能够从临时目的地中提取消息。
1.2.6 持久订阅
首先消息生产者必须使用PERSISTENT提交消息。客户可以通过会话上的createDurableSubscriber方法来创建一个持久订阅,该方法的第一个参数必须是一个topic。第二个参数是订阅的名称。
JMS provider会存储发布到持久订阅对应的topic上的消息。如果最初创建持久订阅的客户或者任何其它客户使用相同的连接工厂和连接的客户ID、相同的主题和相同的订阅名再次调用会话上的createDurableSubscriber方法,那么该持久订阅就会被激活。JMS provider会象客户发送客户处于非激活状态时所发布的消息。
持久订阅在某个时刻只能有一个激活的订阅者。持久订阅在创建之后会一直保留,直到应用程序调用会话上的unsubscribe方法。
1.2.7 本地事务
在一个JMS客户端,可以使用本地事务来组合消息的发送和接收。JMS Session接口提供了commit和rollback方法。事务提交意味着生产的所有消息被发送,消费的所有消息被确认;事务回滚意味着生产的所有消息被销毁,消费的所有消息被恢复并重新提交,除非它们已经过期。
事务性的会话总是牵涉到事务处理中,commit或rollback方法一旦被调用,一个事务就结束了,而另一个事务被开始。关闭事务性会话将回滚其中的事务。
需要注意的是,如果使用请求/回复机制,即发送一个消息,同时希望在同一个事务中等待接收该消息的回复,那么程序将被挂起,因为知道事务提交,发送操作才会真正执行。
需要注意的还有一个,消息的生产和消费不能包含在同一个事务中。
1.3 JMS 规范的变迁
JMS的最新版本的是1.1。它和同1.0.2版本之间最大的差别是,JMS1.1通过统一的消息传递域简化了消息传递。这不仅简化了JMS API,也有利于开发人员灵活选择消息传递域,同时也有助于程序的重用和维护。
以下是不同消息传递域的相应接口:
JMS 公共 点对点域 发布/订阅域
ConnectionFactory QueueConnectionFactory TopicConnectionFactory
Connection QueueConnection TopicConnection
Destination Queue Topic
Session QueueSession TopicSession
MessageProducer QueueSender TopicPublisher
MessageConsumer QueueReceiver TopicSubscriber

2 ActiveMQ
2.1 Broker
2.1.1 Running Broker
ActiveMQ5.0 的二进制发布包中bin目录中包含一个名为activemq的脚本,直接运行这个脚本就可以启动一个broker。
此外也可以通过Broker Configuration URI或Broker XBean URI对broker进行配置,以下是一些命令行参数的例子:
Example Description
activemq Runs a broker using the default 'xbean:activemq.xml' as the broker configuration file.
activemq xbean:myconfig.xml Runs a broker using the file myconfig.xml as the broker configuration file that is located in the classpath.
activemq xbean:file:./conf/broker1.xml Runs a broker using the file broker1.xml as the broker configuration file that is located in the relative file path ./conf/broker1.xml
activemq xbean:file:C:/ActiveMQ/conf/broker2.xml Runs a broker using the file broker2.xml as the broker configuration file that is located in the absolute file path C:/ActiveMQ/conf/broker2.xml
activemq broker:(tcp://localhost:61616, tcp://localhost:5000)?useJmx=true Runs a broker with two transport connectors and JMX enabled.
activemq broker:(tcp://localhost:61616, network:tcp://localhost:5000)?persistent=false Runs a broker with 1 transport connector and 1 network connector with persistence disabled.

2.1.2 Embedded Broker
可以通过在应用程序中以编码的方式启动broker,例如:
Java代码
1. BrokerService broker = new BrokerService();
2. broker.addConnector("tcp://localhost:61616");
3. broker.start();
如果需要启动多个broker,那么需要为broker设置一个名字。例如:
Java代码
1. BrokerService broker = new BrokerService();
2. broker.setName("fred");
3. broker.addConnector("tcp://localhost:61616");
4. broker.start();
如果希望在同一个JVM内访问这个broker,那么可以使用VM Transport,URI是:vm://brokerName。关于更多的broker属性,可以参考Apache的官方文档。
此外,也可以通过BrokerFactory来创建broker,例如:
Java代码
1. BrokerService broker = BrokerFactory.createBroker(new URI(someURI));
someURI的可选值如下:
URI scheme Example Description
xbean: xbean:activemq.xml Searches the classpath for an XML document with the given URI (activemq.xml in this case) which will then be used as the Xml Configuration
file: file:foo/bar/activemq.xml Loads the given file (in this example foo/bar/activemq.xml) as the Xml Configuration
broker: broker:tcp://localhost:61616 Uses the Broker Configuration URI to configure the broker

当使用XBean的配置方式的时候,需要指定一个xml配置文件,例如:
Java代码
1. BrokerService broker = BrokerFactory.createBroker(new URI("xbean:com/test/activemq.xml"));
使用Spring的配置方式如下:
Xml代码
1. <bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
2. <property name="config" value="classpath:org/apache/activemq/xbean/activemq.xml" />
3. <property name="start" value="true" />
4. </bean>

2.1.3 Monitoring Broker
2.1.3.1 JMX
在使用JMX监控broker之前,首先要启用broker的JMX监控功能,例如在配置文件中设置useJmx="true",如下:
Xml代码
1. <broker useJmx="true" brokerName="broker1>
2. <managementContext>
3. <managementContext createConnector="true"/>
4. </managementContext>
5. ...
6. </broker>
接下来运行JDK自带的jconsole。在运行了jconsole后,它会弹出对话框来选择需要连接到的agent。如果是在启动broker的主机上运行jconsole,那么ActiveMQ broker会出现在jconsole的Local 标签中。如果要连接到远程的broker,那么可以在Advanced标签中指定JMX URL,以下是一个连接到本机的JMX URL:
service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi
在jconsole的MBeans标签中,可以查看详细信息,也可以执行相应的operation。需要注意的是,在jconsole连接到broker 的时候,并不需要输入用户名和密码,如果这存在潜在的安全问题,那么就需要为JMX Connector配置密码保护(需要使用1.5以上版本的JDK)。
首先要禁止ActiveMQ创建自己的connector,例如:
Xml代码
1. <broker xmlns="http://activemq.org/config/1.0" brokerName="localhost"useJmx="true">
2. <managementContext>
3. <managementContext createConnector="false"/>
4. </managementContext>
5. </broker>
然后在ActiveMQ的conf目录下创建一个访问控制文件和密码文件,如下:
conf/jmx.access:
# The "monitorRole" role has readonly access.
# The "controlRole" role has readwrite access.
monitorRole readonly
controlRole readwrite

conf/jmx.password:
# The "monitorRole" role has password "abc123".
# The "controlRole" role has password "abcd1234".
monitorRole abc123
controlRole abcd1234

然后修改ActiveMQ的bin目录下activemq的启动脚本,查找包含"SUNJMX="的一行如下:
REM set SUNJMX=-Dcom.sun.management.jmxremote.port=1616 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
把它替换成
set SUNJMX=-Dcom.sun.management.jmxremote.port=1616 -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.password.file=%ACTIVEMQ_BASE%/conf/jmx.password -Dcom.sun.management.jmxremote.access.file=%ACTIVEMQ_BASE%/conf/jmx.access
最后重启ActiveMQ和jconsole,这时候需要强制login。如果在启动activemq的过程中出现以下错误,那么需要为这个文件增加访问控制。Windows平台上的具体解决方法请参考如下网址:http://java.sun.com/j2se/1.5.0/docs/guide /management/security-windows.html
Error: Password file read access must be restricted: D:\apache-activemq-5.0.0\bin\../conf/jmx.password

2.1.3.2 Web Console
Web Console被集成到了ActiveMQ的二进制发布包中,因此缺省访问http://localhost:8161/admin即可访问Web Console。
在配置文件中,可以通过修改nioConnector的port属性来修改Web console的缺省端口:
Xml代码
1. <jetty xmlns="http://mortbay.com/schemas/jetty/1.0">
2. <connectors>
3. <nioConnector port="8161" />
4. </connectors>
5. ...
6. </jetty>
出于安全性或者可靠性的考虑,Web Console 可以被部署到不同于ActiveMQ的进程中。例如把activemq-web-console.war部署到一个单独的web容器中(Tomcat,Jetty等)。在ActiveMQ5.0的二进制发布包中不包含activemq-web-console.war,因此需要下载 ActiveMQ的源码,然后进入到${activemq.base}/src/activemq-web-console目录中执行mvn instanll。如果一切正常,那么缺省会在${activemq.base}/src/activemq-web-console/target目录中生成activemq-web-console-5.0.0.war。然后将activemq-web-console-5.0.0.war拷贝到 Tomcat的webapps目录中,并重命名成activemq-web-console.war。
需要注意的是,要将activemq-all-5.0.0.jar拷贝到WEB-INF\lib目录中(可能还需要拷贝jms.jar)。还要为Tomcat设置以下五个系统属性(修改catalina.bat文件):
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.type="properties"
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jms.url="tcp://localhost:61616"
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jmx.url="service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jmx.role=""
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jmx.password=""
如果JMX没有配置密码保护,那么webconsole.jmx.role和webconsole.jmx.password设置成""即可。如果 broker被配置成了Master/Slave模式,那么可以配置成使用failover transport,例如:
-Dwebconsole.jms.url=failover:(tcp://serverA:61616,tcp://serverB:61616)
顺便说一下,由于webconsole.type 属性是properties,因此实际上起作用的Web Console的配置文件是WEB-INF/ webconsole-properties.xml。最后启动被监控的ActiveMQ,访问http://localhost:8080 /activemq-web-console/,查看显示是否正常。

2.1.3.3 Advisory Message
ActiveMQ 支持Advisory Messages,它允许你通过标准的JMS 消息来监控系统。目前的Advisory Messages支持:
• consumers, producers and connections starting and stopping
• temporary destinations being created and destroyed
• messages expiring on topics and queues
• brokers sending messages to destinations with no consumers.
• connections starting and stopping
Advisory Messages可以被想象成某种的管理通道,通过它你可以得到关于JMS provider、producers、consumers和destinations的信息。Advisory topics都使用ActiveMQ.Advisory.这个前缀,以下是目前支持的topics:
Client based advisories
Advisory Topics Description
ActiveMQ.Advisory.Connection Connection start & stop messages
ActiveMQ.Advisory.Producer.Queue Producer start & stop messages on a Queue
ActiveMQ.Advisory.Producer.Topic Producer start & stop messages on a Topic
ActiveMQ.Advisory.Consumer.Queue Consumer start & stop messages on a Queue
ActiveMQ.Advisory.Consumer.Topic Consumer start & stop messages on a Topic

在消费者启动/停止的Advisory Messages的消息头中有个consumerCount属性,他用来指明目前desination上活跃的consumer的数量。
Destination and Message based advisories
Advisory Topics Description
ActiveMQ.Advisory.Queue Queue create & destroy
ActiveMQ.Advisory.Topic Topic create & destroy
ActiveMQ.Advisory.TempQueue Temporary Queue create & destroy
ActiveMQ.Advisory.TempTopic Temporary Topic create & destroy
ActiveMQ.Advisory.Expired.Queue Expired messages on a Queue
ActiveMQ.Advisory.Expired.Topic Expired messages on a Topic
ActiveMQ.Advisory.NoConsumer.Queue No consumer is available to process messages being sent on a Queue
ActiveMQ.Advisory.NoConsumer.Topic No consumer is available to process messages being sent on a Topic
以上的这些destnations都可以用来作为前缀,在其后面追加其它的重要信息,例如topic、queue、clientID、 producderID和consumerID等。这令你可以利用Wildcards 和 Selectors 来过滤Advisory Messages(关于Wildcard和Selector会在稍后介绍)。
例如,如果你希望订阅FOO.BAR这个queue上Consumer的start/stop的消息,那么可以订阅 ActiveMQ.Advisory.Consumer.Queue.FOO.BAR;如果希望订阅所有queue上的start/stop消息,那么可以订阅ActiveMQ.Advisory.Consumer.Queue.>;如果希望订阅所有queue或者topic上的 start/stop消息,那么可以订阅ActiveMQ.Advisory.Consumer. >。
org.apache.activemq.advisory.AdvisorySupport类上有如下的helper methods,用来在程序中得到advisory destination objects。
Java代码
1. AdvisorySupport.getConsumerAdvisoryTopic()
2. AdvisorySupport.getProducerAdvisoryTopic()
3. AdvisorySupport.getDestinationAdvisoryTopic()
4. AdvisorySupport.getExpiredTopicMessageAdvisoryTopic()
5. AdvisorySupport.getExpiredQueueMessageAdvisoryTopic()
6. AdvisorySupport.getNoTopicConsumersAdvisoryTopic()
7. AdvisorySupport.getNoQueueConsumersAdvisoryTopic()
以下是段使用Advisory Messages的程序代码:
Java代码
1. Destination advisoryDestination = AdvisorySupport.getProducerAdvisoryTopic(destination)
2. MessageConsumer consumer = session.createConsumer(advisoryDestination);
3. consumer.setMessageListener(this);
4. ...
5. public void onMessage(Message msg){
6. if (msg instanceof ActiveMQMessage){
7. try {
8. ActiveMQMessage aMsg = (ActiveMQMessage)msg;
9. ProducerInfo prod = (ProducerInfo) aMsg.getDataStructure();
10. } catch (JMSException e) {
11. log.error("Failed to process message: " + msg);
12. }
13. }
14. }

2.1.3.4 Command Agent
在介绍Command Agent前首先简要介绍一下XMPP(Jabber)协议,XMPP是一种基于XML的即时通信协议,它由Jabber软件基金会开发。在配置文件中通过增加transportConnector来支持XMPP协议:
Xml代码
1. <broker xmlns="http://activemq.org/config/1.0">
2. <transportConnectors>
3. ...
4. <transportConnector name="xmpp" uri="xmpp://localhost:61222"/>
5. </transportConnectors>
6. </broker>
ActiveMQ提供了ActiveMQ messages和XMPP之间的双向桥接:
• 如果客户加入了一个聊天室,那么这个聊天室的名字会被映射到一个JMS topic。
• 尝试在聊天室内发送消息会导致一个JMS消息被发送到这个topic。
• 呆在一个聊天室中意味着这将保持一个对相应JMS topic的订阅。因此发送到这个topic的JMS消息也会被发送到聊天室。
推荐XMPP客户端Spark(http://www.igniterealtime.org/)。
从4.2版本起,ActiveMQ支持Command Agent。在配置文件中,通过设置commandAgent来启用Command Agent:
Xml代码
1. <beans>
2. <broker useJmx="true" xmlns="http://activemq.org/config/1.0">
3. ...
4. </broker>
5. <commandAgent xmlns="http://activemq.org/config/1.0"/>
6. </beans>
启用了Command Agent的broker上会有一个来自Command Agent的连接,它同时订阅topic: ActiveMQ.Agent。在你启动XMPP客户端,加入到ActiveMQ.Agent聊天室后,就可以同broker进行交谈了。通过在XMPP 客户端中键入help,可以得到帮助信息。
需要注意的是,ActiveMQ5.0版本有个小bug,如果broker没有采用缺省的用户名和密码,那么Command Agent便无法正常启动。Apache官方文档说,此bug已经被修正,预定在5.2.0版本上体现。修改方式如下:
Xml代码
1. <commandAgent xmlns="http://activemq.org/config/1.0" brokerUser="user" brokerPassword="passward"/>

2.1.3.5 Visualization plugin
ActiveMQ支持以broker插件的形式生成DOT文件(可以用agrviewer来查看),以图表的方式描述connections、sessions、producers、consumers、destinations等信息。配置方式如下:
Xml代码
1. <broker xmlns="http://activemq.org/config/1.0" brokerName="localhost" useJmx="true">
2. ...
3. <plugins>
4. <connectionDotFilePlugin file="connection.dot"/>
5. <destinationDotFilePlugin file="destination.dot"/>
6. </plugins>
7. </broker>
需要注意的是,笔者认为ActiveMQ5.0版本的Visualization Plugin尚不稳定,存在诸多问题。例如:如果使用connectionDotFilePlugin,那么brokerName必须是 localhost;如果使用destinationDotFilePlugin可能会导致ArrayStoreException。
2.2 Transport
ActiveMQ目前支持的transport有:VM Transport、TCP Transport、SSL Transport、Peer Transport、UDP Transport、Multicast Transport、HTTP and HTTPS Transport、Failover Transport、Fanout Transport、Discovery Transport、ZeroConf Transport等。以下简单介绍其中的几种,更多请参考Apache官方文档。

2.2.1 VM Transport
VM transport允许在VM内部通信,从而避免了网络传输的开销。这时候采用的连接不是socket连接,而是直接地方法调用。 第一个创建VM 连接的客户会启动一个embed VM broker,接下来所有使用相同的broker name的VM连接都会使用这个broker。当这个broker上所有的连接都关闭的时候,这个broker也会自动关闭。
以下是配置语法:
vm://brokerName?transportOptions
例如:vm://broker1?marshal=false&broker.persistent=false
Transport Options的可选值如下:
Option Name Default Value Description
Marshal false If true, forces each command sent over the transport to be marshlled and unmarshlled using a WireFormat
wireFormat default The name of the WireFormat to use
wireFormat.* All the properties with this prefix are used to configure the wireFormat
create true If the broker should be created on demand if it does not allready exist. Only supported in ActiveMQ 4.1
broker.* All the properties with this prefix are used to configure the broker. See Configuring Wire Formats for more information

以下是高级配置语法:
vm:(broker:(tcp://localhost)?brokerOptions)?transportOptions
vm:broker:(tcp://localhost)?brokerOptions
例如:vm:(broker:(tcp://localhost:6000)?persistent=false)?marshal=false
Transport Options的可选值如下:
Option Name Default Value Description
marshal false If true, forces each command sent over the transport to be marshlled and unmarshlled using a WireFormat
wireFormat default The name of the WireFormat to use
wireFormat.* All the properties with this prefix are used to configure the wireFormat

使用配置文件的配置语法:
vm://localhost?brokerConfig=xbean:activemq.xml
例如:vm:// localhost?brokerConfig=xbean:com/test/activemq.xml

使用Spring的配置:
Xml代码
1. <bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
2. <property name="config" value="classpath:org/apache/activemq/xbean/activemq.xml" />
3. <property name="start" value="true" />
4. </bean>
5.
6. <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory" depends-on="broker">
7. <property name="brokerURL" value="vm://localhost"/>
8. </bean>
如果persistent是true,那么ActiveMQ会在当前目录下创建一个缺省值是activemq-data的目录用于持久化保存数据。需要注意的是,如果程序中启动了多个不同名字的VM broker,那么可能会有如下警告:Failed to start jmx connector: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.NameAlreadyBoundException…可以通过在transportOptions中追加 broker.useJmx=false来禁用JMX来避免这个警告。

2.2.2 TCP Transport
TCP transport 允许客户端通过TCP socket连接到远程的broker。以下是配置语法:
tcp://hostname:port?transportOptions
Transport Options的可选值如下:
Option Name Default Value Description
minmumWireFormatVersion 0 The minimum version wireformat that is allowed
trace false Causes all commands that are sent over the transport to be logged
useLocalHost true When true, it causes the local machines name to resolve to "localhost".
socketBufferSize 64 * 1024 Sets the socket buffer size in bytes
soTimeout 0 sets the socket timeout in milliseconds
connectionTimeout 30000 A non-zero value specifies the connection timeout in milliseconds. A zero value means wait forever for the connection to be established. Negative values are ignored.
wireFormat default The name of the WireFormat to use
wireFormat.* All the properties with this prefix are used to configure the wireFormat. See Configuring Wire Formats for more information
例如:tcp://localhost:61616?trace=false

2.2.3 Failover Transport
Failover Transport是一种重新连接的机制,它工作于其它transport的上层,用于建立可靠的传输。它的配置语法允许制定任意多个复合的URI。 Failover transport会自动选择其中的一个URI来尝试建立连接。如果没有成功,那么会选择一个其它的URI来建立一个新的连接。以下是配置语法:
failover:(uri1,...,uriN)?transportOptions
failover:uri1,...,uriN
Transport Options的可选值如下:
Option Name Default Value Description
initialReconnectDelay 10 How long to wait before the first reconnect attempt (in ms)
maxReconnectDelay 30000 The maximum amount of time we ever wait between reconnect attempts (in ms)
useExponentialBackOff true Should an exponential backoff be used between reconnect attempts
backOffMultiplier 2 The exponent used in the exponential backoff attempts
maxReconnectAttempts 0 If not 0, then this is the maximum number of reconnect attempts before an error is sent back to the client
randomize true use a random algorithm to choose the URI to use for reconnect from the list provided
backup false initialize and hold a second transport connection - to enable fast failover
例如:failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100

2.2.4 Discovery transport
Discovery transport是可靠的tranport。它使用Discovery transport来定位用来连接的URI列表。以下是配置语法:
discovery:(discoveryAgentURI)?transportOptions
discovery:discoveryAgentURI
Transport Options的可选值如下:
Option Name Default Value Description
initialReconnectDelay 10 How long to wait before the first reconnect attempt
maxReconnectDelay 30000 The maximum amount of time we ever wait between reconnect attempts
useExponentialBackOff true Should an exponential backoff be used btween reconnect attempts
backOffMultiplier 2 The exponent used in the exponential backoff attempts
maxReconnectAttempts 0 If not 0, then this is the maximum number of reconnect attempts before an error is sent back to the client
例如:discovery:(multicast://default)?initialReconnectDelay=100
为了使用Discovery来发现broker,需要为broker启用discovery agent。 以下是XML配置文件中的一个例子:
Xml代码
1. <broker name="foo">
2. <transportConnectors>
3. <transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/>
4. </transportConnectors>
5. ...
6. </broker>
在使用Failover Transport或Discovery transport等能够自动重连的transport的时候,需要注意的是:设想有两个broker,它们都启用AMQ Message Store作为持久化存储,有一个producer和一个consumer连接到某个queue。当因其中一个broker失效时而切换到另一个 broker的时候,如果失效的broker的queue中还有未被consumer消费的消息,那么这个queue里的消息仍然滞留在失效broker 的中,直到失效的broker被修复并重新切换回这个被修复的broker后,之前被保留的消息才会被consumer消费掉。如果被处理的消息有时序限制,那么应用程序就需要处理这个问题。另外也可以通过ActiveMQ集群来解决这个问题。
在transport重连的时候,可以在connection上注册TransportListener来获得回调,例如:
Java代码
1. (ActiveMQConnection)connection).addTransportListener(new TransportListener() {
2. public void onCommand(Object cmd) {
3. }
4.
5. public void onException(IOException exp) {
6. }
7.
8. public void transportInterupted() {
9. // The transport has suffered an interruption from which it hopes to recover.
10. }
11.
12. public void transportResumed() {
13. // The transport has resumed after an interruption.
14. }
15. });
2.3 Persistence
2.3.1 AMQ Message Store
AMQ Message Store是ActiveMQ5.0缺 省的持久化存储。Message commands被保存到transactional journal(由rolling data logs组成)。Messages被保存到data logs中,同时被reference store进行索引以提高存取速度。Date logs由一些单独的data log文件组成,缺省的文件大小是32M,如果某个消息的大小超过了data log文件的大小,那么可以修改配置以增加data log文件的大小。如果某个data log文件中所有的消息都被成功消费了,那么这个data log文件将会被标记,以便在下一轮的清理中被删除或者归档。以下是其配置的一个例子:
Xml代码
1. <broker brokerName="broker" persistent="true" useShutdownHook="false">
2. <persistenceAdapter>
3. <amqPersistenceAdapter directory="${activemq.base}/data" maxFileLength="32mb"/>
4. </persistenceAdapter>
5. </broker>
Property name Default value Comments
directory activemq-data the path to the directory to use to store the message store data and log files
useNIO true use NIO to write messages to the data logs
syncOnWrite false sync every write to disk
maxFileLength 32mb a hint to set the maximum size of the message data logs
persistentIndex true use a persistent index for the message logs. If this is false, an in-memory structure is maintained
maxCheckpointMessageAddSize 4kb the maximum number of messages to keep in a transaction before automatically committing
cleanupInterval 30000 time (ms) before checking for a discarding/moving message data logs that are no longer used
indexBinSize 1024 default number of bins used by the index. The bigger the bin size - the better the relative performance of the index
indexKeySize 96 the size of the index key - the key is the message id
indexPageSize 16kb the size of the index page - the bigger the page - the better the write performance of the index
directoryArchive archive the path to the directory to use to store discarded data logs
archiveDataLogs false if true data logs are moved to the archive directory instead of being deleted

2.3.2 Kaha Persistence
Kaha Persistence 是一个专门针对消息持久化的解决方案。它对典型的消息使用模式进行了优化。在Kaha中,数据被追加到data logs中。当不再需要log文件中的数据的时候,log文件会被丢弃。以下是其配置的一个例子:
Xml代码
1. <broker brokerName="broker" persistent="true" useShutdownHook="false">
2. <persistenceAdapter>
3. <kahaPersistenceAdapter directory="activemq-data" maxDataFileLength="33554432"/>
4. </persistenceAdapter>
5. </broker>

2.3.3 JDBC Persistence
目前支持的数据库有Apache Derby, Axion, DB2, HSQL, Informix, MaxDB, MySQL, Oracle, Postgresql, SQLServer, Sybase。
如果你使用的数据库不被支持,那么可以调整StatementProvider 来保证使用正确的SQL方言(flavour of SQL)。通常绝大多数数据库支持以下adaptor:
• org.activemq.store.jdbc.adapter.BlobJDBCAdapter
• org.activemq.store.jdbc.adapter.BytesJDBCAdapter
• org.activemq.store.jdbc.adapter.DefaultJDBCAdapter
• org.activemq.store.jdbc.adapter.ImageJDBCAdapter
也可以在配置文件中直接指定JDBC adaptor,例如:
Xml代码
1. <jdbcPersistenceAdapter adapterClass="org.apache.activemq.store.jdbc.adapter.ImageBasedJDBCAdaptor"/>
以下是其配置的一个例子:

Xml代码
1. <persistence>
2. <jdbcPersistence dataSourceRef=" mysql-ds"/>
3. </persistence>
4.
5. <bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
6. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
7. <property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/>
8. <property name="username" value="activemq"/>
9. <property name="password" value="activemq"/>
10. <property name="poolPreparedStatements" value="true"/>
11. </bean>
需要注意的是,如果使用MySQL,那么需要设置relaxAutoCommit 标志为true。

2.3.4 Disable Persistence
以下是其配置的一个例子:
Xml代码
1. <broker persistent="false">
2. </broker>
2.4 Security
ActiveMQ支持可插拔的安全机制,用以在不同的provider之间切换。
2.4.1 Simple Authentication Plugin
Simple Authentication Plugin适用于简单的认证需求,或者用于建立测试环境。它允许在XML配置文件中指定用户、用户组和密码等信息。以下是ActiveMQ配置的一个例子:
Xml代码
1. <plugins>
2. ...
3. <simpleAuthenticationPlugin>
4. <users>
5. <authenticationUser username="system" password="manager" groups="users,admins"/>
6. <authenticationUser username="user" password="password" groups="users"/>
7. <authenticationUser username="guest" password="password" groups="guests"/>
8. </users>
9. </simpleAuthenticationPlugin>
10. </plugins>

2.4.2 JAAS Authentication Plugin
JAAS Authentication Plugin依赖标准的JAAS机制来实现认证。通常情况下,你需要通过设置java.security.auth.login.config系统属性来配置login modules的配置文件。如果没有指定这个系统属性,那么JAAS Authentication Plugin会缺省使用login.config作为文件名。以下是一个login.config文件的例子:
activemq-domain {
org.apache.activemq.jaas.PropertiesLoginModule required debug=true org.apache.activemq.jaas.properties.user="users.properties" org.apache.activemq.jaas.properties.group="groups.properties";
};
这个login.config文件中设置了两个属性:org.apache.activemq.jaas.properties.user和 org.apache.activemq.jaas.properties.group分别用来指向user.properties和 group.properties文件。需要注意的是,PropertiesLoginModule使用本地文件的查找方式,而且查找时采用的base directory是login.config文件所在的目录。因此这个login.config说明user.properties和 group.properties文件存放在跟login.config文件相同的目录里。
以下是ActiveMQ配置的一个例子:
Xml代码
1. <plugins>
2. ...
3. <jaasAuthenticationPlugin configuration="activemq-domain" />
4. </plugins>
基于以上的配置,在JAAS的LoginContext中会使用activemq-domain中配置的PropertiesLoginModule来进行登陆。
ActiveMQ JAAS还支持LDAPLoginModule、CertificateLoginModule、TextFileCertificateLoginModule等login module。

2.4.3 Custom Authentication Implementation
可以通过编码的方式为ActiveMQ增加认证功能。例如编写一个类继承自XBeanBrokerService。
Java代码
1. package com.yourpackage;
2.
3. import java.net.URI;
4. import java.util.HashMap;
5. import java.util.Map;
6.
7. import org.apache.activemq.broker.Broker;
8. import org.apache.activemq.broker.BrokerFactory;
9. import org.apache.activemq.broker.BrokerService;
10. import org.apache.activemq.security.SimpleAuthenticationBroker;
11. import org.apache.activemq.xbean.XBeanBrokerService;
12.
13. public class SimpleAuthBroker extends XBeanBrokerService {
14. //
15. private String user;
16. private String password;
17.
18. @SuppressWarnings("unchecked")
19. protected Broker addInterceptors(Broker broker) throws Exception {
20. broker = super.addInterceptors(broker);
21. Map passwords = new HashMap();
22. passwords.put(getUser(), getPassword());
23. broker = new SimpleAuthenticationBroker(broker, passwords, new HashMap());
24. return broker;
25. }
26.
27. public String getUser() {
28. return user;
29. }
30.
31. public void setUser(String user) {
32. this.user = user;
33. }
34.
35. public String getPassword() {
36. return password;
37. }
38.
39. public void setPassword(String password) {
40. this.password = password;
41. }
42. }
以下是ActiveMQ配置文件的一个例子:
Xml代码
1. <beans>
2. …
3. <auth:SimpleAuthBroker
4. xmlns:auth="java://com.yourpackage"
5. xmlns="http://activemq.org/config/1.0" brokerName="SimpleAuthBroker1" user="user" password="password" useJmx="true">
6.
7. <transportConnectors>
8. <transportConnector uri="tcp://localhost:61616"/>
9. </transportConnectors>
10. </auth:SimpleAuthBroker>
11. …
12. </beans>
在这个配置文件中增加了一个namespace auth,用于指向之前编写的哪个类。同时为SimpleAuthBroker注入了两个属性值user和password,因此在被 SimpleAuthBroker改写的addInterceptors方法里,可以使用这两个属性进行认证了。ActiveMQ提供的 SimpleAuthenticationBroker类继承自BrokerFilter(可以简单的看成是Broker的Adaptor),它的构造函数中的两个Map分别是userPasswords和userGroups。 SimpleAuthenticationBroker在 addConnection方法中使用userPasswords进行认证,同时会把userGroups的信息保存到 ConnectionContext中 。

2.4.4 Authorization Plugin
可以通过Authorization Plugin为认证后的用户授权,以下ActiveMQ配置文件的一个例子:
Xml代码
1. <plugins>
2. <jaasAuthenticationPlugin configuration="activemq-domain"/>
3.
4. <authorizationPlugin>
5. <map>
6. <authorizationMap>
7. <authorizationEntries>
8. <authorizationEntry queue=">" read="admins" write="admins" admin="admins" />
9. <authorizationEntry queue="USERS.>" read="users" write="users" admin="users" />
10. <authorizationEntry queue="GUEST.>" read="guests" write="guests,users" admin="guests,users" />
11.
12. <authorizationEntry topic=">" read="admins" write="admins" admin="admins" />
13. <authorizationEntry topic="USERS.>" read="users" write="users" admin="users" />
14. <authorizationEntry topic="GUEST.>" read="guests" write="guests,users" admin="guests,users" />
15.
16. <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users" write="guests,users" admin="guests,users"/>
17. </authorizationEntries>
18. </authorizationMap>
19. </map>
20. </authorizationPlugin>
21. </plugins>
2.5 Clustering
ActiveMQ从多种不同的方面提供了集群的支持。
2.5.1 Queue consumer clusters
ActiveMQ支持订阅同一个queue的consumers上的集群。如果一个consumer失效,那么所有未被确认(unacknowledged)的消息都会被发送到这个queue上其它的consumers。如果某个consumer的处理速度比其它 consumers更快,那么这个consumer就会消费更多的消息。
需要注意的是,笔者发现AcitveMQ5.0版本的Queue consumer clusters存在一个bug:采用AMQ Message Store,运行一个producer,两个consumer,并采用如下的配置文件:
Xml代码
1. <beans>
2. <broker xmlns="http://activemq.org/config/1.0" brokerName="BugBroker1" useJmx="true">
3.
4. <transportConnectors>
5. <transportConnector uri="tcp://localhost:61616"/>
6. </transportConnectors>
7.
8. <persistenceAdapter>
9. <amqPersistenceAdapter directory="activemq-data/BugBroker1" maxFileLength="32mb"/>
10. </persistenceAdapter>
11.
12. </broker>
13. </beans>
那么经过一段时间后可能会报出如下错误:
ERROR [ActiveMQ Transport: tcp:///127.0.0.1:1843 - RecoveryListenerAdapter.java:58 - RecoveryListenerAdapter] Message id ID:versus-1837-1203915536609-0:2:1:1:419 could not be recovered from the data store!
Apache官方文档说,此bug已经被修正,预定在5.1.0版本上体现。

2.5.2 Broker clusters
一个常见的场景是有多个JMS broker,有一个客户连接到其中一个broker。如果这个broker失效,那么客户会自动重新连接到其它的broker。在ActiveMQ中使用failover:// 协议来实现这个功能。ActiveMQ3.x版本的reliable://协议已经变更为failover://。
如果某个网络上有多个brokers而且客户使用静态发现(使用Static Transport或Failover Transport)或动态发现(使用Discovery Transport),那么客户可以容易地在某个broker失效的情况下切换到其它的brokers。然而,stand alone brokers并不了解其它brokers上的consumers,也就是说如果某个broker上没有consumers,那么这个broker上的消息可能会因得不到处理而积压起来。目前的解决方案是使用Network of brokers,以便在broker之间存储转发消息。ActiveMQ在未来会有更好的特性,用来在客户端处理这个问题。
从ActiveMQ1.1版本起,ActiveMQ支持networks of brokers。它支持分布式的queues和topics。一个broker会相同对待所有的订阅(subscription):不管他们是来自本地的客户连接,还是来自远程broker,它都会递送有关的消息拷贝到每个订阅。远程broker得到这个消息拷贝后,会依次把它递送到其内部的本地连接上。有两种方式配置Network of brokers,一种是使用static transport,如下:
Xml代码
1. <broker brokerName="receiver" persistent="false" useJmx="false">
2. <transportConnectors>
3. <transportConnector uri="tcp://localhost:62002"/>
4. </transportConnectors>
5. <networkConnectors>
6. <networkConnector uri="static:( tcp://localhost:61616,tcp://remotehost:61616)"/>
7. </networkConnectors>
8. …
9. </broker>
另外一种是使用multicast discovery,如下:
Xml代码
1. <broker name="sender" persistent="false" useJmx="false">
2. <transportConnectors>
3. <transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/>
4. </transportConnectors>
5. <networkConnectors>
6. <networkConnector uri="multicast://default"/>
7. </networkConnectors>
8. ...
9. </broker>
Network Connector有以下属性:
Property Default Value Description
name bridge name of the network - for more than one network connector between the same two brokers - use different names
dynamicOnly false if true, only forward messages if a consumer is active on the connected broker
decreaseNetworkConsumerPriority false decrease the priority for dispatching to a Queue consumer the further away it is (in network hops) from the producer
networkTTL 1 the number of brokers in the network that messages and subscriptions can pass through
conduitSubscriptions true multiple consumers subscribing to the same destination are treated as one consumer by the network
excludedDestinations empty destinations matching this list won't be forwarded across the network
dynamicallyIncludedDestinations empty destinations that match this list will be forwarded across the network n.b. an empty list means all destinations not in the excluded list will be forwarded
staticallyIncludedDestinations empty destinations that match will always be passed across the network - even if no consumers have ever registered an interest
duplex false if true, a network connection will be used to both produce AND Consume messages. This is useful for hub and spoke scenarios when the hub is behind a firewall etc.
关于conduitSubscriptions属性,这里稍稍说明一下。设想有两个brokers,分别是brokerA和brokerB,它们之间用 forwarding bridge连接。有一个consumer连接到brokerA并订阅queue:Q.TEST。有两个consumers连接到brokerB,也是订阅queue:Q.TEST。这三个consumers有相同的优先级。然后启动一个producer,它发送了30条消息到brokerA。如果 conduitSubscriptions=true,那么brokerA上的consumer会得到15条消息,另外15条消息会发送给brokerB。此时负载并不均衡,因为此时brokerA将brokerB上的两个consumers视为一个;如果 conduitSubscriptions=false,那么每个consumer上都会收到10条消息。以下是关于NetworkConnector属性的一个例子:
Xml代码
1. <networkConnectors>
2. <networkConnector uri="static://(tcp://localhost:61617)"
3. name="bridge" dynamicOnly="false" conduitSubscriptions="true"
4. decreaseNetworkConsumerPriority="false">
5. <excludedDestinations>
6. <queue physicalName="exclude.test.foo"/>
7. <topic physicalName="exclude.test.bar"/>
8. </excludedDestinations>
9. <dynamicallyIncludedDestinations>
10. <queue physicalName="include.test.foo"/>
11. <topic physicalName="include.test.bar"/>
12. </dynamicallyIncludedDestinations>
13. <staticallyIncludedDestinations>
14. <queue physicalName="always.include.queue"/>
15. <topic physicalName="always.include.topic"/>
16. </staticallyIncludedDestinations>
17. </networkConnector>
18. </networkConnectors>

2.5.3 Master Slave
在一个网络内运行多个brokers或者stand alone brokers时存在一个问题,这就是消息在物理上只被一个broker持有,因此当某个broker失效,那么你只能等待直到它重启后,这个 broker上的消息才能够被继续发送(如果没有设置持久化,那么在这种情况下,消息将会丢失)。Master Slave 背后的想法是,消息被复制到slave broker,因此即使master broker遇到了像硬件故障之类的错误,你也可以立即切换到slave broker而不丢失任何消息。
Master Slave是目前ActiveMQ推荐的高可靠性和容错的解决方案。以下是几种不同的类型:
Master Slave Type Requirements Pros Cons
Pure Master Slave None No central point of failure Requires manual restart to bring back a failed master and can only support 1 slave
Shared File System Master Slave A Shared File system such as a SAN Run as many slaves as required. Automatic recovery of old masters Requires shared file system
JDBC Master Slave A Shared database Run as many slaves as required. Automatic recovery of old masters Requires a shared database. Also relatively slow as it cannot use the high performance journal

2.5.3.1 Pure Master Slave
Pure Master Slave的工作方式如下:
• Slave broker消费master broker上所有的消息状态,例如消息、确认和事务状态等。只要slave broker连接到了master broker,它不会(也不被允许)启动任何network connectors或者transport connectors,所以唯一的目的就是复制master broker的状态。
• Master broker只有在消息成功被复制到slave broker之后才会响应客户。例如,客户的commit请求只有在master broker和slave broker都处理完毕commit请求之后才会结束。
• 当master broker失效的时候,slave broker有两种选择,一种是slave broker启动所有的network connectors和transport connectors,这允许客户端切换到slave broker;另外一种是slave broker停止。这种情况下,slave broker只是复制了master broker的状态。
• 客户应该使用failover transport并且应该首先尝试连接master broker。例如:
failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false
设置randomize为false就可以让客户总是首先尝试连接master broker(slave broker并不会接受任何连接,直到它成为了master broker)。
Pure Master Slave具有以下限制:
• 只能有一个slave broker连接到master broker。
• 在因master broker失效而导致slave broker成为master之后,之前的master broker只有在当前的master broker(原slave broker)停止后才能重新生效。
• Master broker失效后而切换到slave broker后,最安全的恢复master broker的方式是人工处理。首先要停止slave broker(这意味着所有的客户也要停止)。然后把slave broker的数据目录中所有的数据拷贝到master broker的数据目录中。然后重启master broker和slave broker。
Master broker不需要特殊的配置。Slave broker需要进行以下配置
Xml代码
1. <broker masterConnectorURI="tcp://masterhost:62001" shutdownOnMasterFailure="false">
2. ...
3. <transportConnectors>
4. <transportConnector uri="tcp://slavehost:61616"/>
5. </transportConnectors>
6. </broker>
其中的masterConnectorURI用于指向master broker,shutdownOnMasterFailure用于指定slave broker在master broker失效的时候是否需要停止。此外,也可以使用如下配置:
Xml代码
1. <broker brokerName="slave" useJmx="false" deleteAllMessagesOnStartup="true" xmlns="http://activemq.org/config/1.0">
2. ...
3. <services>
4. <masterConnector remoteURI= "tcp://localhost:62001" userName="user" password="password"/>
5. </services>
6. </broker>
需要注意的是,笔者认为ActiveMQ5.0版本的Pure Master Slave仍然不够稳定。

2.5.3.2 Shared File System Master Slave
如果你使用SAN或者共享文件系统,那么你可以使用Shared File System Master Slave。基本上,你可以运行多个broker,这些broker共享数据目录。当第一个broker得到文件上的排他锁之后,其它的broker便会在循环中等待获得这把锁。客户端使用failover transport来连接到可用的broker。当master broker失效的时候会释放这把锁,这时候其中一个slave broker会得到这把锁从而成为master broker。以下是ActiveMQ配置的一个例子:
Xml代码
1. <broker useJmx="false" xmlns="http://activemq.org/config/1.0">
2. <persistenceAdapter>
3. <journaledJDBC dataDirectory="/sharedFileSystem/broker"/>
4. </persistenceAdapter>
5. …
6. </broker>

2.5.3.3 JDBC Master Slave
JDBC Master Slave的工作原理跟Shared File System Master Slave类似,只是采用了数据库作为持久化存储。以下是ActiveMQ配置的一个例子:
Xml代码
1. <beans>
2. <broker xmlns="http://activemq.org/config/1.0" brokerName="JdbcMasterBroker">
3. ...
4. <persistenceAdapter>
5. <jdbcPersistenceAdapter dataSource="#mysql-ds"/>
6. </persistenceAdapter>
7.
8. </broker>
9.
10. <bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
11. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
12. <property name="url" value="jdbc:mysql://localhost:3306/test?relaxAutoCommit=true"/>
13. <property name="username" value="username"/>
14. <property name="password" value="passward"/>
15. <property name="poolPreparedStatements" value="true"/>
16. </bean>
17. </beans>
需要注意的是,如果你使用MySQL数据库,需要首先执行以下三条语句:(Apache官方文档说,此bug已经被修正,预定在5.1.0版本上体现)
Sql代码
1. ALTER TABLE activemq_acks ENGINE = InnoDB;
2. ALTER TABLE activemq_lock ENGINE = InnoDB;
3. ALTER TABLE activemq_msgs ENGINE = InnoDB;
2.6 Features
ActiveMQ包含了很多功能强大的特性,下面简要介绍其中的几个。
2.6.1 Exclusive Consumer
Queue中的消息是按照顺序被分发到consumers的。然而,当你有多个consumers同时从相同的queue中提取消息时,你将失去这个保证。因为这些消息是被多个线程并发的处理。有的时候,保证消息按照顺序处理是很重要的。例如,你可能不希望在插入订单操作结束之前执行更新这个订单的操作。
ActiveMQ从4.x版本起开始支持Exclusive Consumer (或者说Exclusive Queues)。 Broker会从多个consumers中挑选一个consumer来处理queue中所有的消息,从而保证了消息的有序处理。如果这个consumer 失效,那么broker会自动切换到其它的consumer。
可以通过Destination Options 来创建一个Exclusive Consumer,如下:
Java代码
1. queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true");
2. consumer = session.createConsumer(queue);
顺便说一下,可以给consumer设置优先级,以便针对网络情况(如network hops)进行优化,如下:
Java代码
1. queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true &consumer.priority=10");

2.6.2 Message Groups
用Apache官方文档的话说,Message Groups rock!它是Exclusive Consumer功能的增强。逻辑上,Message Groups 可以看成是一种并发的Exclusive Consumer。跟所有的消息都由唯一的consumer处理不同,JMS 消息属性JMSXGroupID 被用来区分message group。Message Groups特性保证所有具有相同JMSXGroupID 的消息会被分发到相同的consumer(只要这个consumer保持active)。另外一方面,Message Groups特性也是一种负载均衡的机制。
在一个消息被分发到consumer之前,broker首先检查消息JMSXGroupID属性。如果存在,那么broker 会检查是否有某个consumer拥有这个message group。如果没有,那么broker会选择一个consumer,并将它关联到这个message group。此后,这个consumer会接收这个message group的所有消息,直到:
• Consumer被关闭。
• Message group被关闭。通过发送一个消息,并设置这个消息的JMSXGroupSeq为0。
从4.1版本开始,ActiveMQ支持一个布尔字段JMSXGroupFirstForConsumer 。当某个message group的第一个消息被发送到consumer的时候,这个字段被设置。如果客户使用failover transport连接到broker。在由于网络问题等造成客户重新连接到broker的时候,相同message group的消息可能会被分发到不同与之前的consumer,因此JMSXGroupFirstForConsumer字段也会被重新设置。
以下是使用message groups的例子:
Java代码
1. Mesasge message = session.createTextMessage("<foo>hey</foo>");
2. message.setStringProperty("JMSXGroupID", "IBM_NASDAQ_20/4/05");
3. ...
4. producer.send(message);

2.6.3 JMS Selectors
JMS Selectors用于在订阅中,基于消息属性对进行消息的过滤。JMS Selectors由SQL92语法定义。以下是个Selectors的例子:
Java代码
1. consumer = session.createConsumer(destination, "JMSType = 'car' AND weight > 2500");
在JMS Selectors表达式中,可以使用IN、NOT IN、LIKE等,例如:
LIKE '12%3' ('123' true,'12993' true,'1234' false)
LIKE 'l_se' ('lose' true,'loose' false)
LIKE '\_%' ESCAPE '\' ('_foo' true,'foo' false)
需要注意的是,JMS Selectors表达式中的日期和时间需要使用标准的long型毫秒值。另外表达式中的属性不会自动进行类型转换,例如:
Java代码
1. myMessage.setStringProperty("NumberOfOrders", "2");
"NumberOfOrders > 1" 求值结果是false。关于JMS Selectors的详细文档请参考javax.jms.Message的javadoc。
上一小节介绍的Message Groups虽然可以保证具有相同message group的消息被唯一的consumer顺序处理,但是却不能确定被哪个consumer处理。在某些情况下,Message Groups可以和JMS Selector一起工作,例如:
设想有三个consumers分别是A、B和C。你可以在producer中为消息设置三个message groups分别是"A"、"B"和"C"。然后令consumer A使用"JMXGroupID = 'A'"作为selector。B和C也同理。这样就可以保证message group A的消息只被consumer A处理。需要注意的是,这种做法有以下缺点:
• producer必须知道当前正在运行的consumers,也就是说producer和consumer被耦合到一起。
• 如果某个consumer失效,那么应该被这个consumer消费的消息将会一直被积压在broker上。
2.6.4 Pending Message Limit Strategy
首先简要介绍一下prefetch机制。ActiveMQ通过prefetch机制来提高性能,这意味这客户端的内存里可能会缓存一定数量的消息。缓存消息的数量由prefetch limit来控制。当某个consumer的prefetch buffer已经达到上限,那么broker不会再向consumer分发消息,直到consumer向broker发送消息的确认。可以通过在 ActiveMQConnectionFactory或者ActiveMQConnection上设置ActiveMQPrefetchPolicy对象来配置prefetch policy。也可以通过connection options或者destination options来配置。例如:
tcp://localhost:61616?jms.prefetchPolicy.all=50
tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=1
queue = new ActiveMQQueue("TEST.QUEUE?consumer.prefetchSize=10");
prefetch size的缺省值如下:
• persistent queues (default value: 1000)
• non-persistent queues (default value: 1000)
• persistent topics (default value: 100)
• non-persistent topics (default value: Short.MAX_VALUE -1)
慢消费者会在非持久的topics上导致问题:一旦消息积压起来,会导致broker把大量消息保存在内存中,broker也会因此而变慢。未来 ActiveMQ可能会实现磁盘缓存,但是这也还是会存在性能问题。目前ActiveMQ使用Pending Message Limit Strategy来解决这个问题。除了prefetch buffer之外,你还要配置缓存消息的上限,超过这个上限后,新消息到来时会丢弃旧消息。通过在配置文件的destination map中配置PendingMessageLimitStrategy,可以为不用的topic namespace配置不同的策略。目前有以下两种:
• ConstantPendingMessageLimitStrategy。这个策略使用常量限制。
例如:<constantPendingMessageLimitStrategy limit="50"/>
• PrefetchRatePendingMessageLimitStrategy。这个策略使用prefetch size的倍数限制。
例如:<prefetchRatePendingMessageLimitStrategy multiplier="2.5"/>
在以上两种方式中,如果设置0意味着除了prefetch之外不再缓存消息;如果设置-1意味着禁止丢弃消息。
此外,你还可以配置消息的丢弃策略,目前有以下两种:
• oldestMessageEvictionStrategy。这个策略丢弃最旧的消息。
• oldestMessageWithLowestPriorityEvictionStrategy。这个策略丢弃最旧的,而且具有最低优先级的消息。
以下是个ActiveMQ配置文件的例子:
Xml代码
1. <broker persistent="false" brokerName="${brokername}" xmlns="http://activemq.org/config/1.0">
2. <destinationPolicy>
3. <policyMap>
4. <policyEntries>
5. <policyEntry topic="PRICES.>">
6. <!-- 10 seconds worth -->
7. <subscriptionRecoveryPolicy>
8. <timedSubscriptionRecoveryPolicy recoverDuration="10000" />
9. </subscriptionRecoveryPolicy>
10.
11. <!-- lets force old messages to be discarded for slow consumers -->
12. <pendingMessageLimitStrategy>
13. <constantPendingMessageLimitStrategy limit="10"/>
14. </pendingMessageLimitStrategy>
15. </policyEntry>
16. </policyEntries>
17. </policyMap>
18. </destinationPolicy>
19. ...
20. </broker>

2.6.5 Composite Destinations
从1.1版本起, ActiveMQ支持composite destinations。它允许用一个虚拟的destination 代表多个destinations。例如你可以通过composite destinations在一个操作中同时向12个queue发送消息。在composite destinations中,多个destination之间采用","分割。例如:
Java代码
1. Queue queue = new ActiveMQQueue("FOO.A,FOO.B,FOO.C");
如果你希望使用不同类型的destination,那么需要加上前缀如queue:// 或topic://,例如:
Java代码
1. Queue queue = new ActiveMQQueue("FOO.A,topic://NOTIFY.FOO.A");
以下是ActiveMQ配置文件进行配置的一个例子:
Xml代码
1. <destinationInterceptors>
2. <virtualDestinationInterceptor>
3. <virtualDestinations>
4. <compositeQueue name="MY.QUEUE">
5. <forwardTo>
6. <queue physicalName="FOO" />
7. <topic physicalName="BAR" />
8. </forwardTo>
9. </compositeQueue>
10. </virtualDestinations>
11. </virtualDestinationInterceptor>
12. </destinationInterceptors>
可以在转发前,先通过JMS Selector判断一个消息是否需要转发,例如:
Xml代码
1. <destinationInterceptors>
2. <virtualDestinationInterceptor>
3. <virtualDestinations>
4. <compositeQueue name="MY.QUEUE">
5. <forwardTo>
6. <filteredDestination selector="odd = 'yes'" queue="FOO"/>
7. <filteredDestination selector="i = 5" topic="BAR"/>
8. </forwardTo>
9. </compositeQueue>
10. </virtualDestinations>
11. </virtualDestinationInterceptor>
12. </destinationInterceptors>

2.6.6 Mirrored Queues
每个queue中的消息只能被一个consumer消费。然而,有时候你可能希望能够监视生产者和消费者之间的消息流。你可以通过使用Virtual Destinations 来建立一个virtual queue 来把消息转发到多个queues中。但是为系统中每个queue都进行如此的配置可能会很麻烦。
ActiveMQ支持Mirrored Queues。Broker会把发送到某个queue的所有消息转发到一个名称类似的topic,因此监控程序可以订阅这个mirrored queue topic。为了启用Mirrored Queues,首先要将BrokerService的useMirroredQueues属性设置成true,然后可以通过 destinationInterceptors设置其它属性,如mirror topic的前缀,缺省是"VirtualTopic.Mirror."。以下是ActiveMQ配置文件的一个例子:
Xml代码
1. <broker xmlns="http://activemq.org/config/1.0" brokerName="MirroredQueuesBroker1" useMirroredQueues="true">
2.
3. <transportConnectors>
4. <transportConnector uri="tcp://localhost:61616"/>
5. </transportConnectors>
6.
7. <destinationInterceptors>
8. <mirroredQueue copyMessage = "true" prefix="Mirror.Topic"/>
9. </destinationInterceptors>
10. ...
11. </broker>
假如某个producer向名为Foo.Bar的queue中发送消息,那么你可以通过订阅名为Mirror.Topic.Foo.Bar的topic来获得发送到Foo.Bar中的所有消息。

2.6.7 Wildcards
Wildcards用来支持联合的名字分层体系(federated name hierarchies)。它不是JMS规范的一部分,而是ActiveMQ的扩展。ActiveMQ支持以下三种wildcards:
• "." 用于作为路径上名字间的分隔符。
• "*" 用于匹配路径上的任何名字。
• ">" 用于递归地匹配任何以这个名字开始的destination。
使用activeMQ发送邮件 第一步 安装activemq,参考http://blog.163.com/yangzhanghui_job/blog/static/17957506220127171173225/。 第二步 登录activemq查询信息http://localhost:8161/admin/,原始用户名和密码都为admin,为了安全起见,可以修改密码,控制台的登录用户名密码保存在conf/jetty-realm 阅读详情

相关推荐

《供应链金融风控模型设计:多维度数据整合与动态风险预警机制》

注:方案突出供应链金融「三流合一」(资金流、物流、信息流)的风控逻辑,通过多维度数据整合与动态预警,解决传统风控中中小企业信用评估难、链条风险传导监测滞后的问题。◦ 构建供应链拓扑图,计算「核心企业违约对上下游企业的影响系数」(如某电子厂商违约时,其前五大供应商的风险权重自动提升40%)• 区块链应用:将核心企业应付账款上链存证,防止「一票多融」欺诈(如某供应商用同一应收账款在多家银行融资)◦ 财务数据:核心企业审计报告、上下游企业纳税数据(增值税发票频次/金额)

2501_91651383的博客 1549

Activemq -5.* 学习

1 JMS 在介绍ActiveMQ之前,首先简要介绍一下JMS规范。 1.1 JMS的基本构件 1.1.1 连接工厂 连接工厂是客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQConnectionFactory。 1.1.2 连接 JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。 1.1.3 会话 ...

jamour的专栏 227

RoboMaster机甲大师赛备赛:如何用STM32+CAN总线精准控制四个大疆3508电机?

本文详细介绍了在RoboMaster机甲大师赛中,如何利用STM32和CAN总线技术精准控制四台大疆3508电机。从硬件架构设计、多电机ID分配、数据解析到同步控制算法,全面解析了构建高响应、低延迟底盘驱动系统的关键技术,助力参赛队伍提升机器人运动性能。

weixin_28684825的博客 133

ActiveMQ 简介、安装、与 Maven 项目基本使用

目录 ActiveMQ 简介 ActiveMQ 5.15.9 安装 ActiveMQ 基本使用 ActiveMQ 简介 1、ActiveMQ 是 Apache 推出的一款开源免费的,完全支持 JMS1.1 和 J2EE 1.4 规范的 JMS Provider 实现的消息中间件(Message Oriented Middleware,MOM) 2、ActiveMQ 最主要的功能是实现 ...

蚩尤后裔-汪茂雄 9593

FinRL实战:5步搞定股票量化交易策略(附Python代码)

本文详细介绍了如何使用FinRL框架,通过5个步骤构建基于深度强化学习的股票量化交易策略。内容涵盖从环境搭建、数据获取与预处理,到交易环境构建、模型训练(如PPO算法),以及策略回测评估与优化的完整流程,并提供了可运行的Python代码示例,帮助读者快速入门AI驱动的量化交易。

weixin_29062865的博客 626

手把手拆解UCIe协议层:从Raw Mode到Flit Mode,数据包到底经历了什么?

本文深入解析UCIe协议层的数据包处理机制,从Raw Mode到Flit Mode的三大变体(Standard 256B、68B-Enhanced、Latency-Optimized),详细揭示数据包在不同模式下的封装与转换过程。通过工程师视角,展示协议层如何实现多模式动态切换、错误恢复及与D2D Adapter的协同设计,为芯片级互连技术提供实践指导。

weixin_28728495的博客 239

ActiveMQ使用步骤

1.1. 什么是ActiveMQ ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。 主要特点: 1. 多种语言和协议编写客户端。语言: Java, C, C++, C#, Ru

llsai2204的博客 799

Rerender A Video 技术浅析(四):跨帧约束

时间一致性是指在视频处理过程中,保持相邻帧之间内容、风格和运动的平滑过渡,避免出现视觉上的不连续性或伪影。时间一致性跨帧约束模块的主要目标是在视频增强、风格迁移等任务中,确保处理后的视频在时间维度上保持一致性和稳定性。该模块主要通过以下几种方式实现时间一致性:1.时序损失(Temporal Loss):最小化相邻帧之间的差异,确保帧间内容的平滑过渡。2.循环一致性损失(Cycle Consistency Loss):确保帧间转换的一致性,避免出现风格漂移。3.

m0_75253143的博客 1480

ActiveMQ与JMS详解

什么是JMS JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。(来自百度百科) 简单来说就是应用程序A 想从应用程序B中收到某个消息,甚至是N个应用程序A从应用...

qq_20282955的博客 656

StarRC常见问题整理

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?使用starrc_cmd “错误代码”就能看对应Error或Warnning的解释。

m0_61544122的博客 3119

3D深度饼图的QT实现

首先,我们需要创建一个QT项目并创建一个窗口,用于显示我们的饼图。在QT Creator中,选择创建一个新的QT Widgets应用程序项目,并创建一个新的窗口类。在本文中,我将介绍如何使用QT实现一个3D深度饼图(Pie 3D)的可视化效果。在构造函数中,我们将创建一个饼图(QChartView)并设置饼图的数据。这是一个简单的示例,您可以根据需要自定义和扩展代码来创建更复杂的饼图。现在,我们已经完成了3D深度饼图的QT实现。您将看到一个具有相应数据的3D深度饼图显示在窗口中。3D深度饼图的QT实现。

CodeJolt的博客 277

Rabbitmq 安全账号管理方案

第一方案 创建与业务相关的账号及vhost,添加自定义权限 rabbitmqctl add_vhost host rabbitmqctl add_user admin 123456 rabbitmqctl set_permissions -p host admin ".*" ".*" ".*" 只对其相关联的应用有管理的权限 风险:目前的线上连接rabbitmq 的账号大多是administrator 角色的账号,如果修改为低级别的权限,如果交叉访问vhost,必然会有权限问题; rabb

屈帅波的技术博客 2872

LVGL 拨盘菜单(Dial Menu)

h。

摸鱼专家 191

ActiveMQ使用入门

面向消息中间件 和 ActiveMQ 简介,ActiveMQ的安装和配置详解,MessageProducer的发送模式、优先级和过期时间,使用 Spring Boot 简化JMS开发,使用ActiveMQ实现抢购时的并发效率优化

qq_55917018的博客 9263

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

SpringBoot 使用 ActiveMq

AtiveMq部署与springboot集成

空明的博客 6262

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

ActiveMQ的安装与使用

ActiveMQ的安装与使用 一、安装 1、下载安装包 官网下载地址: http://activemq.apache.org/components/classic/download/ 根据自己的电脑的系统下载对应的版本. 2、解压 cd 到下载压缩包的目录,解压压缩包,输入 tar -zxvf +文件名即可解压 tar zxvf apache-activemq-5.16.0-bin.tar.gz 3、启动 进入相对目录: /apache-activemq-5.16.0/bin/macosx 命令行

菜鸟的博客 2万+

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

activemq 用jmx监控activemq的配置步骤

一般用activemq都会用到activemq的监控功能,监控方式有很多种,官网可查。 下面讲一下5.9版本的activemq用jmx来监控的配置步骤: 1:首先在/conf/activemq.xml中修改如下 <broker xmlns="http://activemq.apache.org/schema/core" brokerName="BROKER1" dataDir

johnnie_deng的专栏 7451

Java中Activemq的使用

什么是Activemq Activemq是一种消息中间件,也就是在两个服务中间负责消息的传递和消息队列。就好比菜市场和饭店的关系中,菜市场就是生产者,饭店就是消费者,蔬菜说就是消息,运输车就是中间件,负责将菜市场中的蔬菜运送到饭店中,饭店再根据菜进行下一步骤。同时Activemq可以以队列的方式进行数据的传输,可以防止数据消费者,也就是接收数据的一方由于数据量过大而引发问题。 设置maven ...

m0_46336269的博客 1352

ActiveMQ的学习记录

先启动订阅,再发布消息各个消息中间件的比较点对点点对点模型是基于队列的,生产者发消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。和我们平时给朋友发送短信类似。如果在Session关闭时有部分消息己被收到但还没有被签收(acknowledged),那当消费者下次连接到相同的队列时,这些消息还会被再次接收队列可以长久地保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势发布订阅(1) JMS的发布订阅总结。

weixin_68657865的博客 1474

Linux安装ActiveMQ以及SpringBoot使用ActiveMQ

Linux安装ActiveMQ以及SpringBoot使用ActiveMQ activeMQ下载地址:http://activemq.apache.org/components/classic/download/ ActiveMQ依赖JDK版本,根据自己在Linux安装好的JDK进行选择安装合适的版本。 参考版本链接:https://blog.csdn.net/qq_26483671/arti...

September的博客 964

ActiveMQ

ActiveMQ 的官网 :ActiveMQ扩展出:API 接受发送MQ 的高可用MQ 的集群容错配置MQ 的持久化延时发送签收机制Spring/SpringBoot 整合等// MQ 都需要满足的技术MQ : 消息中间件/消息队列。

while(life)++; 2366

ActiveMQ使用

(2)基于发布/订阅模式的传输,即根据订阅话题来接收相应数据,一个生产者可向多个消费者推送数据,与MQTT协议的实现是类似的,对MQTT协议有兴趣的可跳转到https://www.cnblogs.com/xiguadadage/p/11216463.html。ActiveMQ是一种开源的基于JMS(Java Message Servie)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的,面向消息的,能够跨越多语言和多系统的应用集成消息通信中间件。

weixin_57763462的博客 1185

ActiveMQ 使用

以下:内容参考《Spring 实战》、https://blog.csdn.net/mgsky1/article/details/80024876。如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正 JMS (Java Message Service) : java消息服务 AMQP (Advanced Message Queuing Protocol ) : 高级消息队列协议 注...

猫吻鱼的博客 848

java之activeMQ

文章目录一、activeMQ默认已经安装完毕队列点对点模式服务提供方服务消消费方(一)服务消消费方(二)主题服务提供方服务消费方(一)服务消费方(二)手动签收事物总结springboot 整合activemq点对点坐标yml创建一个Queue(队列)服务提供方启动类服务消费方发布订阅 一、activeMQ 默认已经安装完毕 启动,进入activemq目录下的bin目录,启动,./activemq start 关闭防火前或者打开端口 activemq监听的是61616,我们访要查看消息队列的状态时访问816

weixin_43679491的博客 3016

ActiveMq系列(四):Activemq持久化

系列文章 ActiveMq系列(一):基本服务器搭建 ActiveMq系列(二):ActiveMq使用入门 ActiveMq系列(三):Springboot集成ActiveMq ActiveMq系列(四):Activemq持久化 前言 本章内容紧接ActiveMq系列(三):Springboot集成ActiveMq而来,相关测试代码复用上一章的。 ActiveMq当前(5.15.12)默认使用kahaDB持久化消息,默认配置 <activemq-dir>/conf/active..

Show Me The Code 914

ActiveMQ详细入门使用教程

无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。 ActiveMQ介绍 MQ是消息中间件,是一种在分布式系统中应用程序借以传递消息的媒介,常用的有ActiveMQ,RabbitMQ,kafka。ActiveMQ是Apache下的开源项目,完全支持JMS1.1和...

.... 永远年轻,永远热泪盈眶 16万+
上一篇: spring ibatis入门
下一篇: Eclipse快捷键与启动运行速度调优
log_cd
博客等级 码龄19年 7粉丝 109原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值