本文講述了HBase Region Read Replicas功能詳解
往期文章回顧:Talos 讀寫一致性
背景
CAP原理指出,對於一個分佈式系統來說,不可能同時滿足一致性 (Consistency)、可用性(Availability)、分區容錯性(Partition tolerance),而HBase則被設計成一個CP系統,在保證強一致性的同時,選擇犧牲了一定的可用性。在對HBase的壓測中很容易發現,雖然HBase的平均讀寫延遲很低,但卻存在很高的毛刺,P99、P999延遲很高,主要的一個影響因素則是單點的GC,另外Region的MTTR(平均修復時間)也較高,一旦某個RegionServer宕機或某個Region出現問題,甚至是一次Full GC,都有可能出現較長時間的不可用,影響可用性[4]。爲了實現高可用度, HBase提供了Region Read Replicas功能,在這種模型下,表的每個region都會有多個副本,分佈在不同的RegionServer上。在region恢復期間或請求時間過長時,支持最終一致性的讀服務。在一些不要求強一致性的應用中,可以通過此功能來提高可用性或降低讀請求延遲。
一個region的所有副本都有一個唯一的replica_id。replica_id=0的是primary region(和之前模型中唯一的region一樣),其他的副本region被都叫做secondary region。默認region副本數爲1,此時與之前的region模型並無不同。當region副本數被設置爲2或更多時,Load Balancer會保證同一個region的多個副本會被分散在不同的RegionServer上。其中primary region支持讀寫請求;而secondary region則只支持讀請求。如此設計保證primary region依舊具有強一致性,同時提高讀可用性。但也因爲寫請求只有primary region可以處理,所以寫請求依然會因爲primary region不可用而被阻塞,HBase的寫可用性依然沒有得到改善。
>>>>
Client側實現
1.Timeline Consistency
在該功能的實現中,HBase提供了一種支持單次讀請求的一致性定義。
public enum Consistency {
STRONG,
TIMELINE
}
HBase默認的就是Consistency.STRONG
強一致性模型,與之前的模型一樣,所有讀寫操作都通primary region完成。而當client使用Consistency.TIMELINE
的一致性發起讀請求時,會首先向primary region發起請求,一定時間內沒有返回響應,則同時併發向所有的secondary region發起請求,最終採用率先返回的請求。爲了區分最終的響應是否來自secondary region,在Result
中增加了stale
的boolean屬性,true
則表示來自secondary region。
從語義上講,HBase的TIMELINE一致性並不同於常見的最終一致性解決方案。存在多個讀副本,但不需要考慮副本之間數據衝突的問題。因爲只有primary region可以寫入,而secondary region只接收primary region同步的數據,並按同樣順序處理數據,所以secondary region可以看做是primary region在之前某個時刻的快照。從這一點上看,更像是RDBMS(關係型數據庫管理系統)的複製、或是HBase多集羣之間的複製[1]。
(圖1: Timeline Consistency)
根據圖1我們來更好的理解TIMELINE的語義。首先client1按順序寫了x=1,x=2,x=3,primary region也按寫入順序處理,並將WAL同步給其他secondary region(一種數據同步方式,後面會再講)。在圖中注意到,replica_1只接收到兩次更新,所以最終數據是x=2,replica_2只接收到1次更新,數據是x=1。
client可能會讀到亂序的數據,比如多次請求發往了不同的region。比如圖1中的client2如果使用TIMELINE一致性讀取數據,有可能和所有副本做交互,最終獲得的數據1、2、3都有可能。如果client請求下面將展示下TIMELINE Get/Scan多次,甚至可能出現數據回退,即第1次請求獲得x=2,第2次請求則獲得了x=1。
這裏實際上是不滿足單調讀一致性。單調讀一致性是比強一致性弱,但比最終一致性強的保證:當讀取數據時,單調讀保證,如果某個用戶依次進行多次讀取,則他絕不會看到回滾現象,即在讀取較新值之後又發生讀舊值的情況。實現單調讀的一種方式是,確保每個用戶總是從固定的同一副本執行讀取(而不同的用戶可以從不同的副本讀取)[2]。顯然,這裏不滿足單調讀一致性的原因是無法保證結果總是來自同一個副本。
2. Region Read Replica Location
(圖2: 一個region副本數爲2的表在meta表中的列)
在圖2中,是一個region副本數爲2的表在meta表中info列族下的列,可以看到有一些名爲info:xxx_0001的列,這些列存儲的數據就是replica_id=1的secondary region的數據。同理,當region的備份數量更多時,meta表中名爲info:xxx_0002、info:xxx_0003的列存儲的則爲replica_id爲2、3的secondary region的數據。明白了meta表中是如何存儲secondary region數據,client要獲取secondary region所在的RegionServer自然也簡單,多解析幾個server_xxxx的列便可以了。
3.Get與Scan
HBase的讀請求有兩種,Get和Scan。對於Get這種無狀態的請求,每次RPC對server端來說都是一次獨立的請求,client端的用戶可以多次超時重試,直到獲取到數據。但對於Scan這種有狀態的請求,一次scan可能與同一個region交互多次,也可能跨多個region多個RegionServer請求數據,server端會記錄每個scan的狀態數據,那麼一次scan產生的多次RPC便不能隨意地發給所有的replica。下面將展示下TIMELINE Get/Scan的具體流程。
(圖3: client訪問secondary region)
圖3展示的是client TIMELINE Get的流程,請求primary region超時之後會再請求其他secondary region。
(圖4: client scan過程)
圖4展示的是client執行一個跨region的TIMELINE Scan流程,假設當前表有2個邏輯region(Region_A和Region_B),region的起始區間分別爲[a, d)、[d, f),且該表的region副本數爲2,即每個邏輯region都有一主一備,4個region分佈在4個RegionServer上。當我們執行一次scan操作,設置cacheing爲2(每次RPC最多獲取2個Result),則scan至少進行4次RPC,圖中連線則表示每次RPC,連線上的數字表示RPC的順序編號,虛線表示RPC超時或返回太慢結果沒有被採用。可以看到當client要進行第1次RPC時,將請求同時發給了Region_A的主備2個region,因爲此時server端是沒有任何關於此次scan的狀態數據,client可以選擇率先返回響應的region進行後續的RPC交互。當第2次RPC時便不可以隨意選擇region了,因爲Region_A_primary存儲了此次scan的狀態數據,而Region_A_replica_1沒有,如果請求Region_A_replica_1則只會拋出異常。當第2次RPC結束,已經獲取了Region_A中的全部數據,便可以清理掉Region_A_primary中存儲的狀態數據了。當第3次RPC時,和第1次時情況有些類似,server端暫時沒有存儲scan的狀態數據了,client便可以像第1次RPC一樣,將請求同時發給了Region_B的主備2個region。第4次RPC則像第2次一樣。總結一下:當scan進行TIMELINE Read時,只有對每個邏輯region的第1次rpc可以任意選擇region請求。
備註: Region Read Replicas功能並沒有支持批量請求,即批量Get、Scan都是直接請求primary region。
>>>>
Server側實現
1.數據同步
secondary region要支持讀請求,則必然要有數據,而secondary region又不支持寫請求,那麼數據是哪來的呢?
(圖5: RegionServer 內部結構)
從HBase的數據模型上看,數據主要分爲兩部分:MemStore和HFile。HFile存儲於HDFS上,secondary region只要及時獲知HFile的變化便可以獲取。但MemStore存在於內存,卻只有primary region持有。目前HBase Region Read Replicas功能支持兩種同步數據到secondary region的方式。
StoreFile Refresher
第一種方式是StoreFile Refresher,在HBase-1.0+版本引入。在RegionServer上有一個StorefileRefresherChore任務,會定期地在HDFS上檢查primary region的HFile有沒有變化,以此來及時的發現primary region通過flush、compact、bulk load等操作產生的新HFile。該方案實現上較爲簡單,也不需要太多額外的存儲和網絡開銷,但缺點也非常明顯,在數據寫入primary region,到secondary region可以讀到數據,有相當長的時間間隔,中間需要等待memstore的flush和StorefileRefresherChore任務的定時刷新[1]。如果要開啓這個功能,只要將hbase.regionserver.storefile.refresh.period
配置設置爲非零值即可,表示StorefileRefresherChore任務刷新的時間間隔(單位毫秒)。
Asynchronous Replication
(圖6: Asynchronous Replication示意圖)
第二種方式是Asynchronous Replication。HBase有提供集羣間replication功能,利用WAL在多個集羣之間同步數據。HBase-1.1+版本便利用replication在集羣內部同步數據,將實時寫入的WAL同步到secondary region[1]。如圖6中所示,通過實現一個特殊的ReplicationEndpoint
便可以將WAL的數據同步給集羣中的其他RegionServer。如此,primary region MemStore中的數據也通過replication實時同步到secondary region,從secondary region中也可以讀到primary region還沒有flush到HFile的數據。所以利用Asynchronous Replication
的同步方式比上面講到的StoreFile Refresher
同步方式具有更低的同步延遲。此外primary region還會將flush、compaction和bulk load事件寫到WAL,同樣由replication功能同步到secondary region。當secondary region接收到這些事件時,便也回放同樣的事件來更新自己的數據。所以對HFile文件列表的更新也比StoreFile Refresher
定時刷新的方式更加實時。在這種同步模式下,secondary region的MemStore中也是有數據的,從WAL同步的Put/Delete操作就同primary region一樣寫入MemStore,並且secondary region也會使用block cache,所以在這種模式中內存的開銷會成倍地增長。不同於primary region的是,secondary region在接收到flush事件時,並不會將MemStore中的數據flush成HFile,只會釋放掉MemStore佔用的內存。
Asynchronous Replication
功能默認是關閉的,需要設置hbase.region.replica.replication.enabled
爲true
來打開這個功能。當第一次創建一個region副本數大於1的表時,將會創建一個名爲region_replica_replication
的replication peer,這個replication peer將負責集羣內所有表region replica的數據同步。一旦開啓之後想要再關閉該功能,就不只是改hbase.region.replica.replication.enabled
爲false
了,還需要disable掉region_replica_replication
這個replication peer。
2.存在的問題
HFile的過期時間
在以上兩種數據同步方式中,都會在多個RegionServer上打開同一個HFile,所以當primary region進行完major compaction之後,secondary region因爲HFile文件變化更新不及時,依舊引用着舊的HFile。目前並沒有有效的措施保證HFile文件並不會被過早的刪除。只能是將配置項hbase.master.hfilecleaner.ttl
設置爲一個較大的值,比如1小時,以此來儘量避免請求過程中不會出錯,但這同時也會增加HDFS的存儲開銷。
replication不能同步meta表數據
目前的Asynchronous Replication功能並不能同步meta表的WAL數據(最初該功能是用於集羣間同步數據的,畢竟不能把meta數據同步給其他集羣)。所以對於meta表的操作,並不能通過replication儘快的同步到secondary region,只能通過類似於StoreFile Refresher
的方式,使用定時刷新的任務來同步meta表HFile文件的變化。所以單獨存在hbase.regionserver.meta.storefile.refresh.period
配置項用於控制meta表StoreFile的更新時間。該配置項並不同於StoreFile Refresher
功能的hbase.regionserver.storefile.refresh.period
。
內存消耗
在之前已經提到,Asynchronous Replication同步因爲使用MemStore和block cache,會導致內存開銷成倍增加。並且secondary region並不會主動進行flush,只會當接收到同步的WAL中的flush事件時,纔會進行flush。在一些極端情況下,比如replication阻塞收不到flush事件、primary region確實長時間沒有進行flush、secondary region持有的內存得不到釋放,而一個RegionServer上往往同時有多個primary region和secondary region,內存的過度消耗可能會阻塞primary region正常的寫入操作,也會阻塞replication同步的flush事件[1]。
所以HBase提供了一個配置項hbase.region.replica.storefile.refresh.memstore.multiplier
(默認值爲4),表示如果secondary region的MemStore比primary region最大的MemStore的4倍還要大時,便允secondary region自行refresh,檢查HFile文件是否變化,如果primary region早已flush過,卻因爲replication阻塞沒有同步到,則可以利用該機制進行flush。默認情況下最好不要觸發這個操作,可以通過把該配置項設置大一些來避免。
secondary region Failover
當一個secondary region剛open或者fail over,此時必然丟失了之前MemStore的數據,因爲secondary region畢竟不能像primary region一樣通過回放WAL來恢復MemStore。如果此時直接提供讀服務,則可能出現數據版本回退的問題,即恢復之後比恢復之前讀到的數據更舊。爲了避免數據回退,secondary region就必須等待primary region進行一次完整的flush操作或open region事件,在這之前,secondary region都將拒絕接服務。hbase.region.replica.wait.for.primary.flush
配置項是該機制的開關,默認是enable
開啓。
>>>>
如何使用
1.配置
server端
配置項 |
默認值 |
單位 |
描述 |
---|---|---|---|
hbase.master.hfilecleaner.ttl | 300000(5分鐘) | 毫秒 | StoreFile文件的過期刪除時間間隔 |
hbase.master.loadbalancer.class | org.apache.hadoop.hbase.master.balancer.StochasticLoadBalancer | 默認的實現可以保證region的replicas儘量不會分佈在同一個RegionServer上,如果修改該配置,要注意replicas的分佈 | |
hbase.meta.replica.count | 1 | 個 | meta表的region副本數 |
hbase.region.replica.replication.enabled | false | 是否開啓Asnyc WAL replication 功能,開啓後再想關閉,需要改爲false之後再disable掉region_replica_replication 的peer |
|
hbase.region.replica.storefile.refresh.memstore.multiplier | 4 | 倍 | secondary region的MemStore大於同RegionServer上primary region最大的MemStore該倍數時,會觸發刷新StoreFile文件列表的任務 |
hbase.region.replica.wait.for.primary.flush | true | secondary region open之後,是否要等待primary region進行一次flush再提供服務 | |
hbase.regionserver.meta.storefile.refresh.period | 0 | 毫秒 | secondary region刷新hbase:meta表StoreFile的時間間隔,默認0爲關閉 |
hbase.regionserver.storefile.refresh.period | 0 | 毫秒 | secondary region刷新StoreFile的時間間隔,默認0爲關閉 |
(表1: server端配置項)
client端
配置項 |
默認值 |
單位 |
描述 |
---|---|---|---|
hbase.ipc.client.specificThreadForWriting | false | 是否使用特殊線程用於寫請求。使用region replicas功能,經常會在IO過程中中斷線程,所以必須開啓該配置 | |
hbase.client.primaryCallTimeout.get | 10000 | 微秒 | TIMELINE一致性Get時,等待primary region響應的時間,超時之後便請求secondary region |
hbase.client.primaryCallTimeout.scan | 10000 | 微秒 | TIMELINE一致性Scan時,等待primary region響應的時間,超時之後便請求secondary region |
hbase.meta.replicas.use | false | 是否使用meta表的secondary region |
(表2: client端配置項)
2.建表
REGION_REPLICATION
參數控制表中region有多少備份,默認值爲1,即只有primary region。
shell方式建表:
create 't1', 'f1', {REGION_REPLICATION => 2}
shell方式修改表:
alter 't1', {REGION_REPLICATION => 2}
3.Shell Client
允許以TIMELINE
的一致性讀取數據:
hbase(main):001:0> get 't1','r6', {CONSISTENCY => "TIMELINE"}
hbase(main):001:0> get 't1','r6', {CONSISTENCY => "TIMELINE", REGION_REPLICA_ID => 1}
4.Java Client
Get示例:
Get get = new Get(row);
get.setConsistency(Consistency.TIMELINE);
...
Result result = table.get(get);
Scan示例:
Scan scan = new Scan();
scan.setConsistency(Consistency.TIMELINE);
...
ResultScanner scanner = table.getScanner(scan);
可以通過Result.isStale()
判斷數據是否來自於secondary region:
Result result = table.get(get);
if (result.isStale()) {
...
}
>>>>
性能測試
1.機器配置
HBase版本:2.2.0
HDFS版本:3.1.4
service |
job |
實例數 |
cpu |
disk |
netowork |
comment |
---|---|---|---|---|---|---|
HBase | master | 2 | - | - | - | - |
HBase | region server | 5 | 24 core | 12*3.7T HDD | 10000bps | onheap=50g/offheap=50g |
HDFS | namenode | 2 | - | - | - | onheap=10g |
HDFS | datanode | 5 | 24 core | 12*3.7T HDD | 10000bps | onheap=2g |
(表3: 壓測機器配置)
2.不限制QPS
strong |
timeline-10ms |
rate |
|
---|---|---|---|
qps_sec | 12608.23 | 11171.18 | -11.4% |
avg_latency_us | 3760.69 | 4276.76 | 13.72% |
p99_latency_us | 32512.23 | 31148.14 | -4.2% |
p999_latency_us | 64646.38 | 58621.93 | -9.32% |
p9999_latency_us | 136835.92 | 115951.63 | -15.26% |
(表4: 不限制QPS的壓測數據)
3.限制7000QPS
strong |
timeline-10ms |
rate |
|
---|---|---|---|
qps_sec | 6999.58 | 6999.56 | -0.0% |
avg_latency_us | 3223.38 | 3495.51 | 8.44% |
p99_latency_us | 23793.54 | 24469.25 | 2.84% |
p999_latency_us | 48791.00 | 39795.06 | -18.44% |
p9999_latency_us | 93389.08 | 78618.61 | -15.82% |
(表5: 限制7000QPS的壓測數據)
對單Client實例做壓力測試,hbase.client.primaryCallTimeout.get
參數設置爲10000,即等待primary region響應的時間超時10ms之後便請求secondary region。第一組不限制QPS的壓測(表4)中,可以看出開啓TIMELINE Read之後,QPS有一定損失,平均延遲有一定升高,P99/P999/P9999延遲有一定程度優化,但優化效果有限。因爲read replicas會增加線程資源的使用,而日常使用也不會把Client側壓到極限,所以又做了一組限制QPS的壓測(表5),P999/P9999延遲有15%+的下降,且可以看到各項延遲指標對比第一組測試均有所好轉。
>>>>
總結
Region Read Replicas
功能設計初衷是爲了提高HBase的讀可用性,並在一定程度上降低讀請求的毛刺,但考慮到設計上的侷限性, 應用場景有限。另外從瞭解到的情況來看,Region Read Replicas
並沒有在大規模的生產集羣中得到使用。後續小米HBase還會繼續趟坑試用,並不斷完善這個功能。
優點
-
當應用依賴是隻讀的表,或者應用並不要求強一致性或者讀單調一致性(但要求最終一致性,可以接受短時間內數據不一致)時,可以使用該功能來提高讀可用性。在RegionServer或Region出現單點故障恢復期間或長時間Full GC期間儘量保證業務讀請求正常,減少MTTR過長對業務產生的影響,同時也可以減少大量重試請求進一步增加故障節點的壓力。
-
對於部分不要求強一致性且對延遲毛刺有一定要求的應用,當在Client側QPS較低或者CPU、帶寬等資源富餘時,可以使用該功能降低讀請求的P99/P999/P9999延遲。
缺點
-
多倍的MemStore/BlockCache導致更多的內存消耗。
-
集羣內部數據複製導致更多的RPC請求和網絡帶寬消耗。
>>>>
參考
-
[1]官方文檔:
http://hbase.apache.org/book.html#arch.timelineconsistent.reads
-
[2]參考書籍《數據密集型應用系統設計》:
https://book.douban.com/subject/30329536/
-
[3]HBASE-10070:
https://issues.apache.org/jira/browse/HBASE-10070
-
[4]High Availability Design for reads.pdf
好看的人都點了在看