《HBase权威指南》一3.4 行锁
本節(jié)書摘來異步社區(qū)《HBase權(quán)威指南》一書中的第3章,第3.4節(jié),作者: 【美】Lars George 譯者: 代志遠(yuǎn) , 劉佳 , 蔣杰 責(zé)編: 楊海玲,更多章節(jié)內(nèi)容可以訪問云棲社區(qū)“異步社區(qū)”公眾號(hào)查看。
3.4 行鎖
像put()、delete()、checkAndPut()這樣的修改操作是獨(dú)立執(zhí)行的,這意味著在一個(gè)串行方式的執(zhí)行中,對(duì)于每一行必須保證行級(jí)別的操作是原子性的。region服務(wù)器提供了一個(gè)行鎖(row lock)的特性,這個(gè)特性保證了只有一個(gè)客戶端能獲取一行數(shù)據(jù)相應(yīng)的鎖,同時(shí)對(duì)該行進(jìn)行修改。在實(shí)踐中,大部分客戶端應(yīng)用程序都沒有提供顯式的鎖,而是使用這個(gè)機(jī)制來保障每個(gè)操作的獨(dú)立性。
文字用戶應(yīng)該盡可能地避免使用行鎖。就像在RDBMS中,兩個(gè)客戶端很可能在擁有對(duì)方要請(qǐng)求的鎖時(shí),又同時(shí)請(qǐng)求對(duì)方已擁有的鎖,這樣便形成了一個(gè)死鎖。
鎖超時(shí)之前,兩個(gè)被阻塞的客戶端會(huì)占用一個(gè)服務(wù)器端的處理線程(handler),而這個(gè)線程是一種十分稀缺的資源。如果在一個(gè)頻繁操作的行上發(fā)生了這種情況,那么很多其他的客戶端會(huì)占用掉其所有的處理線程,阻塞所有其他客戶端訪問這臺(tái)服務(wù)器,導(dǎo)致這個(gè)region服務(wù)器將不能為其負(fù)責(zé)的region內(nèi)的行提供服務(wù)。
重申一下:在不必要的情況下,盡量不要使用行鎖。如果必須使用,那么一定要節(jié)約占用鎖的時(shí)間!
比如,當(dāng)使用put()訪問服務(wù)器時(shí),Put實(shí)例可以通過以下構(gòu)造函數(shù)生成:
這個(gè)構(gòu)造函數(shù)就沒有RowLock實(shí)例參數(shù),所以服務(wù)器會(huì)在調(diào)用期間創(chuàng)建一個(gè)鎖。實(shí)際上,通過客戶端的API,得不到這個(gè)生存期短暫的服務(wù)器端的鎖的實(shí)例。
除了服務(wù)器端隱式加鎖之外,客戶端也可以顯式地對(duì)單行數(shù)據(jù)的多次操作進(jìn)行加鎖,通過以下調(diào)用便可以做到:
RowLock lockRow(byte[] row) throws IOException void unlockRow(RowLock rl) throws IOException第一個(gè)調(diào)用lockRow()需要一個(gè)行鍵作為參數(shù),返回一個(gè)RowLock的實(shí)例,這個(gè)實(shí)例可以供后續(xù)的Put或者Delete的構(gòu)造函數(shù)使用。一旦不再需要鎖時(shí),必須通過unlockRow()調(diào)用來釋放它。
每一個(gè)排他鎖(unique lock),無論是由服務(wù)器提供的,還是通過客戶端API傳入的,都能保護(hù)這一行不被其他鎖鎖定。換句話說,鎖必須針對(duì)整個(gè)行,并且指定其行鍵,一旦它獲得鎖定權(quán)就能防止其他的并發(fā)修改。
當(dāng)一個(gè)鎖被服務(wù)器端或客戶端顯式獲取之后,其他所有想要對(duì)這行數(shù)據(jù)加鎖的客戶端將會(huì)等待,直到當(dāng)前鎖被釋放,或者鎖的租期超時(shí)。后者是為了確保錯(cuò)誤進(jìn)程不會(huì)占用鎖太長時(shí)間或無限期占用。
圖像說明文字默認(rèn)的鎖超時(shí)時(shí)間是一分鐘,但是可以在hbase-site.xml文件中添加以下配置項(xiàng)來修改這個(gè)默認(rèn)值,時(shí)間以毫秒為單位:
< property>< name>hbase.regionserver.lease.period< /name>< value>120000< /value> < /property>通過添加以上代碼,超時(shí)時(shí)間被設(shè)置為原來的兩倍——120秒也就是2分鐘。小心不要將這個(gè)值設(shè)得太大,因?yàn)槊恳粋€(gè)想獲取被鎖住的行的客戶端都會(huì)阻塞并等待鎖的恢復(fù)。
例3.17展示了如何在行上創(chuàng)建一個(gè)鎖,該鎖阻塞所有的并發(fā)讀取。
例3.17 顯式使用行鎖
static class UnlockedPut implements Runnable { @Overridepublic void run() {try {HTable table = new HTable(conf,"testtable");Put put = new Put(ROW1);put.add(COLFAM1,QUAL1,VAL3);long time = System.currentTimeMillis();System.out.println("Thread trying to put same row now...");table.put(put);System.out.println("Wait time: " +(System.currentTimeMillis() - time)+ "ms");} catch(IOException e){System.err.println("Thread error: " + e);}} }System.out.println("Taking out lock..."); RowLock lock = table.lockRow(ROW1); System.out.println("Lock ID: " + lock.getLockId());Thread thread = new Thread(new UnlockedPut()); thread.start();try {System.out.println("Sleeping 5secs in main()...");Thread.sleep(5000); } catch(InterruptedException e){// ignore }try {Put put1 = new Put(ROW1,lock);put1.add(COLFAM1,QUAL1,VAL1);table.put(put1);Put put2 = new Put(ROW1,lock);put2.add(COLFAM1,QUAL1,VAL2);table.put(put2); } catch(Exception e){System.err.println("Error: " + e); } finally {System.out.println("Releasing lock...");table.unlockRow(lock); }使用一個(gè)異步的線程更新同一個(gè)行,但是不顯式加鎖。
put()調(diào)用會(huì)阻塞,直到鎖被釋放。
給整行加鎖。
啟動(dòng)那個(gè)會(huì)阻塞的異步線程。
休眠一會(huì)兒,以阻塞其他寫入操作。
在擁有鎖的情況下創(chuàng)建Put。
在擁有鎖的情況下創(chuàng)建另外一個(gè)Put。
釋放鎖,讓阻塞線程繼續(xù)執(zhí)行。
執(zhí)行這個(gè)例子代碼時(shí),應(yīng)該能在控制臺(tái)看到以下輸出:
Taking out lock... Lock ID: 4751274798057238718 Sleeping 5secs in main()... Thread trying to put same row now... Releasing lock... Wait time: 5007ms After thread ended... KV: row1/colfam1:qual1/1300775520118/Put/vlen=4,Value: val2 KV: row1/colfam1:qual1/1300775520113/Put/vlen=4,Value: val1 KV: row1/colfam1:qual1/1300775515116/Put/vlen=4,Value: val3從這個(gè)例子能看出,一個(gè)顯示的鎖是如何阻塞另一個(gè)使用隱式鎖的線程的。主線程休眠了5秒,一醒來就調(diào)用了兩次put(),分別將同一列設(shè)置為兩個(gè)不同的數(shù)值。
主線程的鎖一釋放,阻塞線程的run()方法就繼續(xù)執(zhí)行并調(diào)用了第三個(gè)put。觀察put操作在服務(wù)器端的執(zhí)行情況,會(huì)覺得很有意思。讀者可能注意到了,KeyValue實(shí)例的時(shí)間戳顯示第三個(gè)put擁有最小的時(shí)間戳,雖然這個(gè)put表面上是最后執(zhí)行的。這是因?yàn)榫€程中的put()調(diào)用是在兩個(gè)主線程中的put()之前執(zhí)行的,這之后主線程休眠了5秒。當(dāng)put被發(fā)送到服務(wù)器時(shí),如果它的時(shí)間戳沒有被顯式指定,服務(wù)器端會(huì)幫它設(shè)定時(shí)間戳,同時(shí)試圖獲得這一行的鎖。但是示例代碼中主線程已經(jīng)獲得了該行的鎖,因此服務(wù)器端的處理一直等待了5秒多,鎖被釋放才得已繼續(xù)。從上面的輸出可以看出,主線程中兩個(gè)put調(diào)用的執(zhí)行以及行的解鎖只花費(fèi)了7毫秒的時(shí)間。
Get需要鎖嗎?
修改行時(shí)鎖定行是有意義的,那么獲取數(shù)據(jù)時(shí)是否需要加鎖呢?Get類有一個(gè)構(gòu)造器允許用戶指定一個(gè)顯式的鎖:
Get(byte[] row,RowLock rowLock)這是遺留的方法,但服務(wù)器端根本用不著這種方法,因?yàn)樵讷@取數(shù)據(jù)的過程中,服務(wù)器根本不需要任何鎖,而是應(yīng)用了一個(gè)多版本的并發(fā)控制(multiversion concurrencycontrol-style)⑦機(jī)制來保證行級(jí)讀操作。例如,get()調(diào)用永遠(yuǎn)不會(huì)返回寫了一半的數(shù)據(jù),比如當(dāng)這些數(shù)據(jù)是另一個(gè)線程或者客戶端寫的。
這個(gè)就像是小規(guī)模的事務(wù)系統(tǒng):只有當(dāng)一個(gè)變動(dòng)被應(yīng)用到整個(gè)行之后,客戶端才能讀出這個(gè)改動(dòng)。當(dāng)改動(dòng)在進(jìn)行中時(shí),所有的客戶端讀取操作得到的都將是所有列以前的狀態(tài)。
當(dāng)用戶試圖使用之前申請(qǐng)的顯式鎖,但鎖的租約已經(jīng)超時(shí)并恢復(fù),用戶將會(huì)從服務(wù)器得到一個(gè)以UnknownRowLockException形式報(bào)告的錯(cuò)誤。這個(gè)異常告訴用戶服務(wù)器已經(jīng)廢棄了用戶嘗試使用的鎖。用戶應(yīng)該在代碼中丟棄這個(gè)鎖,然后請(qǐng)求一個(gè)新的鎖再試圖恢復(fù)鎖定狀態(tài)。
總結(jié)
以上是生活随笔為你收集整理的《HBase权威指南》一3.4 行锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Django通过中间件实现登录验证dem
- 下一篇: hdu 4738 Caocao's Br