前言:
維護公司項目,用的是JDK6 + spring2.5.6.SEC01,需求是實現一個rabbitmq客戶端發送消息的工具類。
太難了~非springboot項目,無法直接配置使用rabbitTemplate;版本太低不能也不敢修改spring版本,無法使用amqpTemplate;嘗試直接使用官方rabbitmq-java客戶端amqp-client,每次發送都得創建和銷燬channel,Public速度只有13/s,實在太慢......中間各種踩坑,最後使用apache的commons-pool2通用對象池解決(jedis連接池(v3.0.1)用的也是這個redis.clients.jedis.util.Pool#internalPool)。
爲了適配公司項目,項目主要配置如下,各位看官請知悉:
jdk 1.6,amqp-client:4.0.3,commons-pool2:2.0
代碼在文末。
介紹:
- 連接池/對象池(ObjectPool):用於存放連接對象的一個池子(集合),通常用數組或者List對象.durid用兩個數組分別保存活躍的連接和空閒連接.commons-pool2用雙端阻塞隊列LinkedBlockingDeque保存空閒連接
- 用ConcurrentHashMap保存所有的連接.
- 對象工廠(PooledObjectFactory):連接池工廠,用於產生一個新的連接對象.
- 連接對象/池中對象(PooledObject):連接池裏面存放的對象.
相關的類:
連接池主要兩個類,PooledObjectFactory:管理池中對象,如創建/銷燬對象,驗證對象是否可用,鈍化/激活對象(這個暫時不理解,所以沒用到)。第二個就是GenericObjectPool了:操作池中對象,借用/歸還對象。它繼承了BaseGenericObjectPool,實現了ObjectPool,一般默認用這個作爲連接池對象。它構造方法還可以傳入兩個類:GenericObjectPoolConfig,AbandonedConfig,用於連接池配置。
PooledObjectFactory:
GenericObjectPool繼承BaseGenericObjectPool實現ObjectPool,通常用此實現來作爲默認的連接池對象。
BaseGenericObjectPool一個抽象類,主要實現了兩個功能:1.註冊JMX
2.管理一個連接池驅逐線程,此線程調用GenericObjectPool的evict()驅逐超過最大生命週期的連接對象。ObjectPool連接池的最上層接口,定義了一個連接池必須具備的方法,比如借用一個連接對象T borrowObject(),歸還一個使用後的連接對象void returnObject(T obj)。
PooledObjectFactory:
我們的自定義mq連接池得實現這五個方法:makeObject(創建對象),destroyObject(銷燬對象),validateObject(驗證對象可用),activateObject(激活對象),passivateObject(鈍化對象)。
GenericObjectPoolConfig:
字段 | 意義 | 默認值 | 備註 |
---|---|---|---|
lifo | 對象池存儲空閒對象是使用的LinkedBlockingDeque,它本質上是一個支持FIFO和FILO的雙向的隊列,common-pool2中的LinkedBlockingDeque不是Java原生的隊列,而有common-pool2重新寫的一個雙向隊列。如果爲true,表示使用FIFO獲取對象。 | true | |
fairness | common-pool2實現的LinkedBlockingDeque雙向阻塞隊列使用的是Lock鎖。這個參數就是表示在實例化一個LinkedBlockingDeque時,是否使用lock的公平鎖。 | false | |
maxWaitMillis | 當沒有空閒連接時,獲取一個對象的最大等待時間。如果這個值小於0,則永不超時,一直等待,直到有空閒對象到來。如果大於0,則等待maxWaitMillis長時間,如果沒有空閒對象,將拋出NoSuchElementException異常。默認值是-1;可以根據需要自己調整,單位是毫秒 | -1 | |
minEvictableIdleTimeMillis | 對象最小的空閒時間。如果爲小於等於0,最Long的最大值,如果大於0,當空閒的時間大於這個值時,執行移除這個對象操作。默認值是1000L * 60L * 30L;即30分鐘。可以避免(連接)泄漏。 | 1000L * 60L * 30L | |
evictorShutdownTimeoutMillis | shutdown驅逐線程的超時時間。當創建驅逐線(evictor)程時,如發現已有一個evictor正在運行則會停止該evictor,evictorShutdownTimeoutMillis表示當前線程需等待多長時間讓ScheduledThreadPoolExecutor(evictor繼承自TimerTask,由ScheduledThreadPoolExecutor進行調度)停止該evictor線程。 | 當前版本沒有這個屬性 | |
softMinEvictableIdleTimeMillis | 對象最小的空間時間,如果小於等於0,取Long的最大值,如果大於0,當對象的空閒時間超過這個值,並且當前空閒對象的數量大於最小空閒數量(minIdle)時,執行移除操作。這個和上面的minEvictableIdleTimeMillis的區別是,它會保留最小的空閒對象數量。而上面的不會,是強制性移除的。默認值是-1; | -1 | |
numTestsPerEvictionRun | 檢測空閒對象線程每次檢測的空閒對象的數量。默認值是3;如果這個值小於0,則每次檢測的空閒對象數量等於當前空閒對象數量除以這個值的絕對值,並對結果向上取整。 | 3 | |
testOnCreate | 在創建對象時檢測對象是否有效,true是,默認值是false。做了這個配置會降低性能。 | false | |
testOnBorrow | 在從對象池獲取對象時是否檢測對象有效,true是;默認值是false。做了這個配置會降低性能。 | false | |
testOnReturn | 在向對象池中歸還對象時是否檢測對象有效,true是,默認值是false。做了這個配置會降低性能。 | false | |
testWhileIdle | 在檢測空閒對象線程檢測到對象不需要移除時,是否檢測對象的有效性。true是,默認值是false。建議配置爲true,不影響性能,並且保證安全性。 | false | |
timeBetweenEvictionRunsMillis | 空閒對象檢測線程的執行週期,即多長時候執行一次空閒對象檢測。單位是毫秒數。如果小於等於0,則不執行檢測線程。默認值是-1; | -1 | |
blockWhenExhausted | 當對象池沒有空閒對象時,新的獲取對象的請求是否阻塞。true阻塞。默認值是true; |
true | |
jmxEnabled | 是否註冊JMX | true | |
jmxNamePrefix | JMX前綴 | pool | |
jmxNameBase | 使用base + jmxNamePrefix + i來生成ObjectName | ||
maxTotal | 對象池中管理的最多對象個數。默認值是8。 |
8 | |
maxIdle | 對象池中最大的空閒對象個數。默認值是8。 | 8 | |
minIdle | 對象池中最小的空閒對象個數。默認值是0。 | 0 |
AbandonedConfig
字段 | 意義 | 默認值 | 備註 |
---|---|---|---|
logAbandoned | 標記是否在pooledObject被abandon的時候打出堆棧 | false | 參考GenericObjectPool#removeAbandoned |
removeAbandonedOnBorrow | 標記從objectPool裏"借"出時,是否將一些認爲無用的pooledObject給remove掉 | false | |
removeAbandonedTimeout | 當pooledObject處於allocated狀態下,但是超過該時長沒有用過,則abandon | 300(調用方會轉成300s) | 在removeAbandonedOnBorrow=true的前提下 |
removeAbandonedOnMaintenance | evictor工作時,是否進行abandon | false |
另外還有個EvictionConfig配置(空閒時驅逐配置),GenericKeyedObjectPool(如果需要定義多個對象池可使用,利用ConcurrentHashMap,每個key對應一個PooledObject)這裏就不貼出來了。
代碼:
思路還是1.配一個PooledObjectFactory管理對象。2.GenericObjectPool初始化配置連接池。3.RabbitMQChannelPool操作對象。
PooledObjectFactory
/**
* @Author canon
* @Date 2020/1/8
* @Description rabbitmq連接池工廠
**/
public class RabbitMQChannelPoolFactory implements PooledObjectFactory<Channel> {
private ConnectionFactory factory;
public RabbitMQChannelPoolFactory(ConnectionFactory factory) {
this.factory = factory;
}
@Override
public PooledObject<Channel> makeObject() throws Exception {
// 池對象創建實例化資源
return new DefaultPooledObject<Channel>(factory.newConnection().createChannel());
}
@Override
public void destroyObject(PooledObject<Channel> p) throws Exception {
// 池對象銷燬資源
if (p != null && p.getObject() != null && p.getObject().isOpen()) {
p.getObject().close();
}
}
@Override
public boolean validateObject(PooledObject<Channel> p) {
// 驗證資源是否可用
return p.getObject() != null && p.getObject().isOpen();
}
@Override
public void activateObject(PooledObject<Channel> p) throws Exception {
}
@Override
public void passivateObject(PooledObject<Channel> p) throws Exception {
}
}
GenericObjectPool
/**
* @Author canon
* @Date 2020/1/8
* @Description rabbitmq channel池
**/
public class RabbitMqChannelPool extends GenericObjectPool<Channel> {
public RabbitMqChannelPool(PooledObjectFactory<Channel> factory) {
super(factory);
}
public RabbitMqChannelPool(PooledObjectFactory<Channel> factory, GenericObjectPoolConfig config) {
super(factory, config);
}
public RabbitMqChannelPool(PooledObjectFactory<Channel> factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) {
super(factory, config, abandonedConfig);
}
}
RabbitMQChannelPool
/**
* @Author canon
* @Date 2020/1/8
* @Description 操作channel池
**/
public class RabbitMQChannelPool {
private GenericObjectPool<Channel> pool;
public RabbitMQChannelPool(RabbitMQChannelPoolFactory factory, GenericObjectPoolConfig poolConfig) {
this.pool = new GenericObjectPool<Channel>(factory, poolConfig);
}
public Channel getChannel() {
try {
return pool.borrowObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void returnChannel(Channel channel) {
if (channel != null) {
pool.returnObject(channel);
}
}
}
spirng配置(公司項目):
<!-- rabbitmq連接及channel池配置開始 -->
<bean id="rabbitConnectionFactory" class="com.rabbitmq.client.ConnectionFactory">
<property name="host" value="${mq.host}"/>
<property name="port" value="${mq.port}"/>
<property name="virtualHost" value="${mq.virtual}"/>
<property name="username" value="${mq.username}"/>
<property name="password" value="${mq.password}"/>
</bean>
<bean id="rabbitMQChannelPoolFactory" class="com.canon.push.pool.RabbitMQChannelPoolFactory">
<constructor-arg index="0" ref="rabbitConnectionFactory"/>
</bean>
<bean id="myRabbitMqPoolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
<property name="jmxEnabled" value="true"/>
<property name="blockWhenExhausted" value="true"/>
<property name="maxWaitMillis" value="5000"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
<property name="maxTotal" value="100"/>
<property name="maxIdle" value="20"/>
<property name="minIdle" value="10"/>
<property name="timeBetweenEvictionRunsMillis" value="6000"/>
<property name="softMinEvictableIdleTimeMillis" value="20000"/>
</bean>
<bean id="rabbitMQChannelPool" class="com.canon.push.config.RabbitMqChannelPool">
<constructor-arg index="0" ref="rabbitMQChannelPoolFactory"/>
<constructor-arg index="1" ref="myRabbitMqPoolConfig"/>
</bean>
<bean id="rabbitService" class="com.canon.push.service.impl.RabbitMqServiceImpl"/>
<!-- rabbitmq連接及channel池配置結束 -->
代碼已上傳:https://gitee.com/Canon_Canon/rabbitmq_pool.git,打包引入項目即可,在項目中配置GenericObjectPoolConfig和AbandonedConfig及相關bean,就可直接@Autowried RabbitMqServiceImpl rabbitService使用了。
測試可參考項目test目錄。
完~
參考博客:
commons-pool2 3 - 配置介紹,BaseObjectPoolConfig,AbandonedConfig,EvictionConfig