HBase客戶端代碼書寫規范
本文檔提到的一些規范條例,主要來源于對定位問題過程中所積累的經驗、客戶端代碼實踐、對HBase Client源碼的分析以及已總結的ReleaseNotes中的一些注意點等。這些條款,主要分為規則、建議、示例三種類型。規則類,是在寫HBase客戶端代碼時必須遵循的一些條款。建議類,需要依據實際的應用需求來決定是否遵循。示例類,給出了一些功能代碼的實現示例,供參考,本文中使用的HBase版本仍為1.0+。
1【規則】 Configuration實例的創建
該類應該通過調用HBaseConfiguration的Create()方法來實例化。否則,將無法正確加載HBase中的相關配置項。
正確示例:
//該部分,應該是在類成員變量的聲明區域聲明
private?Configuration?hbaseConfig?=?null;
//最好在類的構造函數中,或者初始化方法中實例化該類
hbaseConfig?= HBaseConfiguration.create();
錯誤示例:
hbaseConfig?= new Configuration();
2【規則】 共享Configuration實例
HBase客戶端代碼通過創建一個與Zookeeper之間的HConnection,來獲取與一個HBase集群進行交互的權限。一個Zookeeper的HConnection連接,對應著一個Configuration實例,已經創建的HConnection實例,會被緩存起來。也就是說,如果客戶端需要與HBase集群進行交互的時候,會傳遞一個Configuration實例過去,HBase Client部分通過已緩存的HConnection實例,來判斷屬于這個Configuration實例的HConnection實例是否存在,如果不存在,就會創建一個新的,如果存在,就會直接返回相應的實例。
因此,如果頻頻的創建Configuration實例,會導致創建很多不必要的HConnection實例,很容易達到Zookeeper的連接數上限。
建議在整個客戶端代碼范圍內,都共用同一個Configuration對象實例。
3【規則】 HTable實例的創建
HTable類有多種構造函數,如:
1.public HTable(final String tableName)
2.public HTable(final byte [] tableName)
3.public HTable(Configuration conf, final byte [] tableName)
4.public HTable(Configuration conf, final String tableName)
5.public HTable(final byte[] tableName, final HConnection connection, final ExecutorService pool)
建議采用第5種構造函數。之所以不建議使用前面的4種,是因為:前兩種方法實例化一個HTable時,沒有指定Configuration實例,那么,在實例化的時候,就會自動創建一個Configuration實例。如果需要實例化過多的HTable實例,那么,就可能會出現很多不必要的HConnection(關于這一點,前面部分已經有講述)。因此,而對于第3、4種構造方法,每個實例都可能會創建一個新的線程池,也可能會創建新的連接,導致性能偏低。
正確示例:
private HTable table = null;
public initTable(Configuration config, byte[] tableName)
{
// sharedConn和pool都已經是事先實例化好的。建議進程級別內共享同一個。
//?初始化HConnection的方法:
// HConnection sharedConn =
//??? HConnectionManager.createConnection(this.config);
table = new HTable(config, tableName, sharedConn, pool);
}
錯誤示例:
private HTable table = null;
public initTable(String tableName)
{
table = new HTable(tableName);
}
public initTable(byte[] tableName)
{
table = new HTable(tableName);
}
4?【規則】 不允許多個線程在同一時間共用同一個HTable實例
HTable是一個非線程安全類,因此,同一個HTable實例,不應該被多個線程同時使用,否則可能會帶來并發問題。
5【規則】 HTable實例緩存
如果一個HTable實例可能會被長時間且被同一個線程固定且頻繁的用到,例如,通過一個線程不斷的往一個表內寫入數據,那么這個HTable在實例化后,就需要緩存下來,而不是每一次插入操作,都要實例化一個HTable對象(盡管提倡實例緩存,但也不是在一個線程中一直沿用一個實例,個別場景下依然需要重構,可參見下一條規則)。
正確示例:
//注意該實例中提供的以Map形式緩存HTable實例的方法,未必通用。這與多線程多HTable實例的設計方案有關。如果確定一個HTable實例僅僅可能會被用于一個線程,而且該線程也僅有一個HTable實例的話,就無須使用Map。這里提供的思路僅供參考
//該Map中以TableName為Key值,緩存所有已經實例化的HTable
private Map
//所有的HTable實例,都將共享這個Configuration實例
private Configuration demoConf = null;
/**
* <初始化一個HTable類>
* <功能詳細描述>
* @param tableName
* @return
* @throws IOException
* @see [類、類#方法、類#成員]
*/
private HTable initNewTable(String tableName) throws IOException
{
return new HTable(demoConf, tableName);
}
/**
* <獲取HTable實例>
* <功能詳細描述>
* @see [類、類#方法、類#成員]
*/
private HTable getTable(String tableName)
{
if (demoTables.containsKey(tableName))
{
return demoTables.get(tableName);
} else {
HTable table = null;
try
{
table = initNewTable(tableName);
demoTables.put(tableName, table);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return table;
}
}
/**
* <寫數據>
* <這里未涉及到多線程多HTable實例在設計模式上的優化.這里只所以采用同步方法,
*??是考慮到同一個HTable是非線程安全的.通常,我們建議一個HTable實例,在同一
*??時間只能被用在一個寫數據的線程中>
* @param dataList
* @param tableName
* @see [類、類#方法、類#成員]
*/
public void putData(List
{
HTable table = getTable(tableName);
//關于這里的同步:如果在采用的設計方案中,不存在多線程共用同一個HTable實例
//的可能的話,就無須同步了。這里需要注意的一點,就是HTable實例是非線程安全的
synchronized (table)
{
try
{
table.put(dataList);
table.notifyAll();
}
catch (IOException e)
{
//?在捕獲到IOE時,需要將緩存的實例重構。
try {
//?關閉之前的Connection.
table.close();
//?重新創建這個實例.
table = new HTable(this.config, "jeason");
} catch (IOException e1) {
// TODO
}
}
}
}
錯誤示例:
public void putDataIncorrect(List
{
HTable table = null;
try
{
//每次寫數據,都創建一個HTable實例
table = new HTable(demoConf, tableName);
table.put(dataList);
}
catch (IOException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
finally
{
table.close();
}
}
6【規則】 HTable實例寫數據的異常處理
盡管在前一條規則中提到了提倡HTable實例的重構,但是,并非提倡一個線程自始至終要沿用同一個HTable實例,當捕獲到IOException時,依然需要重構HTable實例。示例代碼可參考上一個規則的示例。
另外,勿輕易調用如下兩個方法:
?? Configuration#clear:
這個方法,會清理掉所有的已經加載的屬性,那么,對于已經在使用這個Configuration的類或線程而言,可能會帶來潛在的問題(例如,假如HTable還在使用這個Configuration,那么,調用這個方法后,HTable中的這個Configuration的所有的參數,都被清理掉了),也就是說:只要還有對象或者線程在使用這個Configuration,我們就不應該調用這個clear方法,除非,所有的類或線程,都已經確定不用這個Configuration了。那么,這個操作,可以在所有的線程要退出的時候來做,而不是每一次。
因此,這個方法,應該要放在進程要退出的地方去做。而不是每一次HBaseAdmin要重構的時候做。
?? HConnectionManager#deleteAllConnections:
這個可能會導致現有的正在使用的連接被從連接集合中清理掉,同時,因為在HTable中保存了原有連接的引用,可能會導致這個連接無法關閉,進而可能會造成泄漏。因此,這個方法不建議使用。
7【規則】 寫入失敗的數據要做相應的處理
在寫數據的過程中,如果進程異常或一些其它的短暫的異常,可能會導致一些寫入操作失敗。因此,對于操作的數據,需要將其記錄下來。在集群恢復正常后,重新將其寫入到HBase數據表中。
另外,有一點需要注意:HBase Client返回寫入失敗的數據,是不會自動重試的,僅僅會告訴接口調用者哪些數據寫入失敗了。對于寫入失敗的數據,一定要做一些安全的處理,例如可以考慮將這些失敗的數據,暫時寫在文件中,或者,直接緩存在內存中。
正確示例:
private List
/**
* <采用PutList的模式插入數據>
* <如果不是多線程調用該方法,可不采用同步>
* @param put?一條數據記錄
* @throws IOException
* @see [類、類#方法、類#成員]
*/
public synchronized void putData(Put put)
{
//?暫時將數據緩存在該List中
dataList.add(put);
//?當dataList的大小達到PUT_LIST_SIZE之后,就執行一次Put操作
if (dataList.size() >= PUT_LIST_SIZE)
{
try
{
demoTable.put(dataList);
}
catch (IOException e)
{
//?如果是RetriesExhaustedWithDetailsException類型的異常,
//?說明這些數據中有部分是寫入失敗的這通常都是因為
// HBase集群的進程異常引起,當然有時也會因為有大量
//?的Region正在被轉移,導致嘗試一定的次數后失敗
if (e instanceof RetriesExhaustedWithDetailsException)
{
RetriesExhaustedWithDetailsException ree =
(RetriesExhaustedWithDetailsException)e;
int failures = ree.getNumExceptions();
for (int i = 0; i < failures; i++)
{
errorList.add(ree.getRow(i));
}
}
}
dataList.clear();
}
}
8【規則】?資源釋放
關于ResultScanner和HTable實例,在用完之后,需要調用它們的Close方法,將資源釋放掉。Close方法,要放在finally塊中,來確保一定會被調用到。
正確示例:
ResultScanner scanner = null;
try
{
scanner = demoTable.getScanner(s);
//Do Something here.
}
finally
{
scanner.close();
}
錯誤示例:
1.?在代碼中未調用scanner.close()方法釋放相關資源。
2. scanner.close()方法未放置在finally塊中:
ResultScanner scanner = null;
scanner = demoTable.getScanner(s);
//Do Something here.
scanner.close();
9【規則】 Scan時的容錯處理
Scan時不排除會遇到異常,例如,租約過期。在遇到異常時,建議Scan應該有重試的操作。
事實上,重試在各類異常的容錯處理中,都是一種優秀的實踐,這一點,可以應用在各類與HBase操作相關的接口方法的容錯處理過程中。
10【規則】 不用HBaseAdmin時,要及時關閉,HBaseAdmin實例不應常駐內存
HBaseAdmin的示例應盡量遵循 “用時創建,用完關閉”的原則。不應該長時間緩存同一個HBaseAdmin實例。
11【規則】 暫時不建議使用HTablePool獲取HTable實例,因為當前的HTablePool實現中可能會帶來泄露。創建HTable實例的方法,參考上面的規則4。
12【建議】 不要調用HBaseAdmin的closeRegion方法關閉一個Region
HBaseAdmin中,提供了關閉一個Region的接口:
//hostAndPort可以指定,也可以不指定。
public void closeRegion(final String regionname, final String hostAndPort)
通過該方法關閉一個Region, HBase Client端會直接發RPC請求到Region所在的RegionServer上,整個流程對Master而言,是不感知的。也就是說,盡管RegionServer關閉了這個Region,但是,在Master側,還以為該Region是在該RegionServer上面打開的。假如,在執行Balance的時候,Master計算出恰好要轉移這個Region,那么,這個Region將無法被關閉,本次轉移操作將無法完成(關于這個問題,在當前的HBase版本中的處理的確還欠缺妥當)。
因此,暫時不建議使用該方法關閉一個Region。
13【建議】 采用PutList模式寫數據
HTable類中提供了兩種寫數據的接口:
1.???? public void put(final Put put) throws IOException
2.???? public void put(final List
第1種方法較之第2種方法,在性能上有明顯的弱勢。因此,寫數據時應該采用第2種方法。
14【建議】 Scan時指定StartKey和EndKey
一個有確切范圍的Scan,在性能上會帶來較大的好處。
代碼示例:
Scan scan = new Scan();
scan.addColumn(Bytes.toBytes("familyname"),Bytes.toBytes("columnname"));
scan.setStartRow( Bytes.toBytes("rowA")); //?假設起始Key為rowA
scan.setStopRow( Bytes.toBytes("rowB"));? //?假設EndKey為rowB
for(Result result : demoTable.getScanner(scan)) {
// process Result instance
}
15【建議】 不要關閉WAL
WAL是Write-Ahead-Log的簡稱,是指數據在入庫之前,首先會寫入到日志文件中,借此來確保數據的安全性。
WAL功能默認是開啟的,但是,在Put類中提供了關閉WAL功能的接口:
public void setWriteToWAL(boolean write)
因此,不建議調用該方法將WAL關閉(即將writeToWAL設置為False),因為可能會造成最近1S(該值由RegionServer端的配置參數hbase.regionserver.optionallogflushinterval決定,默認為1S)內的數據丟失。但如果在實際應用中,對寫入的速率要求很高,并且可以容忍丟失最近1S內的數據的話,可以將該功能關閉。
16【建議】 創建一張表或Scan時設定blockcache為 true
HBase客戶端建表和scan時,設置blockcache=true。需要根據具體的應用需求來設定它的值,這取決于有些數據是否會被反復的查詢到,如果存在較多的重復記錄,將這個值設置為true可以提升效率,否則,建議關閉。
建議按默認配置,默認就是true,只要不強制設置成false就可以,例如:
HColumnDescriptor fieldADesc = new HColumnDescriptor("value".getBytes());
fieldADesc.setBlockCacheEnabled(false);
17【示例】 Configuration可以設置的參數
為了能夠建立一個HBase Client端到HBase Server端的連接,需要設置如下幾個參數:
hbase.zookeeper.quorum: Zookeeper的IP。多個Zookeeper節點的話,中間用”,”隔開。
hbase.zookeeper.property.clientPort: Zookeeper的端口。
說明:
通過HBaseConfiguration.create()創建的Configuration實例,會自動加載如下配置文件中的配置項:
1.???? core-default.xml
2.???? core-site.xml
3.???? hbase-default.xml
4.???? hbase-site.xml
因此,這四個配置文件,應該要放置在“Source Folder”下面(將一個文件夾設置為Source Folder的方法:如果在工程下面建立了一個resource的文件夾,那么,可以在該文件夾上右鍵鼠標,依次選擇”Build Path”->”Use as Source Folder”即可,可參考下圖)
下面是客戶端可配置的一些參數集合(在通常情況下,這些值都不建議修改):
參數名
參數解釋
hbase.client.pause
每次異常或者其它情況下重試等待相關的時間參數(實際等待時間將根據該值與已重試次數計算得出)
hbase.client.retries.number
異常或者其它情況下的重試次數
hbase.client.retries.longer.multiplier
與重試次數有關
hbase.client.rpc.maxattempts
RPC請求不可達時的重試次數
hbase.regionserver.lease.period
與Scanner超時時間有關(單位ms)
hbase.client.write.buffer
在啟用AutoFlush的情況下,該值不起作用。如果未啟用AotoFlush的話,HBase Client端會首先緩存寫入的數據,達到設定的大小后才向HBase集群下發一次寫入操作
hbase.client.scanner.caching
Scan時一次next請求獲取的行數
hbase.client.keyvalue.maxsize
一條keyvalue數據的最大值
hbase.htable.threads.max
HTable實例中與數據操作有關的最大線程數
hbase.client.prefetch.limit
客戶端在寫數據或者讀取數據時,需要首先獲取對應的Region所在的地址。客戶端可以預緩存一些Region地址,這個參數就是與緩存的數目有關的配置
正確設置參數的方法:
hbaseConfig = HBaseConfiguration.create();
//如下參數,如果在配置文件中已經存在,則無須再配置
hbaseConfig.set("hbase.zookeeper.quorum", "157.5.100.1,157.5.100.2,157.5.100.3");
hbaseConfig.set("hbase.zookeeper.property.clientPort", "2181");
18【示例】 HTablePool在多線程寫入操作中的應用
有多個寫數據線程時,可以采用HTablePool。現在先簡單介紹下該類的使用方法和注意點:
(1)????????????? 多個寫數據的線程之間,應共享同一個HTablePool實例。
(2)????????????? 實例化HTablePool的時候,應要指定最大的HTableInterface實例個數maxSize,即需要通過如下構造函數實例化該類:
public HTablePool(final Configuration config, final int maxSize)
關于maxSize的值,可以根據寫數據的線程數Threads以及涉及到的用戶表個數Tables來定,理論上,不應該超過(Threads*Tables)。
(3)????????????? 客戶端線程通過HTablePool#getTable(tableName)的方法,獲取一個表名為tableName的HTableInterface實例。
(4)????????????? 同一個HTableInterface實例,在同一個時刻只能給一個線程使用。
(5)????????????? 如果HTableInterface使用用完了,需要調用HTablePool#putTable(HTableInterface table)方法將它放回去。
示例代碼:
/**
*?寫數據失敗后需要一定的重試次數,每一次重試的等待時間,需要根據已經重試的次數而定.
*/
private static final int[] RETRIES_WAITTIME = {1, 1, 1, 2, 2, 4, 4, 8, 16, 32};
/**
*?限定的重試次數
*/
private static final int RETRIES = 10;
/**
*?失敗后等待的基本時間單位
*/
private static final int PAUSE_UNIT = 1000;
private static Configuration hadoopConfig;
private static HTablePool tablePool;
private static String[] tables;
/**
* <初始化HTablePool>
* <功能詳細描述>
* @param config
* @see [類、類#方法、類#成員]
*/
public static void initTablePool()
{
DemoConfig config = DemoConfig.getInstance();
if (hadoopConfig == null)
{
hadoopConfig = HBaseConfiguration.create();
hadoopConfig.set("hbase.zookeeper.quorum", config.getZookeepers());
hadoopConfig.set("hbase.zookeeper.property.clientPort", config.getZookeeperPort());
}
if (tablePool == null)
{
tablePool = new HTablePool(hadoopConfig, config.getTablePoolMaxSize());
tables = config.getTables().split(",");
}
}
public void run()
{
//?初始化HTablePool.因為這是多線程間共享的一個實例,?僅被實例化一次.
initTablePool();
for (;;)
{
Map
String tableName = tables[(Integer)data.get("table")];
List
//?以Row為Key,保存List中所有的Put.該集合僅僅使用于寫入失敗時查找失敗的數據記錄.
//?因為從Server端僅僅返回了失敗的數據記錄的Row值.
Map
//?如果失敗了(哪怕是部分數據失敗),需要重試.每一次重試,都僅僅提交失敗的數據條目
INNER_LOOP :
for (int retry = 0; retry < RETRIES; retry++)
{
//?從HTablePool中獲取一個HTableInterface實例.用完后需要放回去.
HTableInterface table = tablePool.getTable(tableName);
try
{
table.put(list);
//?如果執行到這里,說明成功了?.
break INNER_LOOP;
}
catch (IOException e)
{
//?如果是RetriesExhaustedWithDetailsException類型的異常,
//?說明這些數據中有部分是寫入失敗的這通常都是因為HBase集群
//?的進程異常引起,當然有時也會因為有大量的Region正在被轉移,
//?導致嘗試一定的次數后失敗.
//?如果非RetriesExhaustedWithDetailsException異常,則需要將
// list中的所有數據都要重新插入.
if (e instanceof RetriesExhaustedWithDetailsException)
{
RetriesExhaustedWithDetailsException ree =
(RetriesExhaustedWithDetailsException)e;
int failures = ree.getNumExceptions();
System.out.println("本次插入失敗了[" + failures + "]條數據.");
//?第一次失敗且重試時,實例化該Map.
if (rowPutMap == null)
{
rowPutMap = new HashMap
for (int m = 0; m < list.size(); m++)
{
Put put = list.get(m);
rowPutMap.put(put.getRow(), put);
}
}
//先Clear掉原數據,然后將失敗的數據添加進來
list.clear();
for (int m = 0; m < failures; m++)
{
list.add(rowPutMap.get(ree.getRow(m)));
}
}
}
finally
{
//?用完之后,再將該實例放回去
tablePool.putTable(table);
}
//?如果異常了,就暫時等待一段時間.該等待應該在將HTableInterface實例放回去之后
try
{
sleep(getWaitTime(retry));
}
catch (InterruptedException e1)
{
System.out.println("Interruped");
}
}
}
}
19【示例】 Put實例的創建
HBase是一個面向列的數據庫,一行數據,可能對應多個列族,而一個列族又可以對應多個列。通常,寫入數據的時候,我們需要指定要寫入的列(含列族名稱和列名稱):
如果要往HBase表中寫入一行數據,需要首先構建一個Put實例。Put中包含了數據的Key值和相應的Value值,Value值可以有多個(即可以有多列值)。
有一點需要注意:在往Put實例中add一條KeyValue數據時,傳入的family,qualifier,value都是字節數組。在將一個字符串轉換為字節數組時,需要使用Bytes.toBytes方法,不要使用String.toBytes方法,因為后者無法保證編碼,尤其是在Key或Value中出現中文字符的時候,就會出現問題。
代碼示例:
//列族的名稱為privateInfo
private final static byte[] FAMILY_PRIVATE = Bytes.toBytes("privateInfo");
//列族privateInfo中總共有兩個列"name"&"address"
private final static byte[] COLUMN_NAME = Bytes.toBytes("name");
private final static byte[] COLUMN_ADDR = Bytes.toBytes("address");
/**
* <創建一個Put實例>
* <在該方法中,將會創建一個具有1個列族,2列數據的Put>
* @param rowKey? Key值
* @param name????人名
* @param address?地址
* @return
* @see [類、類#方法、類#成員]
*/
public Put createPut(String rowKey, String name, String address)
{
Put put = new Put(Bytes.toBytes(rowKey));
put.add(FAMILY_PRIVATE, COLUMN_NAME, Bytes.toBytes(name));
put.add(FAMILY_PRIVATE, COLUMN_ADDR, Bytes.toBytes(address));
return put;
}
20【示例】 HBaseAdmin實例的創建以及常用方法
代碼示例:
private Configuration demoConf = null;
private HBaseAdmin hbaseAdmin = null;
/**
* <構造函數>
*?需要將已經實例化好的Configuration實例傳遞進來
*/
public HBaseAdminDemo(Configuration conf)
{
this.demoConf = conf;
try
{
//?實例化HBaseAdmin
hbaseAdmin = new HBaseAdmin(this.demoConf);
}
catch (MasterNotRunningException e)
{
e.printStackTrace();
}
catch (ZooKeeperConnectionException e)
{
e.printStackTrace();
}
}
/**
* <一些方法使用示例>
* <更多的方法,請參考HBase接口文檔>
* @throws IOException
* @throws ZooKeeperConnectionException
* @throws MasterNotRunningException
* @see [類、類#方法、類#成員]
*/
public void demo() throws MasterNotRunningException, ZooKeeperConnectionException, IOException
{
byte[] regionName = Bytes.toBytes("mrtest,jjj,1315449869513.fc41d70b84e9f6e91f9f01affdb06703.");
byte[] encodeName = Bytes.toBytes("fc41d70b84e9f6e91f9f01affdb06703");
//?重新分配一個Reigon.
hbaseAdmin.unassign(regionName, false);
//?主動觸發Balance.
hbaseAdmin.balancer();
//?移動一個Region,第2個參數,是RegionServer的HostName+StartCode,例如:
// host187.example.com,60020,1289493121758.如果將該參數設置為null,則會隨機移動該Region
hbaseAdmin.move(encodeName, null);
//?判斷一個表是否存在
hbaseAdmin.tableExists("tableName");
//?判斷一個表是否被激活
hbaseAdmin.isTableEnabled("tableName");
}
/**
* <快速創建一個表的方法>
* <首先創建一個HTableDescriptor實例,它里面包含了即將要創建的HTable的描述信息,同時,需要創建相應的列族。列族關聯的實例是HColumnDescriptor。在本示例中,創建的列族名稱為“columnName”>
* @param tableName?表名
* @return
* @see [類、類#方法、類#成員]
*/
public boolean createTable(String tableName)
{
try {
if (hbaseAdmin.tableExists(tableName)) {
return false;
}
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
HColumnDescriptor fieldADesc = new HColumnDescriptor("columnName".getBytes());
fieldADesc.setBlocksize(640 * 1024);
tableDesc.addFamily(fieldADesc);
hbaseAdmin.createTable(tableDesc);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
附 1 ?Scan時的兩個關鍵參數—Batch和Caching
Batch:使用scan調用next接口每次最大返回的記錄數,與一次讀取的列數有關。
Caching:一個RPC查詢請求最大的返回的next數目,與一次RPC獲取的行數有關。
首先舉幾個例子,來介紹這兩個參數在Scan時所起到的作用:
假設表A的一個Region中存在2行(rowkey)數據,每行有1000column,且每列當前只有一個version,即每行就會有1000個key value。
ColuA1
ColuA2
ColuA3
ColuA4
………
ColuN1
ColuN2
ColuN3
ColuN4
Row1
………
Row2
………
例1: 查詢參數: 不設batch,設定caching=2
那么,一次RPC請求,就會返回2000個KeyValue.
例2: 查詢參數: 設定batch=500,設定caching=2
那么,一次RPC請求,只能返回1000個KeyValue.
例3: 查詢參數: 設定batch=300,設定caching=4
那么,一次RPC請求,也只能返回1000個KeyValue.
關于Batch和Caching的進一步解釋:
?? 一次Caching,是一次請求數據的機會。
?? 同一行數據是否可以通過一次Caching讀完,取決于Batch的設置,如果Batch的值小于一行的總列數,那么,這一行至少需要2次Caching才可以讀完(后面的一次Caching的機會,會繼續前面讀取到的位置繼續讀取)。
?? 一次Caching讀取,不能跨行。如果某一行已經讀完,并且Batch的值還沒有達到設定的大小,也不會繼續讀下一行了。
那么,關于例1與例2的結果,就很好解釋了:
例1的解釋:
不設定Batch的時候,默認會讀完改行所有的列。那么,在caching為2的時候,一次RPC請求就會返回2000個KeyValue。
例2的解釋:
設定Batch為500,caching為2的情況下,也就是說,每一次Caching,最多讀取500列數據。那么,第一次Caching,讀取到500列,剩余的500列,會在第2次Caching中讀取到。因此,兩次Caching會返回1000個KeyValue。
例3的解釋:
設定Batch為300,caching為4的情況下,讀取完1000條數據,正好需要4次caching。因此,只能返回1000條數據。
代碼示例:
Scan s = new Scan();
//設置查詢的起始key和結束key
s.setStartRow(Bytes.toBytes("01001686138100001"));
s.setStopRow(Bytes.toBytes("01001686138100002"));
s.setBatch(1000);
s.setCaching(100);
ResultScanner scanner = null;
try {
scanner = tb.getScanner(s);
for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
for (KeyValue kv : rr.raw()) {
//顯示查詢的結果
System.out.println("key:" + Bytes.toString(kv.getRow())
+ "getQualifier:" + Bytes.toString(kv.getQualifier())
+ "value" + Bytes.toString(kv.getValue()));
}
}
} catch (IOException e) {
System.out.println("error!" + e.toString());
} finally {
scanner.close();
}
EI企業智能
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。