? ? ? ?HBase中的寫入方法有主要分為實(shí)時(shí)的put以及批量導(dǎo)入bulkload,這里主要介紹一下實(shí)時(shí)寫入put以及一些HBase里面與MVCC相關(guān)的東西,版本依舊是社區(qū)版1.0.0。
? ? ? ?在regionserver服務(wù)端,與put相關(guān)的操作差不多最后都會(huì)調(diào)用到HRegion的doMiniBatchMutation(BatchOperationInProgress)方法,以下截圖是從RSRpcServices的mutate方法一直到該方法的調(diào)用棧(從下往上看)。

? ? ? ?下面主要看doMiniBatchMutation方法,這個(gè)方法主要處理mutate(例如put、delete)以及replay過程,關(guān)于replay的過程會(huì)選擇性的跳過。
? ? ? ?函數(shù)體中有很詳細(xì)的注釋,主要把一個(gè)批量mutate過程分為了以下幾個(gè)步驟:
1.獲取相關(guān)的鎖,由于HBase要確保行一級(jí)的原子性,所以獲取鎖的時(shí)候獲取的是整個(gè)rowkey的鎖而不是單個(gè)cell的鎖;也只有當(dāng)至少獲取一個(gè)鎖的時(shí)候,這個(gè)方法才會(huì)繼續(xù),否則直接返回。
2.更新cell中的時(shí)間戳(timestamp)以及獲取mvcc相關(guān)參數(shù),其中timestamp(也可以叫做version)可以在客戶端自己手動(dòng)指定,所以在一致性上不能用來做參考,也許正是因此才會(huì)引入一個(gè)叫做sequenceId的概念(當(dāng)然更多的用途是為了保證修改操作在HLog里面的順序)來完成mvcc,最后會(huì)介紹一下mvcc以及在這里HBase是如何處理mvcc的。
3.將這些put操作寫入memstore,雖然數(shù)據(jù)庫系統(tǒng)中寫日志永遠(yuǎn)比寫數(shù)據(jù)重要,但是這里可以認(rèn)為當(dāng)前“事務(wù)”尚未提交,即使現(xiàn)在掛了沒有日志恢復(fù)也不要緊,因?yàn)檫@個(gè)“事務(wù)”是沒有提交的。
4.構(gòu)建walEdit,這一步主要是為了構(gòu)建WALEdit類型的walEdit變量,這個(gè)變量主要是以list的形式聚合了很多HBase里面cell的概念,以后會(huì)寫入到HLog中。
5.追加剛才構(gòu)建好的walEdit:首先構(gòu)造一個(gè)walKey,注意這里的walKey的sequenceId為默認(rèn)值-1,到后面才會(huì)修改為跟region掛鉤的唯一遞增id;接著調(diào)用wal的append方法并返回一個(gè)遞增數(shù)值(txid),用來表示這個(gè)追加到wal內(nèi)存中日志條目的編號(hào),在第七步中這個(gè)數(shù)值將會(huì)作為參數(shù)傳入,確保該數(shù)值之前的日志信息都被寫入到HLog日志文件中,而且在append方法中會(huì)保證walKey的sequenceId變成了region的sequenceId(也是一個(gè)遞增序列)。
6.釋放獲取的鎖。
7.將wal寫入磁盤,正如第五步所說,這里保證txid以及之前的日志條目都被寫入到日志文件中了,一旦寫完便可以認(rèn)為這個(gè)“事務(wù)”成功了,這里跟MySQL里面的auto commit很像。
8.提交本次操作,讓put操作對讀可見,核心步驟就是增加對應(yīng)memstore的readpoint,使得以前講的MemStoreScanner可以看見put過來的數(shù)據(jù),這根后面講的mvcc有關(guān)。
關(guān)于HBase里面的MVCC
? ? ? ?mvcc即多版本并發(fā)控制,針對的問題就就是在數(shù)據(jù)庫系統(tǒng)中,什么樣的數(shù)據(jù)是應(yīng)該被看到的,什么樣的數(shù)據(jù)即使有也不應(yīng)該被讀取,典型的情況就是uncommitted的數(shù)據(jù)。現(xiàn)在的數(shù)據(jù)庫系統(tǒng)即使不是通過天然具有遞增屬性的時(shí)間也是通過類似的遞增數(shù)列完成mvcc的,給每一條插入的數(shù)據(jù)按照時(shí)間打個(gè)標(biāo)簽T,讀取事務(wù)開始的時(shí)候也獲取當(dāng)前時(shí)間t,這個(gè)讀取事務(wù)只能讀取T<t的數(shù)據(jù)。
? ? ? 假設(shè)現(xiàn)在線程A執(zhí)行完上面的第三步,將C這個(gè)cell插入到了memstore中,正在此時(shí)線程B要讀取對應(yīng)rowkey的所有數(shù)據(jù),那么C該不該被讀取到呢?HBase里面的事物隔離級(jí)別默認(rèn)情況下可以說是“read committed”所以C明顯不該被讀取,要如何避免呢?timestamp字段默認(rèn)情況下是服務(wù)端的系統(tǒng)時(shí)間,但是用戶可以在客戶端隨意修改覆蓋,不能用作mvcc。但是剛好put(或者說是mutate操作)在wal中是有序的,還是根據(jù)region提供的sequenceId生成的唯一遞增序列,可以用這種唯一遞增序列來做飯多版本并發(fā)控制的效果。接下來只需要保證每次到了第八步的時(shí)候保證memstore的readpoint大于已經(jīng)提交的最大sequenceId,就可以正確讀取了。生成這個(gè)與mvcc相關(guān)的sequenceId是在上面的第二個(gè)步驟中進(jìn)行的。
? ? ? ?但是有個(gè)問題沒有解決,很有可能先開始的事務(wù)A(假設(shè)sequenceId為1)比后開始的事務(wù)B(假設(shè)sequenceId為2)晚完成。B完成之后將memstore的readpoint設(shè)置為2了,這樣后面的讀取不就可以通過memstore暴露的api讀取到A尚未提交的數(shù)據(jù)了嗎?在HBase中與mvcc相關(guān)的類里面尚未提交的put操作對應(yīng)的sequenceId都增加了10億,以保證在沒有提交之前這些數(shù)據(jù)是不能讀取到的,這樣一來,A在沒有提交之前對應(yīng)的sequenceId實(shí)際是1000000001,根據(jù)目前的sequenceId(2)是看不到的。
? ? ? ?以上是關(guān)于HBase里面寫入的理解,但是有個(gè)問題就是找了好久都沒發(fā)現(xiàn)第八步完成后是怎樣從第三步的memstore里面的減去這多加的10億。