Principio de implementación del protocolo Protobuf
protobuf es el protocolo de código abierto de Google que admite descripción de datos estructurados multiplataforma y de lenguaje neutro y serialización de alto rendimiento. Este protocolo está completamente basado en binario, por lo que su rendimiento es mucho mayor que JSON/XML. Debido a su excelente rendimiento de transmisión, se usa comúnmente en la comunicación entre microservicios, el más famoso de los cuales es el marco gRPC de código abierto de Google.
Entonces, ¿cómo logra protobuf un alto rendimiento y cómo implementa la codificación y decodificación de datos?
Método de almacenamiento de datos basado en 128 bits (Base 128 Varints)
Varint es un método compacto para representar números. Utiliza uno o más bytes para representar un número, y los números más pequeños utilizan menos bytes. Esto reduce la cantidad de bytes utilizados para representar el número.
Por ejemplo, para números de tipo int32, generalmente se requieren 4 bytes para representarlos. Sin embargo, al usar Varint, un pequeño número de tipo int32 se puede representar con 1 byte. Por supuesto, todo tiene sus lados buenos y malos. Usando la notación Varint, los números grandes requieren 5 bytes para representarse. Desde un punto de vista estadístico, generalmente no todos los números en los mensajes son números grandes, por lo que en la mayoría de los casos, después de usar Varint, se pueden usar menos bytes para representar información digital.
El bit más alto de cada byte en Varint tiene un significado especial si el bit es 1, significa que el byte posterior también es parte del número. Si el bit es 0, termina. Los otros 7 bits se utilizan para representar números. Por tanto, cualquier número menor que 128 se puede representar mediante un byte. Los números mayores a 128, como 300, se representan mediante dos bytes: 1010 1100 0000 0010.
Además, desde la perspectiva del tamaño de los datos, este método de representación tiene un bit más que los datos implementados, por lo que el tamaño de transmisión real es 14 más (1/7 = 0,142857143).
El número 1 está representado por: 0000 0001
Es fácil entender los datos pequeños. En circunstancias normales, el número binario de 1 es 0000 0001. Si está representado por 128. bits, el primer indicador de fin también es 0. , por lo que el resultado de ambos es el mismo 0 000 0001.
Representación del número 300: 1010 1100 0000 0010
lt; figcaptiongt; 300lt; /figcaptiongt; podría representarse con un byte (8 bits), pero debido al uso del método de representación de 128 bits, es necesario agregar un bit de bandera de fin al bit más alto de cada byte para representarlo, por lo que un byte ya no es suficiente. y se necesitan dos palabras, los bits más altos de los dos bytes son indicadores de fin.
Si calculamos hacia adelante, sabemos que el valor binario del número 300 es 1 0010 1100. Usando dos bytes para representar el valor completo es
0000 0001 0010 1100 # Binario
_000 0010 _010 1100 # Mueve el bit más alto de cada byte binario una posición hacia la izquierda y colócalo en el indicador final
0 000 0010 1 010 1100 # Convertir al modo de 128 bits, 1: fin, 0: no finalizado
1 010 1100 0 000 0010 # Convertir al orden de bytes little endian, byte bajo primero, byte alto último
Tenga en cuenta que se agrega primero Fin identificador antes de convertir a little endian.
Después de la serialización, el mensaje se convertirá en un flujo de datos binarios y los datos del flujo serán una serie de pares clave-valor. Como se muestra en la siguiente figura:
El uso de esta estructura de par de claves no requiere el uso de separadores para separar diferentes campos. Para el campo opcional, si el campo no existe en el mensaje, no habrá ningún campo en el búfer de mensajes final. Estas funciones ayudan a guardar el tamaño del mensaje en sí.
La clave se utiliza para identificar un campo específico. Al descomprimir, el cliente crea un objeto de estructura. El búfer de protocolo lee y deserializa los datos del flujo de datos y, según la clave, el campo correspondiente. la estructura debe corresponder al Valor.
La clave también se compone de las siguientes dos partes
La clave se define de la siguiente manera:
| (field_number lt; lt; 3) |
p>
Puedes ver que Key consta de dos partes. La primera parte es número_campo. La segunda parte es tipo_cable. Representa el tipo de transferencia de Valor.
Los 3 bits inferiores de un byte indican el tipo de datos y los otros bits indican el número de campo.
Los posibles tipos de tipo de cable se muestran en la siguiente tabla:
En nuestro ejemplo, el tipo de datos utilizado por el campo id es int32, por lo que el tipo de cable correspondiente es 0. Los lectores atentos pueden ver que entre los tipos de datos que el Tipo 0 puede representar, hay dos tipos de datos muy similares, int32 y sint32. El objetivo principal de Google Protocol Buffer para distinguirlos es reducir la cantidad de bytes después de la codificación.
Cada encabezado de datos también utiliza 128 bits. Generalmente, 1 byte es suficiente.
En este ejemplo, el número de secuencia del campo a es 1.
Creado como. arriba Después de obtener la estructura de Test1 y establecer el valor de a en 2, los datos binarios serializados son
0 000 1000 0 000 0010
La parte clave es 0000 1000
< La parte del valor p> es 0000 0010, donde el bit más alto del byte es el bit de identificación final, que es 2 en decimal. Podemos convertir uniformemente el bit de signo a 0 durante la conversión.El protocolo estipula que los 3 bits inferiores del encabezado de datos representan tipo_cable y los otros campos representan el número de serie del campo número_campo, por lo que
0000 1000
_000 1000 # Eliminar el bit identificador final
_000 1000 # 000 representa el tipo de datos, aquí está Varint
_000 1000 # 0001 Estos cuatro dígitos representan el número de campo
/linux/l-cn-gpb /
Texto original: /archives/20215