维护公司项目,用的是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,用于连接池配置。
GenericObjectPool继承BaseGenericObjectPool实现ObjectPool,通常用此实现来作为默认的连接池对象。
BaseGenericObjectPool一个抽象类,主要实现了两个功能:1.注册JMX
2.管理一个连接池驱逐线程,此线程调用GenericObjectPool的evict()驱逐超过最大生命周期的连接对象。ObjectPool连接池的最上层接口,定义了一个连接池必须具备的方法,比如借用一个连接对象T borrowObject(),归还一个使用后的连接对象void returnObject(T obj)。
我们的自定义mq连接池得实现这五个方法:makeObject(创建对象),destroyObject(销毁对象),validateObject(验证对象可用),activateObject(激活对象),passivateObject(钝化对象)。
字段 | 意义 | 默认值 | 备注 |
---|---|---|---|
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 |
字段 | 意义 | 默认值 | 备注 |
---|---|---|---|
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操作对象。
/**
* @Author canon
* @Date 2020/1/8
* @Description rabbitmq连接池工厂
**/
public class RabbitMQChannelPoolFactory implements PooledObjectFactory {
private ConnectionFactory factory;
public RabbitMQChannelPoolFactory(ConnectionFactory factory) {
this.factory = factory;
}
@Override
public PooledObject makeObject() throws Exception {
// 池对象创建实例化资源
return new DefaultPooledObject(factory.newConnection().createChannel());
}
@Override
public void destroyObject(PooledObject p) throws Exception {
// 池对象销毁资源
if (p != null && p.getObject() != null && p.getObject().isOpen()) {
p.getObject().close();
}
}
@Override
public boolean validateObject(PooledObject p) {
// 验证资源是否可用
return p.getObject() != null && p.getObject().isOpen();
}
@Override
public void activateObject(PooledObject p) throws Exception {
}
@Override
public void passivateObject(PooledObject p) throws Exception {
}
}
/**
* @Author canon
* @Date 2020/1/8
* @Description rabbitmq channel池
**/
public class RabbitMqChannelPool extends GenericObjectPool {
public RabbitMqChannelPool(PooledObjectFactory factory) {
super(factory);
}
public RabbitMqChannelPool(PooledObjectFactory factory, GenericObjectPoolConfig config) {
super(factory, config);
}
public RabbitMqChannelPool(PooledObjectFactory factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) {
super(factory, config, abandonedConfig);
}
}
/**
* @Author canon
* @Date 2020/1/8
* @Description 操作channel池
**/
public class RabbitMQChannelPool {
private GenericObjectPool pool;
public RabbitMQChannelPool(RabbitMQChannelPoolFactory factory, GenericObjectPoolConfig poolConfig) {
this.pool = new GenericObjectPool(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);
}
}
}
代码已上传:https://gitee.com/Canon_Canon/rabbitmq_pool.git,打包引入项目即可,在项目中配置GenericObjectPoolConfig和AbandonedConfig及相关bean,就可直接@Autowried RabbitMqServiceImpl rabbitService使用了。
测试可参考项目test目录。
完~
参考博客:
commons-pool2 3 - 配置介绍,BaseObjectPoolConfig,AbandonedConfig,EvictionConfig
利用commons-pool2自定义对象池