Là JSON nhưng nhẹ hơn

Chúng ta hãy nhìn vào đối tượng json này nhé, đây là body của API trừ tiền (100đ) của user có Id là 300

{
  "userId" : 300,
  "transferredMoney": 100
}

Đối tượng json này tiêu tốn mất 38 bytes. Vậy có cách nào để chúng ta giảm được dung lượng của đối tượng json này không? Câu trả lời là có, chúng ta sẽ biến đối tượng json này về dạng array kiểu này (và ngầm hiểu vị trí 0 là userId và vị trí thứ 1 là transferredMoney):

[300, 100]

Json array này sẽ tiêu tốn 10 bytes. Vậy liệu còn cách nào để giảm tiếp dung lượng nữa không? Đó là lúc chúng ta cần đến MessagePack.

Ý tưởng ban đầu

Khi phân tích mảng json [300, 100] ra byte array chúng ta sẽ có:

[[0, 0, 1, 44], [0, 0, 0, 100]]

Chúng ta đều nhận thấy rằng có rất nhiều byte 0 ở đây, những byte này không mang lại giá trị gì cả, nên chúng ta có thể bỏ chúng đi và chúng ta sẽ có:

[[1, 44], [100]]

Tuy nhiên việc biểu diễn trên máy tính thực tế sẽ là [1, 44, 100], như vậy đến bước deserialize nó sẽ bị nhầm thành số: 76900, vậy nên chúng ta cần sử dụng tối đa 1 byte để quy định kiểu

Các kiểu dữ liệu

Với JSON, chúng ta chỉ có 6 kiểu dữ liệu: boolean, null, số, chuỗi, object, array, tuy nhiên với 1 byte (255), Messagepack có thể cung cấp cho chúng ta nhiều kiểu hơn thế

format name first byte (in binary) first byte (in hex)
positive fixint 0xxxxxxx 0x00 - 0x7f
fixmap 1000xxxx 0x80 - 0x8f
fixarray 1001xxxx 0x90 - 0x9f
fixstr 101xxxxx 0xa0 - 0xbf
nil 11000000 0xc0
(never used) 11000001 0xc1
false 11000010 0xc2
true 11000011 0xc3
bin 8 11000100 0xc4
bin 16 11000101 0xc5
bin 32 11000110 0xc6
ext 8 11000111 0xc7
ext 16 11001000 0xc8
ext 32 11001001 0xc9
float 32 11001010 0xca
float 64 11001011 0xcb
uint 8 11001100 0xcc
uint 16 11001101 0xcd
uint 32 11001110 0xce
uint 64 11001111 0xcf
int 8 11010000 0xd0
int 16 11010001 0xd1
int 32 11010010 0xd2
int 64 11010011 0xd3
fixext 1 11010100 0xd4
fixext 2 11010101 0xd5
fixext 4 11010110 0xd6
fixext 8 11010111 0xd7
fixext 16 11011000 0xd8
str 8 11011001 0xd9
str 16 11011010 0xda
str 32 11011011 0xdb
array 16 11011100 0xdc
array 32 11011101 0xdd
map 16 11011110 0xde
map 32 11011111 0xdf
negative fixint 111xxxxx 0xe0 - 0xff

Cơ chế hoạt động

Có 2 thứ chúng ta cần quan tâm đó là chuyển dữ liệu sang byte array (serialize), và chuyển ngược byte array thành đối đối tượng (deserialize)

Serialize

Ví dụ json array [300, 100]

  • Bước 1: chuyển về dạng byte array: [[0, 0, 1, 44], [0, 0, 0, 100]]
  • Bước 2: xác định kiểu, trong ví dụ chúng ta có fixarray, uint16 và fixint
  • Bước 3: thêm các byte kiểu và xoá các byte 0

Kết quả chúng ta sẽ nhận được 5 bytes:

[-110, -51, 1, 44, 100]

Dạng hex sẽ là:

[0x92, 0xCD, 0x01, 0x2C, 0x64]

Deserialize

Với mảng byte array kết quả ở trên, chúng ta sẽ thực hiện từng bước:

  1. Đọc byte đầu tiên để xác định kiểu: ví dụ với 0x92 chúng ta biết đây là fixarray
  2. Đọc các byte tiếp theo để xác size của array, object hoặc string: với fixarray thì byte chứa kiểu dữ liệu cũng chứa luôn size (trường hợp này bằng 2) nên chúng ta có thể bỏ qua bước này
  3. Đọc và phân tích dữ liệu dựa theo kiểu đã biết: đọc tiếp chúng ta sẽ byte 0xCD
  4. Lặp lại quá trình này cho đến khi hết toàn bộ mảng array thì thôi

Ví dụ

Ngày nay với sự ra đời của các thư viện thì chúng ta cũng không cần quan tâm nhiều đến cơ chế hoạt động của MessagePack nữa, chỉ cần tạo đối tượng, là vài bước là xong. Và với ezyfox-msgpack mọi chuyện cũng đơn giản như vậy.

Bạn chỉ cần định nghĩa đối tượng:

@EzyArrayBinding
public class Transfer {
    private int userId;
    private int balance;
}

Tạo đối tượng codec:

final EzyBindingContext bindingContext = EzyBindingContext.builder()
            .scan("com.tvd12.ezyfox.example.msgpack")
            .build();
        final EzyEntityCodec codec = EzyBindingEntityCodec.builder()
            .marshaller(bindingContext.newMarshaller())
            .unmarshaller(bindingContext.newUnmarshaller())
            .messageSerializer(new MsgPackSimpleSerializer())
            .messageDeserializer(new MsgPackSimpleDeserializer())
            .build();

Serialize đối tượng qua byte array:

final Transfer transfer = new Transfer(300, 100);
final byte[] serializedBytes = codec.serialize(transfer);

Deserialize byte array thành đối tượng:

final Transfer deserializedObj = codec.deserialize(serializedBytes, Transfer.class);

Ví dụ đầy đủ bạn có thể xem tại đây nhé

Tham khảo

  1. Specs