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組件,可以有效地減少花在處理對象池化上的工作量,進而,向其它重要的工作里,投入更多的時間和精力。
