使用HBase存儲每個Cell變更歷史的設計思路

前言

寫在前面,首先本文僅僅是個人思路的設計體現,並非什麼業內標準,只是希望能給閱讀的人一點經驗之談,如有不足,直接指正即可。

前置知識

以下內容均可以在官方文檔中直接找到對應標題。

  1. Number of Versions。版本數,最大版本數用於確定需要保留多少個版本數據,對應的版本號可以使用時間戳(long)去標識,從而達到記錄歷史記錄的目的,不宜過大(超過一百)。最小版本數,可以與列簇的生命週期共同使用,至少保留多少個版本。默認爲0,表示禁用。均在HTableDescriptor 中進行修改。
private void getColumnDesc(HTableDescriptor tableDescriptor, int minVersion, int maxVersion, String... columnFamily) {
        for (String column : columnFamily) {
            HColumnDescriptor columnDescriptor;
            if (tableDescriptor.hasFamily(Bytes.toBytes(column))) {
                columnDescriptor = tableDescriptor.getFamily(Bytes.toBytes(column));
            } else {
                columnDescriptor = new HColumnDescriptor(column);
            }
            if (minVersion >= 0) {
                columnDescriptor.setMinVersions(minVersion);
            }
            if (maxVersion > 0) {
                columnDescriptor.setMaxVersions(maxVersion);
            }
            if (tableDescriptor.hasFamily(Bytes.toBytes(column))) {
                tableDescriptor.modifyFamily(columnDescriptor);
            } else {
                tableDescriptor.addFamily(columnDescriptor);
            }
        }
    }
  1. TTL。TimeToLive,生存時間,到達對應時間的內容會被自動刪除,分爲列簇的生存時間和單元的生存時間。列簇的生存時間單位爲秒,表示對該列簇下的所有限定符均生效,默認爲FOREVER,永久有效。單元的生存時間單位爲毫秒,表示僅對Mutation操作中涉及的列生效,且設定的時間無法超過列簇設定的生存時間。
private void getColumnDesc(HTableDescriptor tableDescriptor, int ttl, String... columnFamily) {
        for (String column : columnFamily) {
            HColumnDescriptor columnDescriptor;
            if (tableDescriptor.hasFamily(Bytes.toBytes(column))) {
                columnDescriptor = tableDescriptor.getFamily(Bytes.toBytes(column));
            } else {
                columnDescriptor = new HColumnDescriptor(column);
            }
            if (ttl > 0) {
                columnDescriptor.setMinVersions(0);
                columnDescriptor.setTimeToLive(ttl);
            }
            if (tableDescriptor.hasFamily(Bytes.toBytes(column))) {
                tableDescriptor.modifyFamily(columnDescriptor);
            } else {
                tableDescriptor.addFamily(columnDescriptor);
            }
        }
    }
Put put=new Put(Bytes.toBytes(rowKey), timeStamp);
put.setTTL(360*1000);

設計思路

1.需要兩個列簇用於分別保存真實值和歷史值,其中存儲歷史值的列簇需要設定最大版本數(50)。

2.存儲原則是真實列簇只保留最新數據,歷史列簇保留所有變更。

2.1真實列簇新增、修改操作時(put),對應的歷史列簇內容進行同樣的操作,但是額外的設定了相應的時間戳。

2.2真實列簇刪除操作時(delete),對應的歷史列簇內容存入一個"".toBytes()(byte[]長度爲0),並且設定對應的實際那戳,用於表示對應的內容被刪除。

2.3真實列簇設置了失效時間(put、setTTL),對應的歷史列簇內容根據時間戳+TTL設定未來時間版本,並且給對應內容加上"_f_".toBytes()作爲前綴,用於表示該內容未來會失效。

3.查詢操作時,如果僅獲取對應rowKey的內容,則設定Get操作對應的family指定爲真實列簇。如果需要獲取歷史變更,則Get操作對應的family指定爲歷史列簇,並且由於數據取出時會以時間戳版本倒序進行排序,所以需要自行編寫遍歷邏輯,並且由於插入了未來失效的內容(本質上不屬於變更),所以在上一個值到下個值的變化中穿插的失效內容,需要單獨處理。舉例而言:
列限定符his:A->B->_f_A->C->""
應該獲得以下變更內容:新增A->修改A爲B->A失效->修改B爲C->刪除C

Get get = new Get(Bytes.toBytes(rowKey));
if (startTimeStamp < 0) {
    startTimeStamp = 0;
}
if (endTimeStamp <= 0) {
    endTimeStamp = 9223372036854775807L;
}
get.setTimeRange(startTimeStamp, endTimeStamp);
get.setMaxVersions();
for (String column : columnFamily) {
    get.addFamily(Bytes.toBytes(column));
}
Result result = table.get(get);

多餘的話

放心,這個思路肯定能實現而且不是很難,唯一的問題是數據量大的情況下似乎不是那麼的好用,但是使用傳統的關係型數據庫又難以存儲如此規模的變更數據,另外還需要合適的三元組形式以及高效的歷史記錄查詢。

所以我想應該會有更好的實現去記錄HBase每個Cell的變更歷史。如有任何建議,還望不吝賜教。先謝過了。

發佈了30 篇原創文章 · 獲贊 11 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章