優化生產者連接工廠(帶有session緩存)

之前的連接工廠中,JmsTemlate每次發送消息時都會重新創建connection、session、productor等對象,從而導致性能的下降,是否有類似pool的連接池協助我們提高性能呢?答案必然是肯定的。

本章概要
1、緩衝連接工廠對比選擇;
2、源碼分析;
3、CachingConnectionFactory連接工廠的實現;
4、探索:ActiveMQXAConnectionFactory;


緩衝連接工廠對比選擇

1、ActiveMQ自身即有相關的實現PooledConnectionFactory,其只會緩存connection,session和productor,不會緩存consumer,故其適用於生產者。
2、而spring基於JMS標準進行了連接池的實現,故其不僅僅適用於ActiveMQ,並且還提供了對consumer的緩存實現。Spring定義了CachingConnectionFactory和SingleConnectionFactory,其均有緩存功能,但考慮到CachingConnectionFactory類擴展自SingleConnectionFactory,其有了更多的實現。

小結:故本章將通過CachingConnectionFactory實現帶有緩衝池的連接工廠。

源碼分析

1、從ActiveMQAutoConfiguration入手

再看ActiveMQConnectionFactoryConfiguration

以及ActiveMQXAConnectionFactoryConfiguration

其中均有條件註解@ConditionalOnMissingBean({ ConnectionFactory.class }),而此時我們正需要註冊自定義CachingConnectionFactory,故一旦我們註冊了系統默認的裝配的ConnectionFactory失效。
2、先了解下即將要註冊的CachingConnectionFactory,

又或者

均需要設置其真正生成連接的ConnectionFactory,考慮到1中默認的裝載已經失效,我們需要自己實現ConnectionFactory的註冊。
3、在ActiveMQConnectionFactoryConfiguration中可以找到

創建我們的ConnectionFactory,考慮到1.1中的描述其註解配置不會生效,故準備直接複製一個一樣的ActiveMQConnectionFactoryConfiguration來定義實現,但確發現 ActiveMQConnectionFactoryFactory類是沒有公開的,無法被我自定義的包路徑所識別應用,故只能從ActiveMQConnectionFactoryFactory自定義實現開始了。
4、當然我們也可以很簡單的直接在定義CachingConnectionFactory時直接new一個ActiveMQConnectionFactory實例。以上2、3的分析主要是結合springboot實現機制同步實現,便於更好的理解。


CachingConnectionFactory連接工廠的實現

1、創建ActiveMQConnectionFactory的工廠類,主要負責創建連接:
package com.shf.activemq.config;

import java.lang.reflect.InvocationTargetException;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * 創建ActiveMQConnectionFactory的工廠類
 * @author song
 *
 */
public class ActiveMQConnectionFactoryFactory {
	private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";
	private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
	private final ActiveMQProperties properties;

	ActiveMQConnectionFactoryFactory(ActiveMQProperties properties) {
		Assert.notNull(properties, "Properties must not be null");
		this.properties = properties;
	}

	public <T extends ActiveMQConnectionFactory> T createConnectionFactory(Class<T> factoryClass) {
		try {
			return doCreateConnectionFactory(factoryClass);
		} catch (Exception ex) {
			throw new IllegalStateException("Unable to create ActiveMQConnectionFactory", ex);
		}
	}

	private <T extends ActiveMQConnectionFactory> T doCreateConnectionFactory(Class<T> factoryClass) throws Exception {
		ActiveMQConnectionFactory factory = createConnectionFactoryInstance(factoryClass);
		ActiveMQProperties.Packages packages = this.properties.getPackages();
		if (packages.getTrustAll() != null) {
			factory.setTrustAllPackages(packages.getTrustAll().booleanValue());
		}
		if (!(packages.getTrusted().isEmpty())) {
			factory.setTrustedPackages(packages.getTrusted());
		}
		return (T) factory;
	}

	private <T extends ActiveMQConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass)
			throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		String brokerUrl = determineBrokerUrl();
		String user = this.properties.getUser();
		String password = this.properties.getPassword();
		if ((StringUtils.hasLength(user)) && (StringUtils.hasLength(password))) {
			return (T) ((ActiveMQConnectionFactory) factoryClass
					.getConstructor(new Class[] { String.class, String.class, String.class })
					.newInstance(new Object[] { user, password, brokerUrl }));
		}

		return (T) ((ActiveMQConnectionFactory) factoryClass.getConstructor(new Class[] { String.class })
				.newInstance(new Object[] { brokerUrl }));
	}

	String determineBrokerUrl() {
		if (this.properties.getBrokerUrl() != null) {
			return this.properties.getBrokerUrl();
		}
		if (this.properties.isInMemory()) {
			return "vm://localhost?broker.persistent=false";
		}
		return "tcp://localhost:61616";
	}
}
2、配置註冊連接工廠:
package com.shf.activemq.config;

import javax.jms.ConnectionFactory;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.jta.XAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置註冊連接工廠
 * @author song
 *
 */
@Configuration
public class ActiveMQXAConnectionFactoryConfiguration {
	/**
	 * 創建普通連接工廠
	 * @param properties
	 * @return
	 */
	@Bean(name = "jmsConnectionFactory")
	public ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties) {
		return new ActiveMQConnectionFactoryFactory(properties)
				.createConnectionFactory(ActiveMQConnectionFactory.class);
	}
	
}

3、註冊帶有session緩衝的連接工廠
package com.shf.activemq.config;

import javax.jms.ConnectionFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.connection.CachingConnectionFactory;

/**
 * 定義帶有session緩衝的連接工廠
 * @author song
 *
 */
@Configuration
@EnableConfigurationProperties({ ActiveMQProperties.class })
public class ActiveMQConnectionFactoryConfiguration {
	/**
	 * 注入MQ連接工廠
	 */
	@Autowired
	@Qualifier(value="jmsConnectionFactory")
	private ConnectionFactory connectionFactory;
	
	/**
	 * 創建帶有緩衝session的連接工廠
	 * @return
	 */
	@Bean(name="cachingConnectionFactory")
	@Primary
	public CachingConnectionFactory getConnectionFactory(){
		CachingConnectionFactory cachingConnectionFactory=new CachingConnectionFactory(); 
		//目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory
		cachingConnectionFactory.setTargetConnectionFactory(connectionFactory);
		//Session緩存數量,這裏屬性也可以直接在這裏配置
		cachingConnectionFactory.setSessionCacheSize(10);
		return cachingConnectionFactory;
	}
	
}

4、優化JmsTemplate註冊,其將應用3中定義的CachingConnectionFactory :
package com.shf.activemq.config;

import javax.jms.DeliveryMode;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.destination.DestinationResolver;


/**
 * 自定義JmsTemplate,支持事務
 * @author song
 *
 */
@Configuration
@DependsOn(value="cachingConnectionFactory")
public class JmsTemplateConfiguration {

	private final ObjectProvider<DestinationResolver> destinationResolver;
	private final ObjectProvider<MessageConverter> messageConverter;
	@Autowired
	private CachingConnectionFactory cachingConnectionFactory;

	public JmsTemplateConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
			ObjectProvider<MessageConverter> messageConverter) {
		this.destinationResolver = destinationResolver;
		this.messageConverter = messageConverter;
	}

	/**
	 * 配置隊列生產者的JmsTemplate
	 * @param connectionFactory
	 * @return
	 */
	@Bean(name="jmsQueueTemplate")
	@Primary
	public JmsTemplate jmsQueueTemplate() {
		//設置創建連接的工廠
//		JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
		//優化連接工廠,這裏應用緩存池 連接工廠就即可
		JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
		
		//設置P2P隊列消息類型
		jmsTemplate.setPubSubDomain(false);

		DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();
		if (destinationResolver != null) {
			jmsTemplate.setDestinationResolver(destinationResolver);
		}
		MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();
		if (messageConverter != null) {
			jmsTemplate.setMessageConverter(messageConverter);
		}
		//deliveryMode, priority, timeToLive 的開關,要生效,必須配置爲true,默認false
		jmsTemplate.setExplicitQosEnabled(true);
		//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
		jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
		//默認不開啓事務
		System.out.println("默認是否開啓事務:"+jmsTemplate.isSessionTransacted());
		//如果不啓用事務,則會導致XA事務失效;
		//作爲生產者如果需要支持事務,則需要配置SessionTransacted爲true
		jmsTemplate.setSessionTransacted(true);
		
		return jmsTemplate;
	}
	
	/**
	 * 配置發佈訂閱生產者的JmsTemplate
	 * @param connectionFactory
	 * @return
	 */
	@Bean(name="jmsTopicTemplate")
	public JmsTemplate jmsTopicTemplate() {
		//設置創建連接的工廠
//		JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
		//優化連接工廠,這裏應用緩存池 連接工廠就即可
		JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
		
		//設置發佈訂閱消息類型
		jmsTemplate.setPubSubDomain(true);

		DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();
		if (destinationResolver != null) {
			jmsTemplate.setDestinationResolver(destinationResolver);
		}
		MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();
		if (messageConverter != null) {
			jmsTemplate.setMessageConverter(messageConverter);
		}
		//deliveryMode, priority, timeToLive 的開關,要生效,必須配置爲true,默認false
		jmsTemplate.setExplicitQosEnabled(true);
		//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
		jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
		//默認不開啓事務
		System.out.println("默認是否開啓事務:"+jmsTemplate.isSessionTransacted());
		//如果不啓用事務,則會導致XA事務失效;
		//作爲生產者如果需要支持事務,則需要配置SessionTransacted爲true
		jmsTemplate.setSessionTransacted(true);
		
		return jmsTemplate;
	}
	
}

5、通過之前章節中的單元測試代碼驗證, 不僅CachingConnectionFactory 連接工廠配置成功,其事務仍然生效。

探索

在源碼中我們看到了ActiveMQXAConnectionFactoryConfiguration中不僅有常規普通的連接工廠,還有ActiveMQXAConnectionFactory工廠註冊,其標註了

需要注入XAConnectionFactoryWrapper的實例,但其實源碼中並沒有註解配置XAConnectionFactoryWrapper的bean實例,如果需要應用ActiveMQXAConnectionFactory我們需要優先註冊XAConnectionFactoryWrapper,本案例採用AtomikosXAConnectionFactoryWrapper 。

1、註冊AtomikosXAConnectionFactoryWrapper 實例:
package com.shf.activemq.config;

import org.springframework.boot.jta.atomikos.AtomikosXAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置AtomikosXAConnectionFactoryWrapper的bean實例,否則無法創建ActiveMQXAConnectionFactory工廠
 * @author song
 *
 */
@Configuration
public class AtomikosXAConnectionFactoryWrapperConfiguration {

	@Bean
	public AtomikosXAConnectionFactoryWrapper atomikosXAConnectionFactoryWrapper(){
		return new AtomikosXAConnectionFactoryWrapper();
	}
	
}

2、調整上述的ActiveMQXAConnectionFactoryConfiguration,新增一個ActiveMQXAConnectionFactory的bean註冊:
package com.shf.activemq.config;

import javax.jms.ConnectionFactory;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.jta.XAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置註冊連接工廠
 * @author song
 *
 */
@Configuration
public class ActiveMQXAConnectionFactoryConfiguration {

	/**
	 * 創建XA連接工廠
	 * @param properties
	 * @param wrapper
	 * @return
	 * @throws Exception
	 */
	@Bean(name = "xaJmsConnectionFactory")
	public ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, XAConnectionFactoryWrapper wrapper)
			throws Exception {
		ActiveMQXAConnectionFactory connectionFactory = (ActiveMQXAConnectionFactory) new ActiveMQConnectionFactoryFactory(
				properties).createConnectionFactory(ActiveMQXAConnectionFactory.class);

		return wrapper.wrapConnectionFactory(connectionFactory);
	}
	
	/**
	 * 創建普通連接工廠
	 * @param properties
	 * @return
	 */
	@Bean(name = "jmsConnectionFactory")
	public ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties) {
		return new ActiveMQConnectionFactoryFactory(properties)
				.createConnectionFactory(ActiveMQConnectionFactory.class);
	}
	
}

3、調整配置CachingConnectionFactory 中注入的ConnectionFactory 實例:
package com.shf.activemq.config;

import javax.jms.ConnectionFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.connection.CachingConnectionFactory;

/**
 * 定義帶有session緩衝的連接工廠
 * @author song
 *
 */
@Configuration
@EnableConfigurationProperties({ ActiveMQProperties.class })
public class ActiveMQConnectionFactoryConfiguration {
	/**
	 * 注入MQ連接工廠
	 */
	@Autowired
//	@Qualifier(value="jmsConnectionFactory")//雖然不是ActiveMQXAConnectionFactory,但在JTA事務管理器的事務處理下能夠很好的實現分佈式事務
	@Qualifier(value="xaJmsConnectionFactory")//在JTA事務管理器的事務處理下能夠很好的實現分佈式事務
	private ConnectionFactory connectionFactory;
	
	/**
	 * 創建帶有緩衝session的連接工廠
	 * @return
	 */
	@Bean(name="cachingConnectionFactory")
	@Primary
	public CachingConnectionFactory getConnectionFactory(){
		CachingConnectionFactory cachingConnectionFactory=new CachingConnectionFactory(); 
		//目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory
		cachingConnectionFactory.setTargetConnectionFactory(connectionFactory);
		//Session緩存數量,這裏屬性也可以直接在這裏配置
		cachingConnectionFactory.setSessionCacheSize(10);
		return cachingConnectionFactory;
	}
	
}

4、通過之前章節中的單元測試代碼驗證, 不僅CachingConnectionFactory 連接工廠配置成功,其事務仍然生效。

小結:不管注入使用的是ActiveMQXAConnectionFactory還是ActiveMQConnectionFactory,其應用內的分佈式事務均能生效。


發佈了64 篇原創文章 · 獲贊 219 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章