Tutorials 12 November 2020

Pruebas de rendimiento gRPC usando k6

Simon Aronsson, Developer Advocate

📖Lo que aprenderás

  • ¿Qué es gRPC?
  • En qué se diferencia gRPC de REST basado en JSON
  • Creando y ejecutando la primera prueba de rendimiento gRPC usando k6.

¿Qué es gRPC?

gRPC es un marco de trabajo RPC ligero y de código abierto. Fue desarrollado originalmente por Google, con la versión 1.0 que se lanzó en agosto de 2016. Desde entonces, ha ganado mucha atención así como una amplia adopción.

En comparación con JSON, que se transmite como texto legible por humanos, gRPC es binario, lo que lo hace más rápido de transmitir y más compacto. En los benchmarks que hemos visto entre gRPC y REST basado en JSON gRPC ha demostrado ser mucho más rápido que su contraparte más tradicional.

Un benchmark realizado por Auth0 (https://auth0.com/blog/beating-json-performance-with-protobuf/) informó de un rendimiento hasta 6 veces más rápido, mientras que otras pruebas, como ésta de Alex Pliutau o ésta de Ruwan Fernando, sugieren mejoras de hasta 10 veces.

En el caso de los sistemas distribuidos como chats estas mejoras se acumulan rápidamente, lo que hace que la diferencia no sólo sea perceptible en benchmarks, sino también para el usuario final.

Tipos de API

gRPC soporta cuatro tipos diferentes de RPCs, unary, server streaming, client streaming, y bidireccional. En realidad, los mensajes se multiplexan utilizando la misma conexión, pero pero para mantener las cosas simples y accesibles, esto no se ilustra en los diagramas del modelo de servicio gRPC de servicio gRPC.

Unary

Las llamadas unarias funcionan de la misma manera que una llamada a una función normal: se envía una única petición al servidor que a su vez responde con una única respuesta.

Client Streaming

El modo de streaming del cliente es lo contrario del modo de streaming del servidor. El cliente envía múltiples solicitudes al servidor, que a su vez responde con una única respuesta.

Streaming bidireccional

En el modo de streaming bidireccional, tanto el cliente como el servidor pueden enviar múltiples mensajes.

La definición de .proto

Los mensajes y servicios utilizados por gRPC se describen en archivos .proto, que contienen definiciones de Protocol buffers, o protobuf.

El archivo de definición se utiliza para generar código que puede ser utilizado tanto por los remitentes como por los receptores como un contrato para comunicarse a través de estos mensajes y servicios. Como el formato binario utilizado por gRPC carece de propiedades autodescriptivas, ésta es la única manera de que los emisores y receptores saben cómo interpretar los mensajes.

A lo largo de este artículo, utilizaremos la definición hello.proto disponible para su descarga en el sitio web de K6 grpcbin. Para más detalles sobre cómo construir tu propia definición de grpc proto, vea este excelente artículo de los documentos oficiales de gRPC.

// ./definitions/hello.proto
// based on https://grpc.io/docs/guides/concepts.html
syntax = "proto2";
package hello;
service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse);
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
optional string greeting = 1;
}
message HelloResponse {
required string reply = 1;
}

Comenzando

Con la versión de k6 v0.29.0, estamos contentos de introducir un cliente nativo para la comunicación gRPC. En esta primera versión, nos hemos conformado con proporcionar una experiencia sólida para las llamadas unarias. Si alguno de los otros modos le resulta especialmente útil, nos encantaría conocer su caso de uso para poder adelantarlo.

La API actual para trabajar con gRPC en K6 utilizando el cliente nativo es la siguiente:

MétodoDescripción
Client.load(importPaths, ...protoFiles)Carga y analiza las definiciones de búfer de protocolo dadas para que estén disponibles para las peticiones RPC.
Client.connect(address [,params])Abre una conexión con el servidor gRPC dado.
Client.invoke(url, request [,params])Realiza un RPC unario para el servicio/método dado y devuelve una Respuesta.
Client.close()Cierra la conexión con el servicio gRPC.

Creación de la prueba

El módulo gRPC es un paquete separado, disponible en su script de prueba como k6/net/grpc. Antes de usarlo, tenemos que crear una instancia del cliente. La instanciación del cliente, así como la operación carga, sólo está disponible durante la inicialización de la prueba, es decir, directamente en el ámbito global.

test.js
1import grpc from 'k6/net/grpc';
2
3const client = new grpc.Client();

A continuación, cargaremos una definición .proto aplicable al sistema bajo prueba. Para el propósito de este artículo, usaremos K6 grpcbin. Siéntase libre de cambiar esto por el valor que desee pero tenga en cuenta que también necesitará una definición .proto apropiada para el servidor que estás probando. La función .load() toma dos argumentos, el primero es un array de rutas para buscar archivos proto y el segundo es el nombre del archivo a cargar.

test.js
1import grpc from 'k6/net/grpc';
2
3const client = new grpc.Client();
4client.load(['definitions'], 'hello.proto');

Una vez hecho esto, lo siguiente sería escribir nuestra prueba real.

test.js
1import grpc from 'k6/net/grpc';
2import { check, sleep } from 'k6';
3
4const client = new grpc.Client();
5client.load(['definitions'], 'hello.proto');
6
7export default () => {
8 client.connect('grpcbin.test.k6.io:9001', {
9 // plaintext: false
10 });
11
12 const data = { greeting: 'Bert' };
13 const response = client.invoke('hello.HelloService/SayHello', data);
14
15 check(response, {
16 'status is OK': (r) => r && r.status === grpc.StatusOK,
17 });
18
19 console.log(JSON.stringify(response.message));
20
21 client.close();
22 sleep(1);
23};

Así que vamos a ejecutar este script para asegurarnos de que entendemos lo que está sucediendo. Primero, usamos la función para conectarnos a nuestro sistema bajo prueba. Por defecto, el cliente establecerá plaintext a false, permitiéndole sólo usar conexiones encriptadas. Si, por alguna razón, necesitas conectarte a un servidor que carece de SSL/TLS, basta con cambiar esta configuración a true.

Luego continuamos creando el objeto que queremos enviar al procedimiento remoto que estamos invocando. En caso de SayHello, nos permite especificar a quién debe dirigirse el saludo utilizando el parámetro greeting. .

A continuación, invocamos el procedimiento remoto, utilizando la sintaxis <paquete>.<servicio>/<procedimiento>, como se describe en nuestro archivo proto. Esta llamada se realiza de forma sincrónica, con un tiempo de espera por defecto de 60000 ms (60 segundos). Para cambiar el tiempo de espera, añade la clave timeout al objeto config de .connect() con la duración como valor, por ejemplo '2s' para 2 segundos.

Una vez que hayamos recibido una respuesta del servidor, nos aseguraremos de que el procedimiento se ha ejecutado con éxito. El módulo grpc incluye constantes para esta comparación que se enumeran aquí

La comparación del estado de la respuesta con grpc.StatusOK, que es 200 OK al igual que para la comunicación HTTP/1.1 asegura que la llamada se ha completado con éxito.

Entonces registraremos el mensaje en la respuesta, cerraremos la conexión con el cliente y esperaremos un segundo.

Ejecución de la prueba

La prueba puede ser ejecutada como cualquier otra prueba, aunque debes asegurarte de que estás en al menos versión v0.29.0 para tener acceso al módulo gRPC. Para comprobarlo, ejecute el siguiente comando:

Bash
$ k6 version
k6 v0.29.0 ((devel), go1.15.3, darwin/amd64)

Si estas usando una version menor que v0.29.0, requerirá que primero actualices K6. Las instrucciones sobre cómo hacerlo se pueden encontrar aquí.

Una vez que tengas K6 actualizado, vamos a ejecutar nuestra prueba:

Bash
$ k6 run test.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | () |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: /Users/simme/code/grpc/test.js
output: -
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
* default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
INFO[0000] {"reply":"hello Bert"} source=console
running (00m01.4s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs 00m01.4s/10m0s 1/1 iters, 1 per VU
✓ status is OK
checks...............: 100.00% ✓ 10
data_received........: 3.0 kB 2.1 kB/s
data_sent............: 731 B 522 B/s
grpc_req_duration....: avg=48.44ms min=48.44ms med=48.44ms max=48.44ms p(90)=48.44ms p(95)=48.44ms
iteration_duration...: avg=1.37s min=1.37s med=1.37s max=1.37s p(90)=1.37s p(95)=1.37s
iterations...........: 1 0.714536/s
vus..................: 1 min=1 max=1
vus_max..............: 1 min=1 max=1

A partir de la salida, podemos decir que nuestro script está funcionando y que el servidor efectivamente responde con un saludo dirigido a quien, o a lo que, proporcionamos en el cuerpo de nuestra petición. También podemos ver que nuestro check fue exitoso, lo que significa que el servidor respondió con 200 OK.

Resumen

En este artículo, hemos repasado algunos de los fundamentos de gRPC y su funcionamiento. También hemos echado un vistazo al cliente gRPC introducido en k6 v0.29.0. Por último, pero no menos importante, hemos creado un script de prueba que demuestra esta funcionalidad.

Y con esto concluye este tutorial de pruebas de carga de gRPC. ¡Gracias por leerlo!

Véase también

< Back to all posts