事務
jms中事務分為生產者和消費者兩塊,消息的生產和消費不能包含在同一個事務中。
生產者:
在事務狀態下進行發送操作,消息并未真正投遞到中間件。而只有進行session.commit操作之后,消息才會發送到中間件,再轉發到適當的消費者進行處理。如果是調用rollback操作,則表明,當前事務期間內所發送的消息都取消掉。
在支持事務的session中,producer發送message時在message中帶有transactionID。broker收到message后判斷是否有transactionID,如果有就把message保存在transaction store中,等待commit或者rollback消息。所以ActiveMq的事務是針對broker而不是producer的,不管session是否commit,broker都會收到message。如果producer發送模式選擇了persistent,那么message過期后會進入死亡隊列。在message進入死亡隊列之前,ActiveMQ會刪除message中的transaction ID,這樣過期的message就不在事務中了,不會保存在transaction store中,會直接進入死亡隊列。
消費者:
在Spring整合JMS的應用中,我們要進行本地的事務管理,只需要指定對應的監聽容器的sessionTransacted屬性為true。對于SessionAwareMessageListener,在接收到消息后發送一個返回消息時也處于同一事務下,但是對于其他操作如數據庫訪問等將不屬于該事務控制。
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="consumerMessageListener" />
<property name="sessionTransacted" value="true"/>
</bean>
jta事務:
如果想要接收消息和數據庫訪問處于同一事務中,那么我們就可以配置一個外部的事務管理同時配置一個支持外部事務管理的消息監聽容器(如DefaultMessageListenerContainer)。要配置這樣一個參與分布式事務管理的消息監聽容器,我們可以配置一個JtaTransactionManager,當然底層的JMS ConnectionFactory需要能夠支持分布式事務管理,并正確地注冊我們的JtaTransactionManager。這樣消息監聽器進行消息接收和對應的數據庫訪問就會處于同一數據庫控制下,當消息接收失敗或數據庫訪問失敗都會進行事務回滾操作。
<bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="messageListener" ref="jmsQueueReceiver" />
<property name="destination" ref="queueDestination" />
<property name="sessionTransacted" value="true"/>
<property name="transactionManager" ref="jtaTransactionManager"/>
</bean>
<bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
當指定了transactionManager時,消息監聽容器將忽略sessionTransacted的值。
持久化
默認持久化到文件中:
打開安裝目錄下的配置文件,注意這里使用的是kahaDB,是一個基于文件支持事務的消息存儲器。以日志形式存儲消息,消息索引以B-Tree結構存儲。在D:\ActiveMQ\apache-activemq\conf\activemq.xml中會發現默認的配置項:
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
消息存儲在基于文件的數據日志中。如果消息發送成功,變標記為可刪除的。系統會周期性的清除或者歸檔日志文件。
消息文件的位置索引存儲在內存中,這樣能快速定位到。定期將內存中的消息索引保存到metadata store中,避免大量消息未發送時,消息索引占用過多內存空間。
如需持久化到數據庫中:
首先需要把MySql的驅動放到ActiveMQ的Lib目錄下,例如:mysql-connector-java-5.0.4-bin.jar,在conf/acticvemq.xml中更改persistenceAdapter節點配置并且引用定義的mysql-ds數據源。
<!--
Configure message persistence for the broker. The default persistence
mechanism is the KahaDB store (identified by the kahaDB tag).
For more information, see:
http://activemq.apache.org/persistence.html
<kahaDB directory="${activemq.data}/kahadb"/>
-->
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="false" />
</persistenceAdapter>
dataSource指定持久化數據庫的bean,createTablesOnStartup是否在啟動的時候創建數據表,默認值是true,這樣每次啟動都會去創建數據表了,一般是第一次啟動的時候設置為true,之后改成false。
在conf/acticvemq.xml中定義mysql-ds,如下。
<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=""/>
<property name="maxActive" value="200"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
然后重新啟動消息隊列,你會發現多了3張表activemq_acks,activemq_lock,activemq_msgs。
- activemq_msgs用于存儲消息,然后啟動消費者,發現Mysql中已經沒有這條消息了。
- activemq_acks用于存儲訂閱關系。如果是持久化Topic,訂閱者和服務器的訂閱關系在這個表保存。
- activemq_lock在集群環境中才有用。
PERSISTENT (持久消息)和 NON_PERSISTENT(非持久消息),默認為持久消息。持久化的消息在MQ服務器宕機之后,消息不會丟失,在重啟服務的時候,消息將恢復。
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="cachingConnectionFactory"></property>
<property name="defaultDestination" ref="dest" />
<property name="messageConverter" ref="messageConverter" />
<property name="pubSubDomain" value="false" />
<property name="explicitQosEnabled" value="true" />
<!-- 發送模式 DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久-->
<property name="deliveryMode" value="1" />
</bean>
prefetch:
ActiveMQ的prefetch機制。當消費者去獲取消息時,不會一條一條去獲取,而是一次性獲取一批,默認是1000條。
假設有三個消費者,接收從1到99,共99條消息:
consumer A:1,4,7...
consumer B:2,5,8...
consumer C:3,6,9...
按照默認分配策略,將會把消息如上預分配。
這些預獲取的消息,在還沒確認消費之前,在管理控制臺還是可以看見這些消息的,但是不會再分配給其他消費者,此時這些消息的狀態應該算作“已分配未消費”,如果消息最后被消費,則會在服務器端被刪除。如果消費者崩潰,則這些消息會被重新分配給新的消費者。但是如果消費者既不消費確認,又不崩潰,那這些消息就永遠躺在消費者的緩存區里無法處理。
假設一種情況:某個consumer C性能較差,處理信息速度很慢。會導致consumer C任有消息積壓,但consumer A, consumer B已經空閑。
解決方案:將consumer C 的 prefetch設為1,每次處理1條消息,處理完再去取。
prefetchPolicy.setQueuePrefetch(1);
connectionFactory = new ActiveMQConnectionFactory(JMSConsumer.USERNAME, JMSConsumer.PASSWORD, JMSConsumer.BROKEURL);//實例化連接工廠
connectionFactory.setPrefetchPolicy(prefetchPolicy);
TimeToLive:
表示一個消息的有效期。只有在這個有效期內,消息消費者才可以消費這個消息。默認值為0,表示消息永不過期。
如果使用TTL來判定消息的過期,那么就首先需要確保Producer、broker兩者的系統時間要盡可能的一致,Consumer也盡可能的和broker的時間保持一致。Broker將會在接收Producer消息時,以及將消息發送給Consumer之前都會檢測消息是否過期,判斷過期的方法也就是根據JMSExpiration和當前時間戳比較。
可以通過下面的方式設置:
producer.setTimeToLive(3600000); //有效期1小時 (1000毫秒 * 60秒 * 60分)
可以在消息發送時,為當前消息設定ttl。
messageProducer.send(Message message, int deliveryMode, int priority, long timeToLive)
如果消息過期,將會把消息發送到DLQ中,此消息不會被Consumer消費。如果broker端對DLQ使用Discard策略或者Broker沒有開啟DLQ相關策略,這些過期的消息可能將不復存在。在conf/activemq.xml中:
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" topic=">">
<deadLetterStrategy>
<sharedDeadLetterStrategy processExpired="false" />
</deadLetterStrategy>
<!-- discard all -->
<!--
<discardingDeadLetterStrategy />
-->
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
Priority:
我們可以在發送消息時,指定消息的權重,broker可以建議權重較高的消息將會優先發送給Consumer。不過因為各種原因,priority并不能決定消息傳送的嚴格順序(order)。
JMS標準中約定priority可以為09的數值,值越大表示權重越高,默認值為4。不過activeMQ中各個存儲器對priority的支持并非完全一樣。比如JDBC存儲器可以支持09。但是對于kahadb/levelDB等這種基于日志文件的存儲器而言,priority支持相對較弱,只能識別三種優先級(LOW: < 4,NORMAL: =4,HIGH: > 4)。在broker端,默認是不支持priority排序的,我們需要手動開啟。在conf/activemq.xml中::
<policyEntry queue=">" prioritizedMessages="true"/>
設置message的優先級:
TextMessage message = session.createTextMessage("ActiveMQ 發送消息" +i);//創建一條文本消息
message.setJMSPriority(9);
failover:
如果集群中的某一臺消息服務器宕機,與該臺消息服務器相連接的生產者和消費者需要能夠自動連接到其他正常工作的消息服務器。對此ActiveMQ提供了一種叫做失效轉移(也叫故障轉移,FailOver)的策略。失效轉移提供了在傳輸層上重新連接到其他任何傳輸器的功能。
只需要在uri中配置,語法如下:
failover:(uri1,...,uriN)?transportOptions 或者 failover:uri1,...,uriN
例子:
failover:(tcp://primary:61616,tcp://secondary:61616)?randomize=false
failover:(tcp://localhost:61616,tcp://remotehost:61616)?randomize=false&initialReconnectDelay=100
如果某個ActiveMQ客戶端發現uri1地址失效了,它會立即轉向uri地址列表中其他可以連接的消息服務器進行重連,以保證繼續正常工作,這種選擇其他地址的方式默認是隨機的,以保證負載均衡。如果你想關閉隨機,可以transportOptions中加入randomize=false。
Java例子:
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"failover:(
tcp://192.168.0.87:61616?wireFormat.maxInactivityDuration=0,
tcp://192.168.0.87:61617?wireFormat.maxInactivityDuration=0
)");
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
- transportOptions有多種參數可以選擇,如下:
- initialReconnectDelay:默認為10,單位毫秒,表示第一次嘗試重連之前等待的時間。
- maxReconnectDelay:默認30000,單位毫秒,表示兩次重連之間的最大時間間隔。
- useExponentialBackOff:默認為true,表示重連時是否加入避讓指數來避免高并發。
- reconnectDelayExponent:默認為2.0,重連時使用的避讓指數。
- maxReconnectAttempts:5.6版本之前默認為-1, 5.6版本及其以后,默認為0。 0表示重連的次數無限,配置大于0可以指定最大重連次數。
- randomize:默認為true,表示在URI列表中選擇URI連接時是否采用隨機策略。如果為true的話,有可能生產者連接的是第一個,而消費者連接的是第二個,造成一個服務器上只有生產者,一個服務器上只有消費者。
- backup:默認為false,表示是否在連接初始化時將URI列表中的所有地址都初始化連接,以便快速的失效轉移,默認是不開啟。
- timeout:默認為-1,單位毫秒,是否允許在重連過程中設置超時時間來中斷的正在阻塞的發送操作。-1表示不允許,其他表示超時時間。
5.9版本使用levelDB+zookeeper的方式來實現HA了。
參考:
http://haohaoxuexi.iteye.com/blog/1983532
http://manzhizhen.iteye.com/blog/2105572