O PlainBuffer oferece melhor desempenho que o Protocol Buffers na serialização e análise de objetos pequenos. Por isso, o Tablestore usa esse formato para estruturar dados.
Este documento destina-se a desenvolvedores que implementam um SDK personalizado do Tablestore ou depuram a transmissão de dados brutos no nível de protocolo. Quem utiliza um SDK existente não precisa conhecer os detalhes internos do PlainBuffer.
Definição do formato
Uma mensagem PlainBuffer consiste em um cabeçalho seguido por uma ou mais linhas. Cada linha contém uma seção de chave primária e outra de colunas de atributo. Ambas podem ser opcionais, dependendo da operação (consulte a gramática abaixo). As tags atuam como delimitadores de campo: cada uma indica ao parser o próximo elemento, permitindo que ele avance pelo fluxo de bytes sem ambiguidade.
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)
Valores das tags
A maioria das tags ocupa um único byte e marca o início de um campo; tag_header ocupa 4 bytes. O parser lê a tag, identifica o campo seguinte, consome a quantidade adequada de bytes e avança para a próxima 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)
Valores de ValueType
Valores válidos para value_type em formated_value:
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
Cálculo do checksum
Os checksums usam CRC8. O cálculo segue esta lógica:
Nome, valor, tipo e timestamp de cada célula compõem o checksum correspondente.
O marcador de exclusão de cada linha contribui com um único byte:
0x1se houver marcador, ou0x0caso contrário.O checksum da linha resulta da aplicação do CRC8 sobre os checksums individuais das células, não sobre os dados brutos.
Implementação em Java:
O código a seguir foi extraído de $tablestore-4.2.1-sources/com/alicloud/openservices/tablestore/core/protocol/PlainBufferCrc8.java. Para mais informações, consulte Instalação.
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;
}
Exemplo
A linha a seguir tem duas colunas de chave primária e quatro colunas de atributo.
-
Colunas de chave primária:
[pk1:string:iampk]
[pk2:integer:100]
-
Colunas de atributo:
[column1:string:bad:1001]
[column2:integer:128:1002]
[column3:double:34.2:1003]
[column4:exclua_all_versions]
Codificação:
<The start position for headers>[0x75]
<The start position for primary key columns>[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]
<The start position for attribute columns>[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]
Leitura da primeira célula de chave primária byte a byte:
|
Bytes |
Tag/campo |
Significado |
|
|
|
Início de uma célula |
|
|
|
Indica que o nome da célula vem a seguir |
|
|
value_type |
|
|
|
value_len |
O nome tem 3 bytes |
|
|
value_data |
Nome da célula: |
|
|
|
Indica que o valor da célula vem a seguir |
|
|
value_type |
|
|
|
value_len |
O valor tem 5 bytes |
|
|
value_data |
Valor da célula: |
|
|
|
Checksum calculado sobre nome, valor, tipo e timestamp |
A segunda célula de chave primária segue a mesma estrutura, mas com tipo de valor 0x0 (VT_INTEGER) e value_len igual a 8 (inteiro de 64 bits).
As células de atributo incluem um campo de timestamp (0x7 = tag_cell_ts) após o valor. A column4 não tem valor: a tag 0x6 (tag_cell_op) com valor 1 indica uma operação de exclusão de todas as versões.