[toc]
1. 摘要
客戶端連接HBase,常用的方式主要有兩種,直連Zookeeper和連接HBase Thrift Server。第一種是Java中常用的方式,官方在hbase-client包里提供了豐富的API,另一種是HBase的thrift api,主要在跨語言環(huán)境中使用。
我們線上大部分的業(yè)務(wù)是由happybase封裝的Python API來提供數(shù)據(jù)的讀寫服務(wù),小部分業(yè)務(wù)則是用Java語言,例如:Flink實(shí)時(shí)作業(yè)中的HBaseSink。
實(shí)時(shí)程序讀寫HBase最開始使用的是hbase-client提供的API,隨著越來越多實(shí)時(shí)業(yè)務(wù)的上線,被占用的zookeeper的連接資源也隨之增加,甚至在有些極端場(chǎng)景下(也有可能是程序BUG),ZK的連接會(huì)被迅速消耗,導(dǎo)致HBase服務(wù)拒絕連接,流作業(yè)無法正常拉起。
基于上述因素,最后決定在Java環(huán)境中也使用HBase Thrift的api。但是,如果直接使用HBase Thrift提供的API讀寫HBase,你大概率會(huì)遇見如下問題:
- TScoket連接對(duì)象被頻繁創(chuàng)建,大量消耗內(nèi)存等系統(tǒng)資源
- TScoket連接對(duì)象被頻繁創(chuàng)建,如果忘記關(guān)閉時(shí),會(huì)造成本地的短連接過多
- TScoket連接對(duì)象超時(shí)不用,被服務(wù)端斷開,客戶端再次使用時(shí)拋異常
因此,為了解決直接使用thrift api潛在的風(fēng)險(xiǎn),我們需要為thrift api實(shí)現(xiàn)連接池,連接池應(yīng)該至少具有以下功能。
- 能夠設(shè)置最大連接數(shù),最小連接數(shù),連接可以自動(dòng)創(chuàng)建
- 可以設(shè)置連接的空閑時(shí)間,并定時(shí)檢測(cè)連接,達(dá)到空閑時(shí)間后,連接自動(dòng)釋放,然后重新創(chuàng)建
以下內(nèi)容將記錄如何使用commons-pool2來實(shí)現(xiàn)HBase Thrift API的池化 ,commons-pool2比較典型的應(yīng)用場(chǎng)景應(yīng)該就是jedis啦,在我的設(shè)計(jì)中,也會(huì)或多或少參考jedis的源碼實(shí)現(xiàn),既然有造好的輪子,肯定選擇優(yōu)先使用。
出于文章的完整性考慮,第2小節(jié)中記錄了commons-pool2的一些基礎(chǔ)知識(shí),使得文章略顯冗余。大家可以直接跳讀到自己感興趣的段落。
2. commons-pool2
commons-pool2是Apache的開源的一個(gè)優(yōu)秀的對(duì)象池化組件,它對(duì)對(duì)象池化操作進(jìn)行了很好的封裝,基于此,我們只需要根據(jù)自己的業(yè)務(wù)需求重寫或?qū)崿F(xiàn)部分接口,就可以快速創(chuàng)建一個(gè)方便、簡(jiǎn)單、強(qiáng)大的對(duì)象連接池管理類。
2.1 GenericObjectPool
這個(gè)是對(duì)象池實(shí)現(xiàn)的核心類,它實(shí)現(xiàn)了對(duì)對(duì)象池的管理,是一個(gè)基本的對(duì)象池實(shí)現(xiàn),一般情況下,我們可以直接使用。在使用這個(gè)類的時(shí)候,我們需要傳入兩個(gè)重要的參數(shù):GenericObjectPoolConfig類和PooledObjectFactory接口的實(shí)現(xiàn)。
在GenericObjectPool中,有兩個(gè)我們會(huì)用到的方法:
// 對(duì)象使用完之后,歸還到對(duì)象池
@Override
public void returnObject(final T obj) {
.....
}
// 從對(duì)象池中獲取一個(gè)對(duì)象
@Override
public T borrowObject() throws Exception {
return borrowObject(getMaxWaitMillis());
}
其它還有一些方法,比如關(guān)閉對(duì)象池,銷毀對(duì)象池,獲取對(duì)象池中空閑的對(duì)象個(gè)數(shù)等,可以自行查看相關(guān)API,https://www.javadoc.io/doc/org.apache.commons/commons-pool2/2.5.0
2.2 PooledObjectFactory接口
這個(gè)接口是我們要實(shí)現(xiàn)的,它對(duì)要實(shí)現(xiàn)對(duì)象池化的對(duì)象做了一些管理。這個(gè)工廠接口就是為了讓我們根據(jù)自己的業(yè)務(wù)創(chuàng)建和管理要對(duì)象池化的對(duì)象。也可以繼承默認(rèn)的抽象類:BasePooledObjectFactory
PooledObject<T> makeObject() throws Exception;
這個(gè)方法是用來創(chuàng)建一個(gè)對(duì)象,當(dāng)在GenericObjectPool類中調(diào)用borrowObject方法時(shí),如果當(dāng)前對(duì)象池中沒有空閑的對(duì)象,GenericObjectPool會(huì)調(diào)用這個(gè)方法,創(chuàng)建一個(gè)對(duì)象,并把這個(gè)對(duì)象封裝到PooledObject類中,并交給對(duì)象池管理。
在連接池初始化時(shí),初始化最小連接數(shù),清除線程清除完過期連接后,池中連接數(shù)<最小連接數(shù),需要調(diào)用此方法重新生成有效連接,使連接數(shù)達(dá)到池中最小連接數(shù)。獲取新的連接時(shí),池中連接對(duì)象均被占用,但當(dāng)前連接數(shù)<最大連接數(shù)時(shí),需要調(diào)用該方法產(chǎn)生一個(gè)新的連接。
void destroyObject(PooledObject<T> p) throws Exception;
銷毀對(duì)象,當(dāng)對(duì)象池檢測(cè)到某個(gè)對(duì)象的空閑時(shí)間(idle)超時(shí),或使用完對(duì)象歸還到對(duì)象池之前被檢測(cè)到對(duì)象已經(jīng)無效時(shí),就會(huì)調(diào)用這個(gè)方法銷毀對(duì)象。
對(duì)象的銷毀一般和業(yè)務(wù)相關(guān),但必須明確的是,當(dāng)調(diào)用這個(gè)方法之后,對(duì)象的生命周期必須結(jié)束。如果是對(duì)象是線程,線程必須已結(jié)束,如果是socket,socket必須已close,如果是文件操作,文件數(shù)據(jù)必須已flush,且文件正常關(guān)閉。
對(duì)于實(shí)現(xiàn)這個(gè)方法來說非常重要的是要考慮到處理異常情況,另外實(shí)現(xiàn)必須考慮一個(gè)實(shí)例如果與垃圾回收器失去聯(lián)系那么永遠(yuǎn)不會(huì)被銷毀。
boolean validateObject(PooledObject<T> p);
檢測(cè)一個(gè)對(duì)象是否有效。在對(duì)象池中的對(duì)象必須是有效的,這個(gè)有效的概念是,從對(duì)象池中拿出的對(duì)象是可用的。比如,如果是socket,那么必須保證socket是連接可用的。在從對(duì)象池獲取對(duì)象或歸還對(duì)象到對(duì)象池時(shí),有相應(yīng)的參數(shù)配置,決定是否會(huì)調(diào)用這個(gè)方法,判斷對(duì)象是否有效,如果無效就會(huì)銷毀。
void activateObject(PooledObject<T> p) throws Exception;
激活一個(gè)對(duì)象或者說啟動(dòng)對(duì)象的某些操作。比如,如果對(duì)象是socket,如果socket沒有連接,或意外斷開了,可以在這里啟動(dòng)socket的連接。它會(huì)在檢測(cè)空閑對(duì)象的時(shí)候,如果設(shè)置了測(cè)試空閑對(duì)象是否可以用,就會(huì)調(diào)用這個(gè)方法,在borrowObject的時(shí)候也會(huì)調(diào)用。另外,如果對(duì)象是一個(gè)包含參數(shù)的對(duì)象,可以在這里進(jìn)行初始化。讓使用者感覺這是一個(gè)新創(chuàng)建的對(duì)象一樣。
void passivateObject(PooledObject<T> p) throws Exception;
鈍化一個(gè)對(duì)象。在向?qū)ο蟪貧w還一個(gè)對(duì)象是會(huì)調(diào)用這個(gè)方法。這里可以對(duì)對(duì)象做一些清理操作。比如清理掉過期的數(shù)據(jù),下次獲得對(duì)象時(shí),不受舊數(shù)據(jù)的影響。
一般來說activateObject和passivateObject是成對(duì)出現(xiàn)的。前者是在對(duì)象從對(duì)象池取出時(shí)做一些操作,后者是在對(duì)象歸還到對(duì)象池做一些操作,可以根據(jù)自己的業(yè)務(wù)需要進(jìn)行取舍。
2.3 BasePooledObjectFactory類
這個(gè)抽象類是 PooledObjectFactory 接口的空現(xiàn),并且透出了兩個(gè)抽象方法必須實(shí)現(xiàn)。
public abstract T create() throws Exception;
創(chuàng)建一個(gè)對(duì)象實(shí)例,可以被wrap成一個(gè)PooledObject,這個(gè)方法必須支持并發(fā)和多線程。
public abstract PooledObject<T> wrap(T obj);
把一個(gè)對(duì)象包裝為一個(gè)PooledObject,此方法只在調(diào)用borrowObject方法的時(shí)候,且返回一個(gè)全新對(duì)象的時(shí)候執(zhí)行,此方法處理create()方法的返回值。常見的處理方式是new DefaultPooledObject<>(obj)??梢栽诎b前進(jìn)行其他邏輯的處理。
2.4 GenericKeyedObjectPool帶Key的對(duì)象池
這種對(duì)象池和前面的GenericObjectPool對(duì)象池操作是一樣的,不同的是對(duì)應(yīng)的每個(gè)方法帶一個(gè)key參數(shù)。你可以把這個(gè)GenericKeyedObjectPool的對(duì)象池看作是一個(gè)map的GenericObjectPool,每個(gè)key對(duì)應(yīng)一個(gè)GenericObjectPool。它用于區(qū)別不同類型的對(duì)象。比如數(shù)據(jù)庫連接,有可能會(huì)連接到不同地址的數(shù)據(jù)庫上面。就可以用這個(gè)區(qū)分。
2.5 GenericObjectPoolConfig參數(shù)配置類
這個(gè)類允許使用者對(duì)對(duì)象池的一些參數(shù)進(jìn)行調(diào)整,根據(jù)需要定制對(duì)象池。

lifo:對(duì)象池存儲(chǔ)空閑對(duì)象是使用的LinkedBlockingDeque,它本質(zhì)上是一個(gè)支持FIFO和FILO的雙向的隊(duì)列,common-pool2中的LinkedBlockingDeque不是Java原生的隊(duì)列,而有common-pool2重新寫的一個(gè)雙向隊(duì)列。如果為true,表示使用FIFO獲取對(duì)象。默認(rèn)值是true,建議使用默認(rèn)值。
fairness:common-pool2實(shí)現(xiàn)的LinkedBlockingDeque雙向阻塞隊(duì)列使用的是Lock鎖。這個(gè)參數(shù)就是表示在實(shí)例化一個(gè)LinkedBlockingDeque時(shí),是否使用lock的公平鎖。默認(rèn)值是false,建議使用默認(rèn)值。
maxWaitMillis:當(dāng)沒有空閑連接時(shí),獲取一個(gè)對(duì)象的最大等待時(shí)間。如果這個(gè)值小于0,則永不超時(shí),一直等待,直到有空閑對(duì)象到來。如果大于0,則等待maxWaitMillis長(zhǎng)時(shí)間,如果沒有空閑對(duì)象,將拋出NoSuchElementException異常。默認(rèn)值是-1;可以根據(jù)需要自己調(diào)整,單位是毫秒。
minEvictableIdleTimeMillis:對(duì)象最小的空閑時(shí)間。如果為小于等于0,是Long的最大值,如果大于0,當(dāng)空閑的時(shí)間大于這個(gè)值時(shí),執(zhí)行移除這個(gè)對(duì)象操作。默認(rèn)值是1000L * 60L * 30L;即30分鐘。這個(gè)參數(shù)是強(qiáng)制性的,只要空閑時(shí)間超過這個(gè)值,就會(huì)移除。
softMinEvictableIdleTimeMillis:對(duì)象最小的空間時(shí)間,如果小于等于0,取Long的最大值,如果大于0,當(dāng)對(duì)象的空閑時(shí)間超過這個(gè)值,并且當(dāng)前空閑對(duì)象的數(shù)量大于最小空閑數(shù)量(minIdle)時(shí),執(zhí)行移除操作。這個(gè)和上面的minEvictableIdleTimeMillis的區(qū)別是,它會(huì)保留最小的空閑對(duì)象數(shù)量。而上面的不會(huì),是強(qiáng)制性移除的。默認(rèn)值是-1;
numTestsPerEvictionRun:檢測(cè)空閑對(duì)象線程每次檢測(cè)的空閑對(duì)象的數(shù)量。默認(rèn)值是3;如果這個(gè)值小于0,則每次檢測(cè)的空閑對(duì)象數(shù)量等于當(dāng)前空閑對(duì)象數(shù)量除以這個(gè)值的絕對(duì)值,并對(duì)結(jié)果向上取整。
testOnCreate:在創(chuàng)建對(duì)象時(shí)檢測(cè)對(duì)象是否有效,true是,默認(rèn)值是false,一般新建對(duì)象都是有效的,所以建議為false。
testOnBorrow:在從對(duì)象池獲取對(duì)象時(shí)是否檢測(cè)對(duì)象有效,true是;默認(rèn)值是false,盡量為false,每次獲取對(duì)象都需要檢測(cè)對(duì)象是否可用,會(huì)產(chǎn)生多余的網(wǎng)絡(luò)開銷,對(duì)性能有所影響。
testOnReturn:在向?qū)ο蟪刂袣w還對(duì)象時(shí)是否檢測(cè)對(duì)象有效,true是,默認(rèn)值是false。
testWhileIdle:在檢測(cè)空閑對(duì)象線程檢測(cè)到對(duì)象不需要移除時(shí),是否檢測(cè)對(duì)象的有效性。true是,默認(rèn)值是false。
timeBetweenEvictionRunsMillis:空閑對(duì)象檢測(cè)線程的執(zhí)行周期,即多長(zhǎng)時(shí)候執(zhí)行一次空閑對(duì)象檢測(cè)。單位是毫秒數(shù)。如果小于等于0,則不執(zhí)行檢測(cè)線程。默認(rèn)值是-1;
blockWhenExhausted:當(dāng)對(duì)象池沒有空閑對(duì)象時(shí),新的獲取對(duì)象的請(qǐng)求是否阻塞。true阻塞。默認(rèn)值是true;
maxTotal:對(duì)象池中管理的最多對(duì)象個(gè)數(shù)。默認(rèn)值是8。
maxIdle:對(duì)象池中最大的空閑對(duì)象個(gè)數(shù)。默認(rèn)值是8。
minIdle:對(duì)象池中最小的空閑對(duì)象個(gè)數(shù)。默認(rèn)值是0。
以上就是common-pool2對(duì)象池的配置參數(shù),使用的時(shí)候可以根據(jù)自己的需要進(jìn)行調(diào)整。這些參數(shù)更詳細(xì)的說明以及調(diào)優(yōu)建議,可以參考阿里云數(shù)據(jù)庫Redis的實(shí)踐文檔: https://help.aliyun.com/document_detail/98726.html
上述內(nèi)容的參考鏈接:
https://blog.csdn.net/u_ascend/article/details/80594306https://help.aliyun.com/document_detail/98726.html
3. HBase Thrift 客戶端連接池的實(shí)現(xiàn)
這里只記錄連接池實(shí)現(xiàn)的思路與關(guān)鍵代碼的實(shí)現(xiàn),完整的源碼和測(cè)試用例,可以參考hbase-sdk項(xiàng)目中的hbase-sdk-thrift-core模塊,功能上的不足和建議也希望大家踴躍提issue。項(xiàng)目的地址:
https://gitee.com/weixiaotome/hbase-sdk
https://github.com/CCweixiao/hbase-sdk
3.1 連接池核心實(shí)現(xiàn)類的UML圖

上圖大致列舉了連接池實(shí)現(xiàn)時(shí)所需的類與接口,及其之間的各種繼承和組合關(guān)系。整個(gè)功能的實(shí)現(xiàn)基于commons-pool2組件中幾個(gè)核心的接口與類,代碼簡(jiǎn)潔,邏輯清晰,無需在此處浪費(fèi)過多筆墨,粘貼所有代碼。
Pool<T>是一個(gè)泛型抽象類,其核心功能是在池中產(chǎn)生托管對(duì)象和歸還對(duì)象到池中,其初始化時(shí)需要提供GenericObjectPoolConfig的對(duì)象來設(shè)置連接池的參數(shù),同時(shí),傳遞PooledObjectFactory<T>的工廠對(duì)象來提供連接創(chuàng)建等的具體方法。
HBaseThriftFactory實(shí)現(xiàn)了PooledObjectFactory<T>所提供的接口,請(qǐng)重點(diǎn)關(guān)注以下三個(gè)核心方法:
PooledObject<HBaseThrift> makeObject();
destroyObject(PooledObject<HBaseThrift> pooledObject);
validateObject(PooledObject<HBaseThrift> pooledObject);
HBaseThriftConnection被HBaseThriftClient繼承,HBaseThriftConnection與HBaseThriftTSocketFactory
接口的組合,實(shí)現(xiàn)了TSocket的聲明周期管理,HBaseThriftTSocketFactory接口中提供了TSocket的聲明方法:
TSocket createTSocket() throws HBaseThriftTSocketException;
該方法在DefaultHBaseThriftTSocketFactory類中被具體實(shí)現(xiàn):
@Override
public TSocket createTSocket() throws HBaseThriftTSocketException {
TSocket socket = new TSocket(getHost(), getPort());
socket.setConnectTimeout(getConnectionTimeout());
try {
socket.open();
socket.setSocketTimeout(getSocketTimeout());
return socket;
} catch (TTransportException ex) {
socket.close();
throw new HBaseThriftTSocketException("The TSocket of HBase thrift server create or open failed", ex);
}
}
HBaseThrift類中融合了連接的創(chuàng)建以及各類讀寫HBase API的實(shí)現(xiàn),在這個(gè)類中,需要重點(diǎn)關(guān)注,連接獲取、銷毀與歸還等的邏輯。
如果你不想使用連接池,則可以直接直接創(chuàng)建這個(gè)類的對(duì)象,然后用其API來操作HBase。以上只是簡(jiǎn)單梳理了幾個(gè)核心類大致的功能和關(guān)系,更細(xì)節(jié)的實(shí)現(xiàn),感興趣的伙伴可以參考hbase-sdk中的thrift API模塊的源碼。
3.2 快速使用
以下內(nèi)容將從實(shí)例入手,主要敘述連接池的核心功能,以及一些需要重點(diǎn)關(guān)注的地方。
@Test
public void testPut(){
// 聲明連接池的配置對(duì)象
HBaseThriftPoolConfig config = new HBaseThriftPoolConfig();
// 創(chuàng)建HBase Thrift連接池
HBaseThriftPool hBaseThriftPool = new HBaseThriftPool(config, "localhost", 9090);
// 從連接池中獲取到HBaseThrift對(duì)象,HBaseThrift中封裝了對(duì)HBase的讀寫操作
final HBaseThrift hBaseThrift = hBaseThriftPool.getResource();
Map<String, String> data = new HashMap<>();
data.put("info:name", "leo");
data.put("info:age", "18");
data.put("info:address", "shanghai");
// 保存數(shù)據(jù)
hBaseThrift.save("leo_test", "a10002", data);
// 關(guān)閉hBaseThrift對(duì)象,關(guān)閉即把該對(duì)象放回連接池中
hBaseThrift.close();
}
HBaseThriftPoolConfig繼承GenericObjectPoolConfig,針對(duì)HBase Thrift服務(wù)端的配置以及所需需求,設(shè)置對(duì)象池的參數(shù),以覆蓋原有的參數(shù)。
public class HBaseThriftPoolConfig extends GenericObjectPoolConfig {
public HBaseThriftPoolConfig() {
// 連接池中的最大連接數(shù),默認(rèn)8,根據(jù)服務(wù)端可以容納的最大連接數(shù)和當(dāng)前并發(fā)數(shù)進(jìn)行合理設(shè)置
setMaxTotal(1);
// 連接池中確保的最少空閑連接數(shù)
setMinIdle(1);
// 連接池中允許的最大空閑連接數(shù)
setMaxIdle(1);
// 連接池用盡后,調(diào)用者是否等待,為true時(shí),maxWaitMillis才生效
setBlockWhenExhausted(true);
// 連接池用盡后,調(diào)用者的最大等待時(shí)間,毫秒,默認(rèn)-1,表示永不超時(shí)
setMaxWaitMillis(6000);
// 每次從資源池中拿/歸還連接是否校驗(yàn)連接的有效性,默認(rèn)false,避免每次使用或歸還連接與服務(wù)端進(jìn)行一次連接開銷
setTestOnBorrow(false);
setTestOnReturn(false);
// 開啟JMX監(jiān)控
setJmxEnabled(true);
// 是否開啟空閑連接檢測(cè),默認(rèn)false,建議true
setTestWhileIdle(true);
// 空閑連接的檢測(cè)周期,毫秒,默認(rèn)-1不進(jìn)行檢測(cè),此處周期設(shè)置為1分鐘
setTimeBetweenEvictionRunsMillis(60 * 1000);
// 空閑連接檢測(cè)時(shí),每次檢測(cè)資源的個(gè)數(shù),設(shè)置為-1,就是對(duì)所有連接進(jìn)行檢測(cè)
setNumTestsPerEvictionRun(-1);
// 連接池中連接的最小空閑時(shí)間,默認(rèn)180000毫秒,30分鐘,此處設(shè)置為1分鐘
setMinEvictableIdleTimeMillis(60 * 1000);
}
}
我們可以根據(jù)自己的業(yè)務(wù)場(chǎng)景來設(shè)置合理的連接池大小,在HBase Thrift API的使用場(chǎng)景中,我們需要特別注意對(duì)連接對(duì)象有效性的檢查,此處我們開啟空閑連接檢測(cè),并設(shè)置檢測(cè)周期為60秒,同時(shí)設(shè)置連接池中最小空閑時(shí)間也是60秒,這個(gè)參數(shù)配比主要為了解決,客戶端超過一定時(shí)間不與服務(wù)端進(jìn)行連接(在HBaseThriftServer中,默認(rèn)60s之后,會(huì)斷開客戶端的空閑回話,由參數(shù)hbase.thrift.server.socket.read.timeout控制),服務(wù)端便會(huì)把此回話斷開,當(dāng)客戶端再次嘗試連接,就會(huì)報(bào)錯(cuò):

連接池中會(huì)自動(dòng)檢測(cè)連接的有效性,并及時(shí)清除超時(shí)閑置的鏈接,保證客戶端每次獲取的連接都是可用的,我們禁止在獲取連接和歸還連接時(shí)檢測(cè)連接是否有效,主要為了避免多余的請(qǐng)求開銷。因?yàn)?,我們?duì)連接檢測(cè)的邏輯是:
@Override
public List<String> getTableNames() {
ArrayList<String> tableNames = new ArrayList<>();
try {
for (ByteBuffer name : hbaseClient.getTableNames()) {
tableNames.add(ByteBufferUtil.byteBufferToString(name));
}
return tableNames;
} catch (TException e) {
throw new HBaseThriftException(e);
}
}
public void ping() {
getTableNames();
}
這里我ping的邏輯是獲取集群的表名稱列表,而不是調(diào)用TScoket的相關(guān)API來判斷連接的有效性,如:TSocket中類似isOpen()的方法。Socket中諸如此類的方法來判斷Socket的有效性,只能說明客戶端中TSocket的連接狀態(tài)是有效的,而在服務(wù)端,該對(duì)象對(duì)應(yīng)的回話早已失效。
其次,我們需要業(yè)務(wù)方的并發(fā)需求,給連接池設(shè)置一個(gè)合理的參數(shù),設(shè)置多了,會(huì)產(chǎn)生過多無用的連接;設(shè)置少了,又會(huì)增加客戶端并發(fā)等待的時(shí)間,影響讀寫效率。如果沒有連接池的機(jī)制,想要做到合理地使用這些連接對(duì)象,可能會(huì)產(chǎn)生比較多的問題,另一個(gè)典型的異常就是,業(yè)務(wù)高峰期,過多連接被突然創(chuàng)建,耗費(fèi)本地機(jī)器過多端口,超出限制,造成本地短連接過多等問題。
4. 把連接池的功能再包裝成一個(gè)單例服務(wù)
先來看我們最終的調(diào)用效果
@Test
public void testPut2(){
HBaseThriftService hBaseThriftService = HBaseThriftServiceHolder.getInstance("localhost", 9090);
Map<String, String> data = new HashMap<>();
data.put("info:name", "leo");
data.put("info:age", "18");
data.put("info:address", "shanghai");
// 保存數(shù)據(jù)
hBaseThriftService.save("leo_test", "a10003", data);
}
HBaseThriftServiceHolder是一個(gè)單例容器,單例模式保證我們?cè)谕粋€(gè)進(jìn)程中,連接池的對(duì)象只初始化一次。特別是在多線程的環(huán)境中,可以減少連接池資源創(chuàng)建的開銷。
單例模式是一個(gè)非常常用且簡(jiǎn)單的設(shè)計(jì)模式,其實(shí)現(xiàn)的方式也有不下七八種,各有優(yōu)劣,此處不再過多贅述。
5. 總結(jié)
此篇文章記述了在使用HBase Thrift 原生API的過程中遇到的一些問題,并參考jedis以及happybase對(duì)連接池的實(shí)現(xiàn)思路,基于commons-pool2實(shí)現(xiàn)客戶端連接池,解決了客戶端連接閑置超時(shí)的異常、短連接過多的隱患、以及對(duì)平衡性能與資源消耗的一些思考。文中或許有描述不當(dāng)之處,在代碼的實(shí)現(xiàn)上也許還可以再提高、再優(yōu)化。希望讀到朋友多多包涵,并積極指正。