使用场景
对于一些大对象,或者初始化过程较长的可复用的对象,我们如果每次都new对象出来,那么意味着会耗费大量的时间。
我们可以将这些对象缓存起来,当接口调用完毕后,不是销毁对象,当下次使用的时候,直接从对象池中拿出来即可。
下面以redis客户端举例,说明下连接池的基础实现。
commons-pool2,是常用的对象池工具包,实现了对象池中对象的整个生命周期的管理,同时还可以手动指定对象生命周期的调度阀值。
Jedis是java的redis客户端的实现,能够实现对redis单机以及切片集群的链接。使用起来还很方便。
下面使用Jedis和commons-pool实现客户端连接池的管理。
首先定义生成Jedis链接的工厂
1 public class JedisPooledFactory extends BasePooledObjectFactory<Jedis> {
2
3 //jedis server url
4 private String url = null;
5
6 //redis server port
7 private int port = 6379;
8
9 /**
10 * @param url
11 * @param port
12 */
13 public JedisPooledFactory(String url, int port) {
14 super();
15 this.url = url;
16 this.port = port;
17 }
18
19 /**
20 * @see org.apache.commons.pool2.BasePooledObjectFactory#create()
21 */
22 @Override
23 public Jedis create() throws Exception {
24 Assert.notNull(url);
25 return new Jedis(url, port);
26 }
27
28 @Override
29 public boolean validateObject(PooledObject<Jedis> p) {
30 //if closed,validate error
31 if(!p.getObject().isConnected()){
32 return false;
33 }
34 return super.validateObject(p);
35 }
36
37 @Override
38 public void destroyObject(PooledObject<Jedis> p) throws Exception {
39 // close the connection
40 p.getObject().close();
41 super.destroyObject(p);
42 }
43
44 /**
45 * @see org.apache.commons.pool2.BasePooledObjectFactory#wrap(java.lang.Object)
46 */
47 @Override
48 public PooledObject<Jedis> wrap(Jedis obj) {
49 return new DefaultPooledObject<Jedis>(obj);
50 }
51 }
jedis连接工厂
我们可以看到,这个工厂主要是实现了对Jedis连接对象的生命周期的管理,结合Jedis来说明定义的行为:1.怎么创建Jedis连接(比如连接池中jedis连接不够用的时候)。2.怎么销毁对象(比如连接池中大量空闲连接)。3.每次borrow/return Jedis连接的时候,判断jedis连接的有效性。,如果无效就将该对象销毁,然后重新borrow。4.wrap,将任意对象池化的时候,需要让对象支持一些对象池中的特定的一些特性,比如在对象池中,如果空闲对象超过了阀值并且超过了一定的时间,borrow的时候就清除掉对象,这个意味着池中的对象需要支持池化后的一些特性,主要是与生命状态相关的特性。那么这个wrap就是对象的包装类,有个默认的实现:
DefaultPooledObject
DefaultPooledObject
我们现在要开始使用Jedis连接工厂了
1 public class RedisClientImpl implements InitializingBean, RedisClient {
2
3 private final static Logger logger = LoggerFactory.getLogger(RedisClientImpl.class);
4
5 /** redis url */
6 private String url = null;
7 private int port = 6379;
8
9 /**
10 * The Max wait time.
11 */
12 private int maxWaitTime = 1000;
13
14 /** jedis池化 */
15 private GenericObjectPool<Jedis> jedisPool = null;
16
17 /**
18 * Instantiates a new Redis client.
19 */
20 public RedisClientImpl() {
21 }
22
23 /**
24 * Instantiates a new Redis client.
25 *
26 * @param url the url
27 * @param port the port
28 */
29 public RedisClientImpl(String url, int port){
30 setPort(port);
31 setUrl(url);
32 }
33
34 /**
35 * 不带异常的put数据
36 * @param key
37 * @param value
38 */
39 public void putobjWithExp(String key, Object value) {
40 Jedis jedis = null;
41 try {
42 jedis = getJedis();
43 jedis.set(key, JSON.toJSONString(value));
44 } catch (Exception e) {
45 logger.error("获取Jedis异常", e);
46 } finally {
47 if (jedis != null) {
48 returnJedis(jedis);
49 }
50 }
51 }
52
53 /**
54 * 获取jedis
55 * @return 从池中获取jedis
56 * @throws Exception
57 */
58 private Jedis getJedis() throws Exception {
59 LoggerUtils.info(logger, "borrow jedis,borrowwed=", jedisPool.getNumActive(), ",maxTotal=",
60 jedisPool.getMaxTotal());
61 return jedisPool.borrowObject(maxWaitTime);
62 }
63
64 /**
65 * 归还jedis
66 * @param jedis
67 */
68 private void returnJedis(Jedis jedis) {
69 LoggerUtils.info(logger, "return jedis,borrowwed=", jedisPool.getNumActive(), ",maxTotal=",
70 jedisPool.getMaxTotal());
71 jedisPool.returnObject(jedis);
72 }
73
74 /**
75 * Setter method for property <tt>port</tt>.
76 *
77 * @param port value to be assigned to property port
78 */
79 public void setPort(int port) {
80 this.port = port;
81 }
82
83 /**
84 * Setter method for property <tt>url</tt>.
85 *
86 * @param url value to be assigned to property url
87 */
88 public void setUrl(String url) {
89 this.url = url;
90 }
91
92 @Override
93 public void afterPropertiesSet() throws Exception {
94 LoggerUtils.info(logger, "开始初始化jedis池,url=", url, ",port=", port);
95 Assert.notNull(url);
96 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
97 poolConfig.setBlockWhenExhausted(true);
98 poolConfig.setMaxWaitMillis(100);
99 poolConfig.setLifo(false);
100 poolConfig.setMaxIdle(50); 最大空闲连接数
101 poolConfig.setMinIdle(20);// 最小空闲连接数
102 poolConfig.setMaxTotal(500);// 整个池的最大值,最大连接数
103 poolConfig.setTestOnBorrow(true);
104 poolConfig.setTestOnCreate(true);
105 poolConfig.setTestOnReturn(true);
106 poolConfig.setTestWhileIdle(false);
107 jedisPool = new GenericObjectPool<>(new JedisPooledFactory(url, port), poolConfig);
108 }
redis带连接池的客户端
这里jedis采用单机的形式。
首先是afterPropertiesSet方法,这里对jedis连接池做了一些配置,比如池的大小,borrow jedis连接的时候等待时间(borrow的时候采用的乐观锁),池中空闲对象超过多少的时候,return连接直接就销毁。。。等等
然后是putobjWithExp方法,这里首先是从池中borrow一个链接,如果池中没有的话,commons-pool会自动创建。然后获取到连接了以后,调用下jedis的set方法,将数据保存。
这里采用的是fastJson来做序列化的,保存的内容也是String格式的。 而tair是支持自定义序列化工具的,而且它的序列化是
最后,将jedis连接归还到pool去就好啦。
除了对象复用以外,其实还没有提到一个很重要的使用对象池的原因:
对于连接池的场景而言,连接是有限的资源,不采用池化,那么无法对资源的分配进行管理。
打个比方,不采用连接池,每个请求进来生成一个连接,那么如果突然某个业务请求量递增,直接导致连接数都被该系统占用了。但是采用了连接池,不仅仅可以对象复用,同时还能做资源的管控。
测试:
测试环境:10个线程,同时采用jedispool和非jedispool的方式向redis put数据,put的数据一样,一共put 50000次,以下是测试结果。
带jedis连接池的--->任务执行完毕,执行时间5055ms,共有50000个任务,执行异常0次
不带连接池的--->任务执行完毕,执行时间14654ms,共有50000个任务,执行异常0次
效果还是很明显的。后续还可以增加对连接池的定时任务监控等~~~