paint-brush
Turbolader-Lasttest: YandexTank + GHz-Kombination für blitzschnelle Codeprüfungen!von@mrdrseq
507 Lesungen
507 Lesungen

Turbolader-Lasttest: YandexTank + GHz-Kombination für blitzschnelle Codeprüfungen!

von Ilia Ivankin14m2023/11/23
Read on Terminal Reader

Zu lang; Lesen

Zusammenfassend lässt sich sagen: Wenn Sie eine schnelle Beurteilung der Fähigkeit Ihres Dienstes benötigen, eine Last von mehr als 100 Anfragen pro Sekunde zu verarbeiten oder potenzielle Schwachstellen zu identifizieren, besteht keine Notwendigkeit, komplizierte Prozesse einzuleiten, an denen Teams beteiligt sind, Unterstützung von AQA in Anspruch zu nehmen oder sich auf das Infrastrukturteam zu verlassen. In den meisten Fällen verfügen Entwickler über leistungsfähige Laptops und Computer, die einen kleinen Lasttest durchführen können. Probieren Sie es einfach aus – sparen Sie Zeit!
featured image - Turbolader-Lasttest: YandexTank + GHz-Kombination für blitzschnelle Codeprüfungen!
Ilia Ivankin HackerNoon profile picture

Hallo!


Gelegentlich besteht Bedarf an schnellen Lasttests, sei es in einer lokalen Umgebung oder auf einer Testplattform. Typischerweise werden solche Aufgaben mit speziellen Werkzeugen gelöst, die ein gründliches Vorverständnis erfordern. In Unternehmen und Start-ups, in denen eine schnelle Markteinführung und eine schnelle Hypothesenvalidierung von größter Bedeutung sind, wird eine übermäßige Einarbeitung in die Tools jedoch zum Luxus.


Ziel dieses Artikels ist es, entwicklerzentrierte Lösungen hervorzuheben, die ein tiefgreifendes Engagement überflüssig machen und rudimentäre Tests ermöglichen, ohne sich in seitenweise Dokumentation zu vertiefen.


lokales Laufen

Sie sollten Folgendes installieren:

  1. Docker – alle Dienste und Tools sind dafür erforderlich.


  2. Java 19+ – für Kotlin-Dienst. Sie können auch versuchen, die Java 8-Version zu verwenden; Es sollte funktionieren, aber Sie müssen die Gradle-Einstellungen ändern.


  3. Golang – einer der Dienste ist der Golang-Dienst =)


  4. Python 3+ – für den Yandex-Panzer.

Technische Anforderungen

Bevor wir uns auf den Weg machen, empfiehlt es sich, einige Dienste zu generieren, die als anschauliche Beispiele für Testzwecke dienen können.


Stapel: Kotlin + Webflux. r2dbc + postgres.


Unser Service umfasst:

– Alle Aktien abrufen (Limit 10) GET /api/v1/stocks
– Bestand nach Namen abrufen GET__ /api/v1/stock ?name=appl
– Lagerbestand speichern POST /
api/v1/stock


Es sollte ein einfacher Service sein, da wir uns auf Lasttests konzentrieren müssen =)

Kotlin und der HTTP-Dienst

Beginnen wir mit der Erstellung eines kleinen Dienstes mit einer grundlegenden Logik darin. Dazu bereiten wir ein Modell vor:


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


Einfacher Router:

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


und Behandler:

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


Vollständiger Code hier: GitHub


Erstellen Sie eine Docker-Datei:

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


Erstellen Sie dann ein Docker-Image und optimieren Sie es 🤤

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


Aber vorerst ist es besser, bei der Idee zu bleiben, alles über Docker-Container laufen zu lassen und unseren Service in ein Docker Compose-Setup zu migrieren.

 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, wo sind die Tests?

Weiter geht es: Wie können wir mit dem Testen fortfahren? Wie können wir konkret einen bescheidenen Auslastungstest für unseren kürzlich entwickelten Dienst einleiten? Es ist wichtig, dass die Testlösung sowohl einfach zu installieren als auch benutzerfreundlich ist.


Angesichts unserer Zeitbeschränkungen ist es keine praktikable Option, sich mit umfangreichen Dokumentationen und Artikeln zu befassen. Glücklicherweise gibt es eine praktikable Alternative: Yandex Tank. Der Tank ist ein leistungsstarkes Instrument zum Testen und verfügt über wichtige Integrationen mit JMeter , aber im Artikel werden wir es als einfaches Werkzeug verwenden.


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


Beginnen wir mit der Erstellung eines Ordners für unsere Tests. Sobald wir die Konfigurationen und andere wichtige Dateien platziert haben – zum Glück nur ein paar davon – sind wir startklar.

Für unseren Service müssen wir die Methoden „Get-All“ und „Save“ testen. Die erste Konfiguration für die Suchmethode.

 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


Wichtige Einstellungen zur Konfiguration:

  • Adresse und Hafen: Wie bei unserer Bewerbung.


  • Lasttestprofil (load_profile): Wir verwenden den Typ „lined“, der von 100 bis 250 Anfragen pro Sekunde reicht und auf 30 Sekunden begrenzt ist.


  • URIs: Eine Liste der zu testenden URLs.


  • Autostop-Muster: Kein Stresstest erforderlich, wenn unser Service bereits ausgefallen ist! =)


Kopieren Sie das Bash-Skript (tank sh) und fügen Sie es ein:

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


Und Renn!


Was werden wir als Ergebnis sehen? Yandex Tank protokolliert während des Tests alles, was es für wertvoll hält. Wir können Metriken wie das 99. Perzentil und Anfragen pro Sekunde (rps) beobachten.


Terminal? Wirklich?


Bleiben wir jetzt also beim Terminal hängen? Ich möchte eine GUI! Keine Sorge, Yandex Tank hat auch dafür eine Lösung. Wir können eines der Überlastungs-Plugins verwenden. Hier ist ein Beispiel für das Hinzufügen:

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


Wir sollten unser Token hinzufügen; Gehen Sie einfach hierher und loggen Sie sich bei GitHub ein: https://overload.yandex.net

Codes


Okay, die Bearbeitung einer GET-Anfrage ist unkompliziert, aber was ist mit POST? Wie strukturieren wir die Anfrage? Die Sache ist, dass man die Anfrage nicht einfach in den Müll werfen kann; Sie müssen Muster dafür erstellen! Was sind diese Muster? Es ist ganz einfach: Sie müssen ein kleines Skript schreiben, das Sie wiederum aus der Dokumentation abrufen und ein wenig an unsere Bedürfnisse anpassen können.


Und wir sollten unseren eigenen Text und unsere eigenen Header hinzufügen:

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


Ergebnis:

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


Das ist es! Führen Sie einfach das Skript aus und wir erhalten ammo-json.txt. Legen Sie einfach neue Parameter für config fest und löschen Sie die URLs:

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


Und lassen Sie es noch einmal laufen!

Es ist Zeit, den GRPC zu testen!

Noch nicht?


Nachdem wir uns mit dem Laden von HTTP-Methoden vertraut gemacht haben, liegt es nahe, das Szenario für GRPC in Betracht zu ziehen. Haben wir das Glück, ein ebenso zugängliches Werkzeug für GRPC zu haben, das der Einfachheit eines Tanks ähnelt? Die Antwort ist bejahend. Erlauben Sie mir, Ihnen „ghz“ vorzustellen. Mal schauen:


Aber bevor wir das tun, sollten wir mit Go und GRPC einen kleinen Dienst als guten Testdienst erstellen.


Bereiten Sie eine kleine Prototypdatei vor:

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


Und generieren Sie es! (Außerdem sollten wir protoc installieren)

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


Unsere Ergebnisse:
grpc-Dateien hier!

Codierungszeit!

Nächste Schritte: Erstellen Sie Dienste so schnell wie möglich.

einfach für uns!


  1. Erstellen Sie dto (Stock-Entität für DB-Layer)

     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 implementieren

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


Vollständiger Code hier: Bitte klicken!

Probier es aus!

  1. GHz mit brew installieren (wie üblich): Link


  2. Sehen wir uns ein einfaches Beispiel an: Link


Jetzt sollten wir es ein wenig ändern:

  1. Wechseln Sie in den Ordner mit den Protodateien.


  2. Methode hinzufügen: stocks.StocksService.Save .


  3. Einfachen Text hinzufügen: '{„stock“: { „name“:“APPL“, „price“: „1.3“, „description“: „apple stocks“} }‘.


  4. 10 Verbindungen werden von 20 Goroutine-Mitarbeitern gemeinsam genutzt. Jedes Paar aus 2 Goroutinen teilt sich eine einzelne Verbindung.


  5. Legen Sie den Port des Dienstes fest


und das Ergebnis:

 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


Starte es!

 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


Und was, nochmal alles im Terminal anstarren? Nein, mit ghz können Sie auch einen Bericht erstellen, aber im Gegensatz zu Yandex wird dieser lokal generiert und kann im Browser geöffnet werden. Stellen Sie es einfach ein:

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

-O + html → Ausgabeformat

-o Dateiname


Ergebnisse als Webseite :D


Abschluss

:D


Zusammenfassend lässt sich sagen: Wenn Sie eine schnelle Einschätzung der Fähigkeit Ihres Dienstes benötigen, eine Last von mehr als 100 Anfragen pro Sekunde zu verarbeiten oder potenzielle Schwachstellen zu identifizieren, besteht keine Notwendigkeit, komplizierte Prozesse einzuleiten, an denen Teams beteiligt sind, Unterstützung von AQA in Anspruch zu nehmen oder sich auf das Infrastrukturteam zu verlassen.


In den meisten Fällen verfügen Entwickler über leistungsfähige Laptops und Computer, die einen kleinen Lasttest durchführen können. Probieren Sie es einfach aus – sparen Sie Zeit!


Ich vertraue darauf, dass Sie diesen kurzen Artikel hilfreich fanden.

Wertvolle Dokumentation, die ich zum Lesen empfehle:

Yandex Tank: Link zu Dokumenten

Yandex Tank GitHub: GitHub-Link

Yandex-Tankeinstellung: Link

Offizielle ghz-Seite: Link

GHz-Einstellung: Link
GHz-Konfiguration: Link


Möge die Macht mit dir sein!

Nochmals vielen Dank und viel Glück! 🍀🕵🏻