PlainBuffer 是Table Store自訂的行資料序列化格式,針對小對象的序列化和解析效能進行了最佳化。本文檔面向需要實現自訂 Tablestore SDK或調試底層資料轉送的開發人員。
格式定義
PlainBuffer 訊息由一個 header 和若干行資料群組成。每行包含主鍵列和屬性列兩個部分(根據操作類型,兩者均可選)。tag 作為欄位分隔符號,告知解析器接下來需要讀取的內容類型,使解析器能夠在位元組流中無歧義地推進。
plainbuffer = tag_header row1 [row2] [row3]
row = ( pk [attr] | [pk] attr | pk attr ) [tag_delete_marker] row_checksum;
pk = tag_pk cell_1 [cell_2] [cell_3]
attr = tag_attr cell1 [cell_2] [cell_3]
cell = tag_cell cell_name [cell_value] [cell_op] [cell_ts] cell_checksum
cell_name = tag_cell_name formated_value
cell_value = tag_cell_value formated_value
cell_op = tag_cell_op cell_op_value
cell_ts = tag_cell_ts cell_ts_value
row_checksum = tag_row_checksum row_crc8
cell_checksum = tag_cell_checksum row_crc8
formated_value = value_type value_len value_data
value_type = int8
value_len = int32
cell_op_value = delete_all_version | delete_one_version
cell_ts_value = int64
delete_all_version = 0x01 (1byte)
delete_one_version = 0x03 (1byte)
Tag 取值
大多數 tag 占 1 位元組,tag_header 占 4 位元組。解析器讀取 tag 後確定接下來的欄位類型,讀取相應位元組數,然後推進到下一個 tag。
tag_header = 0x75 (4byte)
tag_pk = 0x01 (1byte)
tag_attr = 0x02 (1byte)
tag_cell = 0x03 (1byte)
tag_cell_name = 0x04 (1byte)
tag_cell_value = 0x05 (1byte)
tag_cell_op = 0x06 (1byte)
tag_cell_ts = 0x07 (1byte)
tag_delete_marker = 0x08 (1byte)
tag_row_checksum = 0x09 (1byte)
tag_cell_checksum = 0x0A (1byte)
ValueType 取值
formated_value 中 value_type 的有效取值如下:
VT_INTEGER = 0x0
VT_DOUBLE = 0x1
VT_BOOLEAN = 0x2
VT_STRING = 0x3
VT_NULL = 0x6
VT_BLOB = 0x7
VT_INF_MIN = 0x9
VT_INF_MAX = 0xa
VT_AUTO_INCREMENT = 0xb
計算 Checksum
Checksum 使用 CRC8 演算法,計算規則如下:
每個 cell 的 name、value、type 和 timestamp 參與該 cell 的 checksum 計算。
每行的 delete marker 貢獻 1 位元組:有 delete marker 時補
0x1,無則補0x0。行的 checksum 通過對各 cell 的 checksum 執行 CRC8 得出,不直接對原始 cell 資料做 CRC。
Java 實現:
以下代碼摘自
$tablestore-4.2.1-sources/com/alicloud/openservices/tablestore/core/protocol/PlainBufferCrc8.java
,具體參見
。
public static byte getChecksum(byte crc, PlainBufferCell cell) throws IOException {
if (cell.hasCellName()) {
crc = crc8(crc, cell.getNameRawData());
}
if (cell.hasCellValue()) {
if (cell.isPk()) {
crc = cell.getPkCellValue().getChecksum(crc);
} else {
crc = cell.getCellValue().getChecksum(crc);
}
}
if (cell.hasCellTimestamp()) {
crc = crc8(crc, cell.getCellTimestamp());
}
if (cell.hasCellType()) {
crc = crc8(crc, cell.getCellType());
}
return crc;
}
public static byte getChecksum(byte crc, PlainBufferRow row) throws IOException {
for (PlainBufferCell cell : row.getPrimaryKey()) {
crc = crc8(crc, cell.getChecksum());
}
for (PlainBufferCell cell : row.getCells()) {
crc = crc8(crc, cell.getChecksum());
}
byte del = 0;
if (row.hasDeleteMarker()) {
del = (byte)0x1;
}
crc = crc8(crc, del);
return crc;
}
舉例
樣本行包含 2 列主鍵和 4 列屬性列:
-
主鍵列:
[pk1:string:iampk]
[pk2:integer:100]
-
屬性列:
[column1:string:bad:1001]
[column2:integer:128:1002]
[column3:double:34.2:1003]
[column4:del_all_versions]
編碼結果:
<Header開始>[0x75]
<主鍵列開始>[0x1]
<Cell1>[0x3][0x4][0x3][3][pk1][0x5][0x3][5][iampk][_cell_checksum]
<Cell2>[0x3][0x4][0x3][3][pk2][0x5][0x0][8][100][_cell_checksum]
<屬性列開始>[0x2]
<Cell1>[0x3][0x4][0x3][7][column1][0x5][0x3][3][bad][0x7][1001][_cell_checksum]
<Cell2>[0x3][0x4][0x3][7][column2][0x5][0x0][8][128][0x7][1002][_cell_checksum]
<Cell3>[0x3][0x4][0x3][7][column3][0x5][0x1][8][34.2][0x7][1003][_cell_checksum]
<Cell4>[0x3][0x4][0x3][7][column4][0x6][1][_cell_checksum]
[_row_check_sum]
以第一個主鍵 cell(pk1)為例,逐位元組解析如下:
|
位元組 |
Tag/欄位 |
含義 |
|
|
|
cell 開始 |
|
|
|
接下來是 cell name |
|
|
value_type |
|
|
|
value_len |
name 長度為 3 位元組 |
|
|
value_data |
cell name: |
|
|
|
接下來是 cell value |
|
|
value_type |
|
|
|
value_len |
value 長度為 5 位元組 |
|
|
value_data |
cell value: |
|
|
|
對 name、value、type 和 timestamp 做 checksum |
第二個主鍵 cell(pk2)結構相同,差異在於 value_type 為 0x0(VT_INTEGER),value_len 為 8(64 位元整數)。
屬性列 cell 在 value 之後多一個 timestamp 欄位(0x7 = tag_cell_ts)。column4 沒有 value,0x6(tag_cell_op)後接值 1,表示刪除所有版本操作。