paint-brush
Prueba de carga de turbocompresor: combinación YandexTank + ghz para verificaciones de códigos ultrarrápidas.por@mrdrseq
503 lecturas
503 lecturas

Prueba de carga de turbocompresor: combinación YandexTank + ghz para verificaciones de códigos ultrarrápidas.

por Ilia Ivankin14m2023/11/23
Read on Terminal Reader

Demasiado Largo; Para Leer

En resumen, cuando necesita una evaluación rápida de la capacidad de su servicio para manejar una carga de más de 100 solicitudes por segundo o identificar posibles debilidades, no es necesario iniciar procesos complejos que involucren equipos, buscar asistencia de AQA o depender del equipo de infraestructura. La mayoría de las veces, los desarrolladores tienen portátiles y ordenadores capaces de ejecutar una pequeña prueba de carga. Entonces, adelante, inténtalo: ¡ahorra algo de tiempo!
featured image - Prueba de carga de turbocompresor: combinación YandexTank + ghz para verificaciones de códigos ultrarrápidas.
Ilia Ivankin HackerNoon profile picture

¡Hola!


En ocasiones, surge la necesidad de realizar pruebas de carga rápidas, ya sea en un entorno local o en una plataforma de prueba. Normalmente, estas tareas se abordan utilizando herramientas especializadas que exigen una comprensión previa exhaustiva. Sin embargo, dentro de las empresas y las nuevas empresas, donde el tiempo de comercialización rápido y la rápida validación de hipótesis son primordiales, la familiarización excesiva con las herramientas se convierte en un lujo.


Este artículo tiene como objetivo destacar soluciones centradas en el desarrollador que obvian la necesidad de un compromiso profundo, permitiendo pruebas rudimentarias sin profundizar en páginas de documentación.


carrera local

Deberías instalar::

  1. Docker: para ello se requieren todos los servicios y herramientas.


  2. Java 19+: para el servicio Kotlin. Además, puedes intentar utilizar la versión Java 8; Debería funcionar, pero debes cambiar la configuración de Gradle.


  3. Golang — uno de los servicios es el servicio golang =)


  4. Python 3+ — para el tanque Yandex.

Requisitos técnicos

Antes de emprender nuestro viaje, es recomendable generar un par de servicios que puedan servir como ejemplos ilustrativos a efectos de prueba.


Pila: Kotlin + webflux. r2dbc + postgres.


Nuestro servicio cuenta con:

– obtener todas las acciones (límite 10) OBTENER /api/v1/acciones
– obtener acciones por nombre GET__ /api/v1/stock ?nombre=aplicación
– guardar stock POST /
api/v1/valores


Debería ser un servicio sencillo porque tenemos que centrarnos en las pruebas de carga =)

Kotlin y el servicio HTTP

Comencemos creando un pequeño servicio con cierta lógica básica interna. Prepararemos un modelo para este propósito:


 @Table("stocks") data class Stock( @field:Id val id: Long?, val name: String, val price: BigDecimal, val description: String )


Enrutador sencillo:

 @Configuration @EnableConfigurationProperties(ServerProperties::class) class StockRouter( private val properties: ServerProperties, private val stockHandler: StockHandler ) { @Bean fun router() = coRouter { with(properties) { main.nest { contentType(APPLICATION_JSON).nest { POST(save, stockHandler::save) } GET(find, stockHandler::find) GET(findAll, stockHandler::findAll) } } } }


y manejador:

 @Service class StockHandlerImpl( private val stockService: StockService ) : StockHandler { private val logger = KotlinLogging.logger {} private companion object { const val DEFAULT_SIZE = 10 const val NAME_PARAM = "name" } override suspend fun findAll(req: ServerRequest): ServerResponse { logger.debug { "Processing find all request: $req" } val stocks = stockService.getAll(DEFAULT_SIZE) return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(stocks, StockDto::class.java) .awaitSingle() } override suspend fun find(req: ServerRequest): ServerResponse { logger.debug { "Processing find all request: $req" } val name = req.queryParam(NAME_PARAM) return if (name.isEmpty) { ServerResponse.badRequest().buildAndAwait() } else { val stocks = stockService.find(name.get()) ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(stocks, StockDto::class.java) .awaitSingle() } } override suspend fun save(req: ServerRequest): ServerResponse { logger.debug { "Processing save request: $req" } val stockDto = req.awaitBodyOrNull(StockDto::class) return stockDto?.let { dto -> stockService.save(dto) ServerResponse .ok() .contentType(MediaType.APPLICATION_JSON) .bodyValue(dto) .awaitSingle() } ?: ServerResponse.badRequest().buildAndAwait() } }


Código completo aquí: GitHub


Cree un archivo acoplable:

 FROM openjdk:20-jdk-slim VOLUME /tmp COPY build/libs/*.jar app.jar ENTRYPOINT ["java", "-Dspring.profiles.active=stg", "-jar", "/app.jar"]


Luego, crea una imagen acoplable y ajústala 🤤

 docker build -t ere/stock-service . docker run -p 8085:8085 ere/stock-service


Pero por ahora, es mejor seguir con la idea de ejecutar todo a través de contenedores Docker y migrar nuestro servicio a una configuración de Docker Compose.

 version: '3.1' services: db: image: postgres container_name: postgres-stocks ports: - "5432:5432" environment: POSTGRES_PASSWORD: postgres adminer: image: adminer ports: - "8080:8080" stock-service: image: ere/stock-service container_name: stock-service ports: - "8085:8085" depends_on: - db 



Ok ok, ¿dónde están las pruebas?

En el futuro: ¿cómo podemos proceder con las pruebas? Específicamente, ¿cómo podemos iniciar una prueba de carga modesta para nuestro servicio recientemente desarrollado? Es imperativo que la solución de prueba sea sencilla de instalar y fácil de usar.


Dadas nuestras limitaciones de tiempo, profundizar en documentación y artículos extensos no es una opción viable. Afortunadamente, existe una alternativa viable: ingresar a Yandex Tank. El tanque es un poderoso instrumento para pruebas y tiene integraciones importantes con JMetro , pero en el artículo lo usaremos como una herramienta sencilla.


Documentos: https://yandextank.readthedocs.org/en/latest/


Comencemos creando una carpeta para nuestras pruebas. Una vez que hayamos colocado las configuraciones y otros archivos esenciales (afortunadamente, solo un par de ellos), estaremos listos.

Para nuestro servicio, necesitamos probar los métodos "obtener todo" y "guardar". La primera configuración para el método de búsqueda.

 phantom: address: localhost port: "8085" load_profile: load_type: rps schedule: line(100, 250, 30s) writelog: all ssl: false connection_test: true uris: - /api/v1/stocks overload: enabled: false telegraf: enabled: false autostop: autostop: - time(1s,10s) # if request average > 1s - http(5xx,100%,1s) # if 500 errors > 1s - http(4xx,25%,10s) # if 400 > 25% - net(xx,25,10) # if amount of non-zero net-codes in every second of last 10s period is more than 25


Configuraciones clave para la configuración:

  • Dirección y puerto: Igual que nuestra aplicación.


  • Cargar perfil de prueba (load_profile): usaremos el tipo 'lineado', que va desde 100 solicitudes por segundo hasta 250 con un límite de 30 segundos.


  • URI: una lista de URL que se van a probar.


  • Patrón de parada automática: ¡No es necesario realizar una prueba de esfuerzo si nuestro servicio ya se ha caído! =)


Copie y pegue el script bash (tank sh):

 docker run \ -v $(pwd):/var/loadtest \ --net="host" \ -it yandex/yandex-tank


¡Y correr!


¿Qué veremos como resultado? Yandex Tank registrará todo lo que considere valioso durante la prueba. Podemos observar métricas como el percentil 99 y solicitudes por segundo (rps).


¿Terminal? ¿en realidad?


Entonces, ¿estamos atrapados con la terminal ahora? ¡Quiero una GUI! No te preocupes, Yandex Tank también tiene una solución para eso. Podemos utilizar uno de los complementos de sobrecarga. A continuación se muestra un ejemplo de cómo agregarlo:

 overload: enabled: true package: yandextank.plugins.DataUploader job_name: "save docs" token_file: "env/token.txt"


Deberíamos agregar nuestro token; solo ve aquí y lógica por GitHub: https://overload.yandex.net

códigos


Bien, manejar una solicitud GET es sencillo, pero ¿qué pasa con POST? ¿Cómo estructuramos la solicitud? La cuestión es que no se puede simplemente tirar la solicitud al tanque; ¡Necesitas crear patrones para ello! ¿Cuáles son estos patrones? Es simple: necesita escribir un pequeño script, que puede recuperar nuevamente de la documentación y modificarlo un poco para adaptarlo a nuestras necesidades.


Y deberíamos agregar nuestro propio cuerpo y encabezados:

 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import json # http request with entity body template req_template_w_entity_body = ( "%s %s HTTP/1.1\r\n" "%s\r\n" "Content-Length: %d\r\n" "\r\n" "%s\r\n" ) # phantom ammo template ammo_template = ( "%d %s\n" "%s" ) method = "POST" case = "" headers = "Host: test.com\r\n" + \ "User-Agent: tank\r\n" + \ "Accept: */*\r\n" + \ "Connection: Close\r\n" def make_ammo(method, url, headers, case, body): """ makes phantom ammo """ req = req_template_w_entity_body % (method, url, headers, len(body), body) return ammo_template % (len(req), case, req) def generate_json(): body = { "name": "content", "price": 1, "description": "description" } url = "/api/v1/stock" h = headers + "Content-type: application/json" s1 = json.dumps(body) ammo = make_ammo(method, url, h, case, s1) sys.stdout.write(ammo) f2 = open("ammo/ammo-json.txt", "w") f2.write(ammo) if __name__ == "__main__": generate_json()


Resultado:

 212 POST /api/v1/stock HTTP/1.1 Host: test.com User-Agent: tank Accept: */* Connection: Close Content-type: application/json Content-Length: 61 {"name": "content", "price": 1, "description": "description"}


¡Eso es todo! Simplemente ejecute el script y tendremos ammo-json.txt. Simplemente establezca nuevos parámetros para configurar y elimine las URL:

 phantom: address: localhost:9001 ammo_type: phantom ammofile: ammo-json.txt


¡Y ejecútalo una vez más!

¡Es hora de probar el GRPC!

¿aún no?


Después de habernos familiarizado con la carga de métodos HTTP, es natural considerar el escenario de GRPC. ¿Somos lo suficientemente afortunados de tener una herramienta igualmente accesible para GRPC, similar a la simplicidad de un tanque? La respuesta es afirmativa. Permítanme presentarles 'ghz'. Solo echa un vistazo:


Pero antes de hacer eso, deberíamos crear un pequeño servicio con Go y GRPC como un buen servicio de prueba.


Prepare un pequeño archivo proto:

 syntax = "proto3"; option go_package = "stock-grpc-service/stocks"; package stocks; service StocksService { rpc Save(SaveRequest) returns (SaveResponse) {} rpc Find(FindRequest) returns (FindResponse) {} } message SaveRequest { Stock stock = 1; } message SaveResponse { string code = 1; } message Stock { string name = 1; float price = 2; string description = 3; } message FindRequest { enum Type { INVALID = 0; BY_NAME = 1; } message ByName { string name = 1; } Type type = 1; oneof body { ByName by_name = 2; } } message FindResponse { Stock stock = 1; }


¡Y generarlo! (Además, deberíamos instalar el protocolo )

 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative stocks.proto


Nuestros resultados:
archivos grpc aquí!

¡Hora de codificar!

Próximos pasos: crear servicios lo más rápido que podamos.

fácil para nosotros!


  1. Crear dto (entidad de stock para la capa de base de datos)

     package models // Stock – base dto type Stock struct { ID *int64 `json:"Id"` Price float32 `json:"Price"` Name string `json:"Name"` Description string `json:"Description"` }


  2. Implementar servidor

     // Server is used to implement stocks.UnimplementedStocksServiceServer. type Server struct { pb.UnimplementedStocksServiceServer stockUC stock.UseCase } // NewStockGRPCService stock gRPC service constructor func NewStockGRPCService(emailUC stock.UseCase) *Server { return &Server{stockUC: emailUC} } func (e *Server) Save(ctx context.Context, request *stocks.SaveRequest) (*stocks.SaveResponse, error) { model := request.Stock stockDto := &models.Stock{ ID: nil, Price: model.Price, Name: model.Name, Description: model.Description, } err := e.stockUC.Create(ctx, stockDto) if err != nil { return nil, err } return &stocks.SaveResponse{Code: "ok"}, nil } func (e *Server) Find(ctx context.Context, request *stocks.FindRequest) (*stocks.FindResponse, error) { code := request.GetByName().GetName() model, err := e.stockUC.GetByID(ctx, code) if err != nil { return nil, err } response := &stocks.FindResponse{Stock: &stocks.Stock{ Name: model.Name, Price: model.Price, Description: model.Description, }} return response, nil }


Código completo aquí: ¡haz clic, por favor!

¡Pruébalo!

  1. Instale GHz con cerveza (como de costumbre): enlace


  2. Veamos un ejemplo simple: enlace


Ahora deberíamos cambiarlo un poco:

  1. vaya a la carpeta con los archivos proto.


  2. agregar método: stocks.StocksService.Save .


  3. agregue un cuerpo simple: '{“stock”: { “name”:”APPL”, “price”: “1.3”, “description”: “apple stocks”} }'.


  4. Se compartirán 10 conexiones entre 20 trabajadores de gorutina. Cada par de 2 gorutinas compartirá una única conexión.


  5. establecer el puerto del servicio


y el resultado:

 cd .. && cd stock-grpc-service/proto ghz --insecure \ --proto ./stocks.proto \ --call stocks.StocksService.Save \ -d '{"stock": { "name":"APPL", "price": "1.3", "description": "apple stocks"} }' \ -n 2000 \ -c 20 \ --connections=10 \ 0.0.0.0:5007


¡Ejecutarlo!

 Summary: Count: 2000 Total: 995.93 ms Slowest: 30.27 ms Fastest: 3.11 ms Average: 9.19 ms Requests/sec: 2008.16 Response time histogram: 3.111 [1] | 5.827 [229] |∎∎∎∎∎∎∎∎∎∎∎ 8.542 [840] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 11.258 [548] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 13.973 [190] |∎∎∎∎∎∎∎∎∎ 16.689 [93] |∎∎∎∎ 19.405 [33] |∎∎ 22.120 [29] |∎ 24.836 [26] |∎ 27.551 [6] | 30.267 [5] | Latency distribution: 10 % in 5.68 ms 25 % in 6.67 ms 50 % in 8.27 ms 75 % in 10.49 ms 90 % in 13.88 ms 95 % in 16.64 ms 99 % in 24.54 ms Status code distribution: [OK] 2000 responses


¿Y qué, volver a mirar todo lo que hay en la terminal? No, con ghz también puedes generar un informe, pero a diferencia de Yandex, se generará localmente y se podrá abrir en el navegador. Simplemente configúrelo:

 ghz --insecure -O html -o reports_find.html \ ...

-O + html → formato de salida

-o nombre de archivo


resultados como página web :D


Conclusión

:D


En resumen, cuando necesita una evaluación rápida de la capacidad de su servicio para manejar una carga de más de 100 solicitudes por segundo o identificar posibles debilidades, no es necesario iniciar procesos complejos que involucren equipos, buscar asistencia de AQA o depender del equipo de infraestructura.


La mayoría de las veces, los desarrolladores tienen portátiles y ordenadores capaces de ejecutar una pequeña prueba de carga. Entonces, adelante, inténtalo: ¡ahorra algo de tiempo!


Espero que este breve artículo le resulte beneficioso.

Documentación valiosa que recomiendo leer:

Tanque Yandex: enlace de documentos

Yandex Tank GitHub: enlace de GitHub

Configuración del tanque Yandex: enlace

página oficial de ghz: enlace

configuración de ghz: enlace
configuración de ghz: enlace


¡Que la fuerza esté con usted!

¡Gracias una vez más y mucha suerte! 🍀🕵🏻