前言
寫在前面,首先本文僅僅是個人思路的設計體現,並非什麼業內標準,只是希望能給閱讀的人一點經驗之談,如有不足,直接指正即可。
前置知識
以下內容均可以在官方文檔中直接找到對應標題。
- 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);
}
}
}
- 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的變更歷史。如有任何建議,還望不吝賜教。先謝過了。