paint-brush
Турбо-нагрузочное тестирование: комбинация ЯндексТанк + ghz для молниеносной проверки кода!к@mrdrseq
493 чтения
493 чтения

Турбо-нагрузочное тестирование: комбинация ЯндексТанк + ghz для молниеносной проверки кода!

к Ilia Ivankin14m2023/11/23
Read on Terminal Reader

Слишком долго; Читать

Таким образом, когда вам нужна быстрая оценка способности вашего сервиса обрабатывать нагрузку более 100 запросов в секунду или выявлять потенциальные слабые места, нет необходимости инициировать сложные процессы с участием команд, обращаться за помощью к AQA или полагаться на команду по инфраструктуре. Чаще всего у разработчиков есть мощные ноутбуки и компьютеры, которые могут выполнить небольшой нагрузочный тест. Итак, попробуйте — сэкономьте себе время!
featured image - Турбо-нагрузочное тестирование: комбинация ЯндексТанк + ghz для молниеносной проверки кода!
Ilia Ivankin HackerNoon profile picture

Всем привет!


Иногда возникает необходимость в быстром нагрузочном тестировании, будь то в локальной среде или на тестовой платформе. Обычно такие задачи решаются с использованием специализированных инструментов, требующих тщательного предварительного понимания. Однако на предприятиях и стартапах, где быстрый выход на рынок и быстрая проверка гипотез имеют первостепенное значение, чрезмерное ознакомление с инструментами становится роскошью.


Цель этой статьи — осветить решения, ориентированные на разработчиков, которые устраняют необходимость глубокого взаимодействия и позволяют проводить элементарное тестирование, не углубляясь в страницы документации.


местный бег

Вам следует установить::

  1. Docker — для него необходимы все сервисы и инструменты.


  2. Java 19+ — для сервиса Kotlin. Также вы можете попробовать использовать версию Java 8; это должно работать, но вам придется изменить настройки Gradle.


  3. Golang — один из сервисов — сервис golang =)


  4. Python 3+ — для танка Яндекс.

Технические требования

Прежде чем отправиться в наше путешествие, желательно сгенерировать пару сервисов, которые могут служить наглядными примерами для целей тестирования.


Стек: Котлин + webflux. r2dbc + постгрес.


В нашем сервисе есть:

— получить все акции (лимит 10) ПОЛУЧИТЬ /api/v1/акции
— получить сток по имени GET__ /api/v1/stock ?name=приложение
— сохранить запас POST/
API/v1/сток


Это должен быть простой сервис, потому что нам нужно сосредоточиться на нагрузочном тестировании =)

Котлин и служба HTTP

Начнем с создания небольшого сервиса с базовой логикой внутри. Для этого подготовим модель:


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


Простой маршрутизатор:

 @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) } } } }


и обработчик:

 @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() } }


Полный код здесь: GitHub


Создайте файл докера:

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


Затем создайте образ докера и настройте его 🤤

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


Но на данный момент лучше придерживаться идеи запуска всего через контейнеры Docker и перенести наш сервис в настройку 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 



ОК ок, где тесты?

Двигаясь вперед: как мы можем продолжить тестирование? В частности, как мы можем запустить скромный нагрузочный тест для нашего недавно разработанного сервиса? Крайне важно, чтобы решение для тестирования было простым в установке и удобным для пользователя.


Учитывая наши ограничения по времени, углубляться в обширную документацию и статьи — не лучший вариант. К счастью, есть реальная альтернатива — введите Яндекс Танк. Танк является мощным инструментом для испытаний и имеет важную интеграцию с JMeter , но в статье мы будем использовать его как простой инструмент.


Документы: https://yandextank.readthedocs.org/en/latest/


Давайте начнем с создания папки для наших тестов. Как только мы разместим конфигурации и другие необходимые файлы (к счастью, их всего пара), все будет готово.

Для нашего сервиса нам нужно протестировать методы «получить все» и «сохранить». Первая конфигурация для метода find.

 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


Ключевые параметры конфигурации:

  • Адрес и порт: такие же, как в нашем приложении.


  • Профиль нагрузочного теста (load_profile): мы будем использовать тип «lined» в диапазоне от 100 запросов в секунду до 250 с ограничением в 30 секунд.


  • URI: список URL-адресов, подлежащих тестированию.


  • Шаблон автоостанова: нет необходимости проводить стресс-тестирование, если наш сервис уже вышел из строя! "="


Скопируйте и вставьте скрипт bash (tank sh):

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


И беги!


Что мы увидим в результате? Яндекс Танк во время теста запишет все, что посчитает нужным. Мы можем наблюдать такие показатели, как 99-й процентиль и количество запросов в секунду (RPS).


Терминал? Действительно?


Итак, мы теперь застряли в терминале? Я хочу графический интерфейс! Не волнуйтесь, у Яндекс Танка есть решение и для этой проблемы. Мы можем использовать один из плагинов перегрузки. Вот пример того, как его добавить:

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


Нам следует добавить наш токен; просто зайдите сюда и получите логику от GitHub: https://overload.yandex.net

коды


Хорошо, с запросом GET справиться несложно, но как насчет POST? Как структурировать запрос? Дело в том, что вы не можете просто отправить запрос в резервуар; вам нужно создать для него выкройки! Что это за шаблоны? Все просто — нужно написать небольшой скрипт, который можно снова взять из документации и немного подправить под свои нужды.


И нам нужно добавить собственное тело и заголовки:

 #!/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()


Результат:

 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"}


Вот и все! Просто запустите скрипт, и у нас появится ammo-json.txt. Просто установите новые параметры для конфигурации и удалите URL-адреса:

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


И запустите его еще раз!

Пришло время протестировать GRPC!

еще нет?


Познакомившись с загрузкой HTTP-методов, естественно рассмотреть сценарий для GRPC. Достаточно ли нам повезло иметь столь же доступный инструмент для GRPC, сродни простоте танка? Ответ утвердительный. Позвольте мне познакомить вас с «ghz». Просто взгляните:


Но прежде чем мы это сделаем, нам следует создать небольшой сервис с Go и GRPC в качестве хорошего тестового сервиса.


Подготовьте небольшой файл прототипа:

 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; }


И сгенерируйте его! (также нам нужно установить protoc )

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


Наши результаты:
grpc файлы здесь!

Время кодирования!

Следующие шаги: создавать сервисы как можно быстрее.

нам легко!


  1. Создать dto (стандартный объект для слоя базы данных)

     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. Реализация сервера

     // 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 }


Полный код здесь: нажмите, пожалуйста!

Попробуй это!

  1. Установите GH с помощью Brew (как обычно): ссылка


  2. Давайте проверим простой пример: ссылка


Теперь нам нужно немного изменить это:

  1. перейдите в папку с файлами прото.


  2. добавить метод: stocks.StocksService.Save .


  3. добавьте простое тело: '{"акции": { "имя":"APPL", "цена": "1.3", "описание": "яблочные акции"} }'.


  4. 10 подключений будут разделены между 20 работниками goroutine. Каждая пара из 2 горутин будет использовать одно соединение.


  5. установить порт службы


и результат:

 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


Запустить его!

 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


И что, опять разглядывать всё в терминале? Нет, с помощью ghz тоже можно сформировать отчет, но в отличие от Яндекса он будет сформирован локально и его можно будет открыть в браузере. Просто установите его:

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

-O + html → формат вывода

-о имя файла


результаты в виде веб-страницы :D


Заключение

:D


Таким образом, когда вам нужна быстрая оценка способности вашего сервиса обрабатывать нагрузку более 100 запросов в секунду или выявлять потенциальные слабые места, нет необходимости инициировать сложные процессы с участием команд, обращаться за помощью к AQA или полагаться на команду по инфраструктуре.


Чаще всего у разработчиков есть мощные ноутбуки и компьютеры, которые могут выполнить небольшой нагрузочный тест. Итак, попробуйте — сэкономьте себе время!


Я надеюсь, что эта краткая статья оказалась для вас полезной.

Ценная документация, которую я рекомендую прочитать:

Яндекс Танк: ссылка на документацию

Яндекс Танк GitHub: ссылка на GitHub

Настройка Яндекс Танка: ссылка

Официальная страница ghz: ссылка

Настройка ГГц: ссылка
Конфигурация ghz: ссылка


Да пребудет с тобой сила!

Еще раз спасибо и удачи! 🍀🕵🏻