redis的并发竞争问题如何解决?
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。
对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;
这里我要提到的是jedis的池化,即jedisPool
对象的池化技术:
我们都知道一个对象比如car(对象)在其生命周期大致可氛围”创建”,”使用”,”销毁”三大阶段,那么它的总时间就是T1(创建)+T2(使用)+T3(销毁),如果创建N个对象都需要这样的步骤的话是非常耗性能的,就算JVM对垃圾回收机制有优化,但”创建”和”销毁”多少总会占用部分资源,那么我们就会想能否像常量池那样,让对象可复用,从而减少T1和T3所消耗的资源呢?这就引出了我们今天的内容—–对象池化技术即ObjectPool (jedis也是一个Object对象,我们下面先介绍对象池)
官网对对象池的解释是:
将用过的对象保存起来,等下次需要这种对象的时候再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销,用于充当保存对象的”容器”对象,被称为”对象池”。
对于没有状态的对象,String,在重复使用之前,无需进行任何处理,对于有状态的对象如StringBuffer,在重复使用之前,需要恢复到同等于生成的状态,如果恢复不了的话,就只能丢掉,改用创建的实例了。并非所有对象都可以用来池化,因为维护池也需要开销,对生成时开销不大的对象进行池化,它可能降低性能。
在上述的对对象池的描述来看,在看源码之前我问了自己一个问题,如果让你来做对象池你觉的应该注意什么?
我们举个例子,所谓对象池嘛肯定是管理对象的,我们将对象看成是一台公共自行车,将对象池比作一个自行车站点。那么我们来想象着,我们怎么可以设计对象池
首先对于这个站点的功能我们猜想,肯定有”借”自行车,”还”自行车,定期”检查”自行车车况,”销毁”(坏了的,拿去修或者销毁)这几个基本的功能。
那对于自行车呢?站点肯定不能自己造自行车,肯定需要一个自行车工厂去维护自行车,那么自然我们想想这个工厂会有哪些功能
首先作为工厂,那么肯定会有”生产”功能,”检测”功能,”出库”功能,”销毁”功能。
有了上述的假象之后,我们带着这些概念,看看真正的代码是怎么设计的,是不是跟我想的有点类似,下面我们就来看源代码
PooledObjectFactory、ObjectPool和ObjectPoolFactory
在Pool组件中,对象池化的工作被划分给了三类对象:
- PooledObjectFactory用于管理被池化的对象的产生、激活、挂起、校验和销毁;
- ObjectPool用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作;
- ObjectPoolFactory则用于大量生成相同类型和设置的ObjectPool。
相应地,使用Pool组件的过程,也大体可以划分成“创立PooledObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三步。
PooledObjectFactory
是一个接口,因为工程可以多多种工厂,自行车,客车,卡车等等。该接口jdk没有默认实现,需要自己实现。点开对象的工厂,我惊奇的发现,完全验证了我的猜想
只是在基础功能的基础上,多增加了一些其他的功能罢了。
import org.apache.commons.pool.PoolableObjectFactory; // 1) 创建一个实现了PoolableObjectFactory接口的类。 public class PoolableObjectFactorySample implements PoolableObjectFactory { private static int counter = 0; // 2) 为这个类添加一个Object makeObject()方法。这个方法用于在必要时产生新的对象。 public Object makeObject() throws Exception { Object obj = String.valueOf(counter++); System.err.println("Making Object " + obj); return obj; } // 3) 为这个类添加一个void activateObject(Object obj)方法。这个方法用于将对象“激活”——设置为适合开始使用的状态。 public void activateObject(Object obj) throws Exception { System.err.println("Activating Object " + obj); } // 4) 为这个类添加一个void passivateObject(Object obj)方法。这个方法用于将对象“挂起”——设置为适合开始休眠的状态。 public void passivateObject(Object obj) throws Exception { System.err.println("Passivating Object " + obj); } // 5) 为这个类添加一个boolean validateObject(Object obj)方法。这个方法用于校验一个具体的对象是否仍然有效,已失效的对象会被自动交给destroyObject方法销毁。 public boolean validateObject(Object obj) { /* 以1/2的概率将对象判定为失效 */ boolean result = (Math.random() > 0.5); System.err.println("Validating Object " + obj + " : " + result); return result; } // 6) 为这个类添加一个void destroyObject(Object obj)方法。这个方法用于销毁被validateObject判定为已失效的对象。 public void destroyObject(Object obj) throws Exception { System.err.println("Destroying Object " + obj); } }
ObjectPool
有了合适的PooledObjectFactory之后,便可以开始请出ObjectPool来与之同台演出了。
ObjectPool是在org.apache.commons.pool包中定义的一个接口,实际使用的时候也需要利用这个接口的一个具体实现。Pool组件本身包含了若干种现成的ObjectPool实现,
StackObjectPool :
StackObjectPool利用一个java.util.Stack对象来保存对象池里的对象。这种对象池的特色是:
- 可以为对象池指定一个初始的参考大小(当空间不够时会自动增长)。
- 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
- 可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后,再向池里送回的对象会被自动送去回收。
SoftReferenceObjectPool
SoftReferenceObjectPool利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。这种对象池的特色是:
- 可以保存任意多个对象,不会有容量已满的情况发生。
- 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
- 可以在初始化同时,在池内预先创建一定量的对象。
- 当内存不足的时候,池中的对象可以被Java虚拟机回收。
GenericObjectPool—-jedis就是基于这个的
GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是:
- 可以设定最多能从池中借出多少个对象。
- 可以设定池中最多能保存多少个对象。
- 可以设定在池中已无对象可借的情况下,调用它的borrowObject方法时的行为,是等待、创建新的实例还是抛出异常。
- 可以分别设定对象借出和还回时,是否进行有效性检查。
- 可以设定是否使用一个单独的线程,对池内对象进行后台清理。
可以直接利用。如果都不合用,也可以根据情况自行创建。具体的创建方法,可以参看Pool组件的文档和源码。
我这边总结了下,对代码做了一个简化的修改
我们可以看到,这个对象池就跟我们之前猜想的站点的功能一样,主要的方法就是一个借borrowObject(),还returnObject()功能。
import org.apache.commons.pool.ObjectPool; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.StackObjectPool; public class ObjectPoolSample { public static void main(String[] args) { Object obj = null; // 1) 生成一个要用的PoolableObjectFactory类的实例。 PoolableObjectFactory factory = new PoolableObjectFactorySample(); // 2) 利用这个PoolableObjectFactory实例为参数,生成一个实现了ObjectPool接口的类(例如StackObjectPool)的实例,作为对象池。 ObjectPool pool = new StackObjectPool(factory); try { for(long i = 0; i < 100 ; i++) { System.out.println("== " + i + " =="); // 3) 需要从对象池中取出对象时,调用该对象池的Object borrowObject()方法。 obj = pool.borrowObject(); System.out.println(obj); // 4) 需要将对象放回对象池中时,调用该对象池的void returnObject(Object obj)方法。 pool.returnObject(obj); } obj = null;//明确地设为null,作为对象已归还的标志 } catch (Exception e) { e.printStackTrace(); } finally { try{ if (obj != null) {//避免将一个对象归还两次 pool.returnObject(obj); } // 5) 当不再需要使用一个对象池时,调用该对象池的void close()方法,释放它所占据的资源。 pool.close(); } catch (Exception e){ e.printStackTrace(); } } } }
ObjectPoolFactory
有时候,要在多处生成类型和设置都相同的ObjectPool。如果在每个地方都重写一次调用相应构造方法的代码,不但比较麻烦,而且日后修改起来,也有所不便。这种时候,正是使用ObjectPoolFactory的时机。
ObjectPoolFactory是一个在org.apache.commons.pool中定义的接口,它定义了一个称为ObjectPool createPool()方法,可以用于大量生产类型和设置都相同的ObjectPool。
public static void main(String[] args) { Object obj = null; PoolableObjectFactory factory = new PoolableObjectFactorySample(); ObjectPoolFactory poolFactory = new StackObjectPoolFactory(factory); ObjectPool pool = poolFactory.createPool(); try { for(long i = 0; i < 100 ; i++) { System.out.println("== " + i + " =="); obj = pool.borrowObject(); System.out.println(obj); pool.returnObject(obj); } obj = null; } catch (Exception e) { e.printStackTrace(); } finally { try{ if (obj != null) { pool.returnObject(obj); } pool.close(); } catch (Exception e){ e.printStackTrace(); } } }
结束语
恰当地使用对象池化,可以有效地降低频繁生成某些对象所造成的开销,从而提高整体的性能。而借助Apache Commons Pool组件,可以有效地减少花在处理对象池化上的工作量,进而,向其它重要的工作里,投入更多的时间和精力。