序言: 因爲ssdb(一種nosql數據庫) java客戶端沒有實現連接池,需要模仿jdbc裏面的連接池的思想,實現一個簡單的連接池,以下是自己的總結:
思想: 每new 一個SSDB實例 ,用完之後close,其實底層就是開了一個socket連接,又關閉了,這其實是相當的消耗資源的,所以連接池的作用就是重用。不是直接關閉而是放到一個容器中去,下次再用的時候,直接從這個容器中拿。那麼要點以下幾點:
- 容器的選擇:是LinkList,,它不同於ArrayList,LinkList是一種線性的結構保持FIFO,相當於一個隊列,優點是可以實現快速的插入和移除。
- 初始化連接數:可以在第一次初始化的時候就先創建了一定數量的連接,這其實是比較大的開銷,但是爲後面的快速調用是非常值得的。
- 最大連接數:當併發很大的時候,就需要大量的鏈接,當從池子裏面取不到連接時,就會創建新的鏈接,任何機器都是有它的極限值,爲了不至於把資源耗完,我們就只能限制這個極限值(最大連接數),一旦大於這個極限值的時候,我就拒絕提供鏈接,直接拋出異常(當然這裏可以有很多種處理方式,比如阻塞進程,讓它等待,直到超時)。
- 超時,最小空閒數:當一個鏈接在隊列中空閒着,超過一定的時間,就會把它銷燬掉,但是還是會保留一個最小的空閒數量(我這裏沒有實現)。
package ssdb.datasource; import com.udpwork.ssdb.SSDB; import java.util.LinkedList; /** * Desc: * User: weiguili([email protected]) * Date: 14-1-15 * Time: 上午9:18 */ public class DataSource { private String host = "192.168.98.250"; private int port = 9966; private int timeoutMs = 5000; private int minIdle = 10;//最小空閒數 private int iniCount = 5;//初始化連接 private int maxCount = 10;//最大連接數 int currentCount = 0;//當前連接數 LinkedList<SSDB> ssdbConnectionsPool = new LinkedList<SSDB>(); public DataSource() { try { for(int i=0;i<iniCount;i++){ currentCount++; ssdbConnectionsPool.addLast(createSSDB(null)); } } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public SSDB createSSDB(ThreadLocal<SSDB> cacheSSDB) throws Exception { return new MySSDB(host,port,timeoutMs,this,cacheSSDB); } public SSDB getSSDB(ThreadLocal<SSDB> cacheSSDB) throws Exception{ synchronized (ssdbConnectionsPool){ if(ssdbConnectionsPool.size()>0){ return ssdbConnectionsPool.removeFirst(); } if(currentCount<maxCount){ currentCount++; return createSSDB(cacheSSDB); } throw new Exception("沒有連接了"); } } }
- 覆蓋close方法:這裏爲了不改變用戶的使用習慣,我需要覆蓋原來的SSDB中的close方法,我這裏使用的是繼承(在這種情況下,我的靈活性要求不大,JDBC裏面用的是代理,最好是動態代理,實際上體現的是組合優於繼承思想)。
package ssdb.datasource; import com.udpwork.ssdb.SSDB; /** * Desc: * User: weiguili([email protected]) * Date: 14-1-15 * Time: 上午10:02 */ public class MySSDB extends SSDB { private DataSource dataSource; private int reusedCount = 20;//可重用次數 private int currentReusedCount = 0;//當前重用次數 private ThreadLocal<SSDB> cacheSSDB = null; MySSDB(String host,int port,int timeoutMs, DataSource dataSource,ThreadLocal<SSDB> cacheSSDB) throws Exception{ super(host,port,timeoutMs); this.dataSource = dataSource; this.cacheSSDB = cacheSSDB; } @Override public void close() { currentReusedCount++; if(currentReusedCount<reusedCount){ this.dataSource.ssdbConnectionsPool.addLast(this); }else { this.dataSource.currentCount--; if(cacheSSDB !=null && cacheSSDB.get() !=null) cacheSSDB.remove();//當連接關閉時,把緩存也幹掉 super.close(); } } }
- SSDBUtil,ThreadLocal:最後是需要提供一個工具類SSDBUtil,目的是把對連接池的使用,都放到一個靜態域裏面去,實際上就是讓它實現單例。在這裏有一點非常關鍵,ThreadLocal是個線程的本地變量,在這裏把每個SSDB包一下放進去可以起到線程之間隔離開來,各自處理自己的連接,避免了資源的搶佔,是解決併發的一種解決方案。
package ssdb; import com.udpwork.ssdb.SSDB; import ssdb.datasource.DataSource; /** * Desc: * User: weiguili([email protected]) * Date: 14-1-15 * Time: 上午10:42 */ public class SSDBUtil { private static DataSource dataSource = new DataSource(); static ThreadLocal<SSDB> cacheSSDB = new ThreadLocal<SSDB>();//包一下,線程之間隔離 public static SSDB getSSDB() throws Exception{ if(cacheSSDB.get() != null){ return cacheSSDB.get(); } SSDB ssdb = dataSource.getSSDB(cacheSSDB); cacheSSDB.set(ssdb); return ssdb; } /*public static SSDB getSSDB() throws Exception{ return dataSource.getSSDB(cacheSSDB); }*/ }
- 最後附上一張連接池的原理圖:
- 示例代碼:
package ssdb; import com.udpwork.ssdb.*; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * SSDB Java client SDK demo. */ public class Demo { public static void main(String[] args) throws Exception { SSDB ssdb = null; Response resp; byte[] b; ssdb = SSDBUtil.getSSDB(); /* kv */ System.out.println("---- kv -----"); //ssdb.set("a", "123"); ssdb.set("a", "122"); b = ssdb.get("a"); System.out.println(new String(b)); ssdb.del("a"); b = ssdb.get("a"); ssdb.set("a","90"); System.out.println(b); long incr = ssdb.incr("a", 10); System.out.println("-------increment the number by--------"); System.out.println(incr); resp = ssdb.scan("", "", 10); resp.print(); resp = ssdb.rscan("", "10000", 10); resp.print(); System.out.println(""); /* hashmap */ System.out.println("---- hashmap -----"); ssdb.hset("n", "a", "123"); b = ssdb.hget("n", "a"); System.out.println(new String(b)); ssdb.hdel("n", "a"); b = ssdb.hget("n", "a"); System.out.println(b); ssdb.hincr("n", "a", 10); ssdb.hset("n", "d", "124"); ssdb.hset("n", "c", "124"); ssdb.hset("n", "b", "124"); resp = ssdb.hscan("n", "a", "z", 10); //resp = ssdb.hrscan("n", "", "", 10); resp.print(); System.out.println(""); /* zset */ System.out.println("---- zset -----"); double d; ssdb.zset("hackers", "Alan Kay", 1940); ssdb.zset("hackers", "Richard Stallman", 1953); ssdb.zset("hackers", "Yukihiro Matsumoto", 1965); ssdb.zset("hackers", "Claude Shannon", 1916); ssdb.zset("hackers", "Linus Torvalds", 1999); ssdb.zset("hackers", "Alan Turing", 1912); ssdb.zset("n", "a", 1); d = ssdb.zget("n", "a"); System.out.println(d); ssdb.zdel("n", "a"); d = ssdb.zget("n", "a"); System.out.println(d); ssdb.zincr("n", "b", 10); //resp = ssdb.zscan("hackers", "", Double.valueOf(1912), Double.valueOf(1999), 10); resp = ssdb.zscan("test", "", null, Double.MAX_VALUE, 10); resp.print(); System.out.println(resp.items); System.out.println(resp.keys); System.out.println(resp.raw); System.out.println(""); /* multi */ ssdb.multi_set("a", "1b", "b", "2b"); resp = ssdb.multi_get("a", "b"); resp.print(); System.out.println(""); // ssdb.close(); concurrentTest(); } public static void concurrentTest(){ Executor pool = Executors.newFixedThreadPool(50); for(int i=0;i<1000;i++){ Runnable task = new Runnable() { @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); SSDB ssdb = null; try { ssdb = SSDBUtil.getSSDB(); System.out.println(ssdb); } catch (Exception e) { e.printStackTrace(); } finally { ssdb.close(); //ssdb.close(); } } } }; pool.execute(task); } } }