Par rapport à Protocol Buffers, PlainBuffer offre de meilleures performances pour la sérialisation et la désérialisation des petits objets. C'est pourquoi Tablestore utilise le format PlainBuffer pour structurer les données.
Cette rubrique s'adresse aux développeurs qui implémentent un SDK Tablestore personnalisé ou qui déboguent la transmission brute des données au niveau du protocole. Les développeurs d'applications utilisant un SDK existant n'ont pas besoin de comprendre le fonctionnement interne de PlainBuffer.
Définition du format
Un message PlainBuffer se compose d'un en-tête suivi d'une ou plusieurs lignes. Chaque ligne contient une section de clé primaire et une section de colonne d'attribut ; ces deux sections peuvent être facultatives selon l'opération (voir la grammaire ci-dessous). Les tags servent de délimiteurs de champ : chaque tag indique à l'analyseur syntaxique l'élément suivant, ce qui lui permet de parcourir le flux d'octets sans ambiguïté.
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)
Valeurs des tags
La plupart des tags sont codés sur un seul octet et marquent le début d'un champ ; tag_header occupe 4 octets. L'analyseur syntaxique lit le tag, identifie le champ suivant, lit le nombre d'octets approprié, puis passe au tag suivant.
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)
Valeurs ValueType
Valeurs valides pour value_type dans 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
Calcul de la somme de contrôle
Les sommes de contrôle utilisent l'algorithme CRC8. Le calcul suit cette logique :
Le nom, la valeur, le type et l'horodatage de chaque cellule contribuent à la somme de contrôle de cette cellule.
Le marqueur de suppression de chaque ligne contribue pour un seul octet :
0x1si la ligne possède un marqueur de suppression,0x0sinon.La somme de contrôle de la ligne est calculée en appliquant CRC8 aux sommes de contrôle individuelles des cellules, et non aux données brutes des cellules.
Implémentation Java :
Le code suivant est extrait de $tablestore-4.2.1-sources/com/alicloud/openservices/tablestore/core/protocol/PlainBufferCrc8.java. Pour plus d'informations, consultez Installation.
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;
}
Exemple
La ligne suivante comporte deux colonnes de clé primaire et quatre colonnes d'attribut.
-
Colonnes de clé primaire :
[pk1:string:iampk]
[pk2:integer:100]
-
Colonnes d'attribut :
[column1:string:bad:1001]
[column2:integer:128:1002]
[column3:double:34.2:1003]
[column4:del_all_versions]
Encodage :
<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]
Lecture octet par octet de la première cellule de clé primaire :
|
Octets |
Tag/champ |
Signification |
|
|
|
Début d'une cellule |
|
|
|
Le nom de la cellule suit |
|
|
value_type |
|
|
|
value_len |
Le nom fait 3 octets de long |
|
|
value_data |
Nom de la cellule : |
|
|
|
La valeur de la cellule suit |
|
|
value_type |
|
|
|
value_len |
La valeur fait 5 octets de long |
|
|
value_data |
Valeur de la cellule : |
|
|
|
Somme de contrôle portant sur le nom, la valeur, le type et l'horodatage |
La deuxième cellule de clé primaire suit la même structure, sauf que le type de valeur est 0x0 (VT_INTEGER) et que value_len vaut 8 (un entier 64 bits).
Les cellules d'attribut ajoutent un champ d'horodatage (0x7 = tag_cell_ts) après la valeur. La colonne column4 ne contient aucune valeur : le tag 0x6 (tag_cell_op) avec la valeur 1 indique une opération de suppression de toutes les versions.