HBase最佳實踐-客戶端超時機制

知與誰同發表於2017-08-01

上篇博文結合一起線上問題介紹了HBase客戶端基於退避演算法的重試機制,並分析得出在某些場景下如果重試策略設定不當會導致長時間的業務阻塞。除了重試機制外,業務童鞋最關心的就是超時機制了。客戶端超時設定對整個系統的穩定性以及敏感性至關重要,一旦沒有超時設定或超時時間設定過長,伺服器端的長時間卡頓必然會引起客戶端阻塞等待,進而影響上層應用。好在HBase提供了多個客戶端引數設定超時,主要包括 hbase.rpc.timeout / hbase.client.operation.timeout / hbase.client.scanner.timeout.period。然而這些引數官方並沒有給出具體的介紹,導致開發人員並不真正理解這些引數的含義。接下來本文分別對這三個引數進行介紹,應用童鞋可以根據自己的實際情況對這三個引數進行設定。

hbase.rpc.timeout

從字面意思就可知道,該參數列示一次RPC請求的超時時間。如果某次RPC時間超過該值,客戶端就會主動關閉socket。此時,伺服器端就會捕獲到如下的異常:

java.io.IOException: Connection reset by peer
        at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
        at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
        at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
        at sun.nio.ch.IOUtil.read(IOUtil.java:197)
        at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:384)
        at org.apache.hadoop.hbase.ipc.RpcServer.channelRead(RpcServer.java:2246)
        at org.apache.hadoop.hbase.ipc.RpcServer$Connection.readAndProcess(RpcServer.java:1496)
....
2016-04-14 21:32:40,173 WARN  [B.DefaultRpcServer.handler=125,queue=5,port=60020] ipc.RpcServer: RpcServer.respondercallId: 7540 service: ClientService methodName: Multi size: 100.2 K connection: 10.160.247.139:56031: output error
2016-04-14 21:32:40,173 WARN  [B.DefaultRpcServer.handler=125,queue=5,port=60020] ipc.RpcServer: B.DefaultRpcServer.handler=125,queue=5,port=60020: caught a ClosedChannelException, this means that the server was processing a request but the client went away. The error message was: null

上述異常經常發生在大量高併發讀寫業務或者伺服器端發生了比較嚴重的Full GC等場景下,導致某些請求無法得到及時處理,超過了時間間隔。該值預設大小為60000ms,即1min。


hbase.client.operation.timeout

該參數列示HBase客戶端發起一次資料操作直至得到響應之間總的超時時間,資料操作型別包括get、append、increment、delete、put等。很顯然,hbase.rpc.timeout表示一次RPC的超時時間,而hbase.client.operation.timeout則表示一次操作的超時時間,有可能包含多個RPC請求。舉個例子說明,比如一次Put請求,客戶端首先會將請求封裝為一個caller物件,該物件傳送RPC請求到伺服器,假如此時因為伺服器端正好發生了嚴重的Full GC,導致這次RPC時間超時引起SocketTimeoutException,對應的就是hbase.rpc.timeout。那假如caller物件傳送RPC請求之後剛好發生網路抖動,進而丟擲網路異常,HBase客戶端就會進行重試,重試多次之後如果總操作時間超時引起SocketTimeoutException,對應的就是hbase.client.operation.timeout。


hbase.client.scanner.timeout.period

看到這裡為止,很多細心的童鞋都會發現,hbase.client.operation.timeout引數規定的超時基本涉及到了HBase所有的資料操作,唯獨沒有scan操作。然而scan操作卻是最有可能發生超時的,也因此是使用者最為關心的。HBase當然考慮到了這點,並提供了一個單獨的超時引數進行設定:hbase.client.scanner.timeout.period。這個引數理解起來稍微有點複雜,需要對scan操作本身有比較全面的理解,這可能也是很多業務使用者並不瞭解的地方。

首先,我們來看一個最簡單的scan操作示例:

public static void scan() {
   HTable table=(HTable) getHTablePool().getTable("tb_stu");  
   Scan scan=new Scan(); 
   scan.setMaxResultSize(10000); 
   scan.setCacheing(500);
   ResultScanner rs = table.getScanner(scan);
   for (Result r : rs) {
      for (KeyValue kv : r.raw()) {
          System.out.println(String.format("row:%s, family:%s, qualifier:%s, qualifiervalue:%s,   timestamp:%s.”,
                  Bytes.toString(kv.getRow()),
                  Bytes.toString(kv.getFamily()),  
                  Bytes.toString(kv.getQualifier()),
                  Bytes.toString(kv.getValue()),
                  kv.getTimestamp()));
      }
   }
}

很多人都會誤認為一次scan操作就是一次RPC請求,實際上,一次請求大量資料的scan操作可能會導致多個很嚴重的後果:伺服器端可能因為大量io操作導致io利用率很高,影響其他正常業務請求;大量資料傳輸會導致網路頻寬等系統資源被大量佔用;客戶端也可能因為記憶體無法快取這些資料導致OOM。基於此,HBase會將一次大的scan操作根據設定條件拆分為多個RPC請求,每次只返回規定數量的結果。上述程式碼中foreach(Result r :rs)語句實際上等價於Result r = rs.next(),每執行一次next()操作就會呼叫客戶端傳送一次RPC請求,引數hbase.client.scanner.timeout.period就用來表示這麼一次RPC請求的超時時間,預設為60000ms,一旦請求超時,就會丟擲SocketTimeoutException異常。


講到這裡,就借用寶地引申地講講另外兩個相關的問題:

1.  上文提到一次大的scan操作會被拆分為多個RPC請求,那到底會拆分為多少個呢?

一次scan請求的RPC次數主要和兩個因素相關,一個是本次scan的待檢索條數,另一個是單次RPC請求的資料條數,很顯然,兩者的比值就是RPC請求次數。

一次scan的待檢索條數由使用者設定的條件決定,比如使用者想一次獲取某個使用者最近一個月的所有操作資訊,這些資訊總和為10w條,那一次scan總掃瞄條數就是10w條。為了防止一次scan操作請求的資料量太大,額外提供了引數maxResultSize對總檢索結果大小進行限制,該參數列示一次scan最多可以請求的資料量大小,預設為-1,表示無限制。

單次RPC請求的資料條數由引數caching設定,預設為100條。因為每次RPC請求獲取到資料都會快取到客戶端,因此該值如果設定過大,可能會因為一次獲取到的資料量太大導致客戶端記憶體oom;而如果設定太小會導致一次大scan進行太多次RPC,網路成本高。

具體使用可以參考上文程式碼示例。

2. 經常有業務童鞋問道,在scan過程中RegionServer端偶爾丟擲leaseException,是怎麼回事?

看到leaseException就會想到租約機制,的確,HBase內部在一次完整的scan操作中引入了租約機制。為什麼需要租約機制?這和整個scan操作流程有莫大的關係,上文講到,一次完整的scan通常會被拆分為多個RPC請求,實際實現中,RegionServer接收到第一次RPC請求之後,會為該scan操作生成一個全域性唯一的id,稱為scanId。除此之外,RegionServer還會進行大量的準備工作,構建整個scan體系,構造需要用到的所有物件,後續的RPC請求只需要攜帶相同的scanId作為標示就可以直接利用這些已經構建好的資源進行檢索。也就是說,在整個scan過程中,客戶端其實都佔用著伺服器端的資源,此時如果此客戶端意外當機,是否就意味著這些資源永遠都不能得到釋放呢?租約機制就是為了解決這個問題。RegionServer接收到第一次RPC之後,除了生成全域性唯一的scanId之外還會生成一個攜帶有超時時間的lease,超時時間可以通過引數hbase.regionserver.lease.period配置,一旦在超時時間內後續RPC請求沒有到來(比如客戶端處理太慢),RegionServer就認為客戶端出現異常,此時會將該lease銷燬並將整個scan所持有的資源全部釋放,客戶端在處理完成之後再發後續的RPC過來,檢查到對應的lease已經不存在,就會丟擲如下leaseExcption:

org.apache.hadoop.hbase.regionserver.LeaseException: lease `-8841369309248784313’ does not exist  
      at org.apache.hadoop.hbase.regionserver.Leases.removeLease(Leases.java:230)  
      at org.apache.hadoop.hbase.regionserver.HRegionServer.next(HRegionServer.java:1847)  
      at sun.reflect.GeneratedMethodAccessor11.invoke(Unknown Source)  
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
      at java.lang.reflect.Method.invoke(Method.java:597)  
      at org.apache.hadoop.hbase.ipc.HBaseRPC$Server.call(HBaseRPC.java:570)
      at org.apache.hadoop.hbase.ipc.HBaseServer$Handler.run(HBaseServer.java:1039) 

寫在最後

一旦發生超時異常,很多童鞋的第一反應是會將超時時間設長。可以負責任的說,這樣的處理是盲目的,不僅不能從本質上解決問題,還會使得整個系統處於特別不敏感的狀態,在某些異常情況下,客戶端就會因為超時時間設定太長而一直阻塞,進而導致上層業務長時間卡頓。因此在大多數情況下都不建議將超時時間設大,推薦的方法是找到超時的原因,分析超時原因是否可以避免。

文章最後再說說如何設定超時,使用者可以通過修改配置檔案hbase-site.xml來設定這幾個引數,也可以通過程式碼的方式進行設定,如下所示:

Confiuration conf = HBaseConfiguration.create();
conf.setInt("hbase.rpc.timeout",20000);
conf.setInt("hbase.client.operation.timeout”,30000);
conf.setInt("hbase.client.scanner.timeout.period",20000);
HTable table = new HTable(conf,"tableName");
…

本文轉載自:http://hbasefly.com


相關文章