paint-brush
Turboşarj Yük Testi: Hızlı Kod Kontrolleri için YandexTank + ghz Kombinasyonu!ile@lookingforere
418 okumalar
418 okumalar

Turboşarj Yük Testi: Hızlı Kod Kontrolleri için YandexTank + ghz Kombinasyonu!

ile Ilia 14m2023/11/23
Read on Terminal Reader

Çok uzun; Okumak

Özetle, hizmetinizin saniyede 100'den fazla isteği karşılama veya potansiyel zayıflıkları belirleme becerisinin hızlı bir şekilde değerlendirilmesine ihtiyaç duyduğunuzda, ekipleri içeren karmaşık süreçler başlatmanıza, AQA'dan yardım istemenize veya altyapı ekibine güvenmenize gerek yoktur. Geliştiricilerin çoğu zaman küçük bir yük testi gerçekleştirebilecek kapasiteye sahip dizüstü bilgisayarları ve bilgisayarları vardır. Öyleyse devam edin ve bir şans verin; kendinize biraz zaman kazandırın!
featured image - Turboşarj Yük Testi: Hızlı Kod Kontrolleri için YandexTank + ghz Kombinasyonu!
Ilia  HackerNoon profile picture

Merhaba!


Bazen ister yerel bir ortamda ister bir test platformunda olsun, hızlı yük testine ihtiyaç duyulur. Tipik olarak bu tür görevler, önceden kapsamlı bir anlayış gerektiren özel araçlar kullanılarak ele alınır. Bununla birlikte, hızlı pazara sunma süresinin ve hızlı hipotez doğrulamanın çok önemli olduğu işletmelerde ve yeni kurulan şirketlerde, aşırı araç alıştırması bir lüks haline gelir.


Bu makale, kapsamlı katılım gerekliliğini ortadan kaldıran, sayfalarca belgeye dalmadan temel testlere olanak tanıyan geliştirici merkezli çözümlere ışık tutmayı amaçlamaktadır.


yerel koşu

Şunları yüklemelisiniz::

  1. Docker — bunun için tüm hizmetler ve araçlar gereklidir.


  2. Java 19+ — kotlin hizmeti için. Ayrıca Java 8 sürümünü kullanmayı deneyebilirsiniz; çalışması gerekir, ancak Gradle ayarlarını değiştirmeniz gerekir.


  3. Golang — hizmetlerden biri golang hizmetidir =)


  4. Python 3+ — Yandex tankı için.

Teknik Gereksinimler

Yolculuğumuza başlamadan önce, test amacıyla açıklayıcı örnekler olarak hizmet edebilecek birkaç hizmet oluşturmanız tavsiye edilir.


Yığın: Kotlin + web akışı. r2dbc + postgres.


Hizmetimiz şunları içerir:

– tüm hisse senetlerini al (sınır 10) AL /api/v1/hisse senetleri
– hisse senedini isme göre al GET__ /api/v1/stock ?isim=uygulama
– stoktan tasarruf edin POST /
API/v1/stok


Kolay bir hizmet olmalı çünkü yük testine odaklanmamız gerekiyor =)

Kotlin ve HTTP Hizmeti

İçinde bazı temel mantık bulunan küçük bir hizmet oluşturarak başlayalım. Bu amaçla bir model hazırlayacağız:


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


Basit yönlendirici:

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


ve işleyici:

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


Tam kod burada: GitHub


Bir liman işçisi dosyası oluşturun:

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


Ardından bir liman işçisi görüntüsü oluşturun ve ayarlayın 🤤

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


Ancak şimdilik her şeyi Docker konteynerleri üzerinden çalıştırma fikrine bağlı kalmak ve hizmetimizi Docker Compose kurulumuna taşımak daha iyi.

 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 



Tamam tamam, testler nerede?

İlerlemek: Teste nasıl devam edebiliriz? Özellikle yakın zamanda geliştirdiğimiz hizmet için mütevazı bir yük testini nasıl başlatabiliriz? Test çözümünün hem kurulumu kolay hem de kullanıcı dostu olması zorunludur.


Zaman kısıtlamalarımız göz önüne alındığında, kapsamlı belgelere ve makalelere dalmak uygun bir seçenek değil. Neyse ki geçerli bir alternatif var; Yandex Tank'a girin. Tank, test için güçlü bir araçtır ve önemli entegrasyonlara sahiptir. JMeter , ancak makalede bunu basit bir araç olarak kullanacağız.


Dokümanlar: https://yandextank.readthedocs.org/en/latest/


Testlerimiz için bir klasör oluşturarak başlayalım. Yapılandırmaları ve diğer önemli dosyaları (neyse ki bunlardan sadece birkaçını) yerleştirdikten sonra her şey hazır olacak.

Hizmetimiz için "hepsini al" ve "kaydet" yöntemlerini test etmemiz gerekir. Find yöntemi için ilk yapılandırma.

 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


Yapılandırma için temel ayarlar:

  • Adres ve port: Uygulamamızla aynı.


  • Test profilini yükleyin (load_profile): Saniyede 100 istekten 30 saniye sınırıyla 250'ye kadar değişen 'çizgili' türü kullanacağız.


  • URI'ler: Test edilecek URL'lerin listesi.


  • Otomatik durdurma modeli: Hizmetimiz zaten çalışmıyorsa stres testine gerek yok! =)


Bash betiğini (tank sh) kopyalayıp yapıştırın:

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


Ve koş!


Sonuç olarak ne göreceğiz? Yandex Tank, test sırasında değerli gördüğü her şeyi günlüğe kaydedecektir. 99. yüzdelik dilim ve saniyedeki istek sayısı (rps) gibi metrikleri gözlemleyebiliyoruz.


terminal? Gerçekten mi?


Peki şimdi terminale mi kaldık? Bir GUI istiyorum! Merak etmeyin, Yandex Tank'ın bunun için de bir çözümü var. Aşırı yükleme eklentilerinden birini kullanabiliriz. İşte nasıl ekleneceğine dair bir örnek:

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


Jetonumuzu eklemeliyiz; sadece buraya gidin ve GitHub ile mantık yürütün: https://overload.yandex.net

kodlar


Tamam, bir GET isteğiyle uğraşmak çok basit, peki ya POST? Talebi nasıl yapılandırırız? Sorun şu ki, isteği öylece depoya atamazsınız; bunun için desenler yaratmanız gerekiyor! Bu kalıplar nelerdir? Çok basit; küçük bir komut dosyası yazmanız gerekiyor; bunu yine belgelerden alabilir ve ihtiyaçlarımıza uyacak şekilde biraz değiştirebilirsiniz.


Ve kendi gövdemizi ve başlıklarımızı eklemeliyiz:

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


Sonuç:

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


Bu kadar! Sadece betiği çalıştırın, ammo-json.txt dosyasına sahip olacağız. Sadece config'e yeni parametreler ayarlayın ve URL'leri silin:

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


Ve bir kez daha çalıştır!

GRPC'yi Test Etme Zamanı!

Henüz değil?


HTTP yöntemlerini yükleme konusunda bilgi sahibi olduğumuz için GRPC senaryosunu dikkate almak doğaldır. GRPC için tankın basitliğine benzer, eşit derecede erişilebilir bir araca sahip olacak kadar şanslı mıyız? Cevap olumlu. Sizi 'ghz' ile tanıştırmama izin verin. Sadece bir göz atın:


Ancak bunu yapmadan önce iyi bir test servisi olarak Go ve GRPC ile küçük bir servis oluşturmalıyız.


Küçük bir protokol dosyası hazırlayın:

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


Ve onu üretin! (ayrıca protokolü yüklememiz gerekir)

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


Sonuçlarımız:
grpc dosyaları burada!

Kodlama Zamanı!

Sonraki adımlar: Hizmetleri olabildiğince hızlı oluşturun.

bizim için kolay!


  1. Dto oluştur (db katmanı için stok varlığı)

     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. Sunucuyu uygula

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


Kodun tamamı burada: tıklayın, lütfen!

Dene!

  1. GHz'i demleme ile yükleyin (her zamanki gibi): bağlantı


  2. Basit bir örneği kontrol edelim: bağlantı


Şimdi bunu biraz değiştirmeliyiz:

  1. protokol dosyalarının bulunduğu klasöre taşıyın.


  2. yöntem ekleme: hisse senetleri.StocksService.Save .


  3. basit gövde ekle: '{“stok”: { “isim”:”APPL”, “fiyat”: “1.3”, “açıklama”: “elma stokları”} }'.


  4. 20 goroutine çalışanı arasında 10 bağlantı paylaşılacak. Her 2 goroutin çifti tek bir bağlantıyı paylaşacaktır.


  5. hizmetin bağlantı noktasını ayarla


ve sonuç:

 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


Çalıştır!

 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


Peki yine terminaldeki her şeye mi bakacaksın? Hayır, ghz ile rapor da oluşturabilirsiniz ancak Yandex'den farklı olarak yerel olarak oluşturulacak ve tarayıcıda açılabilecektir. Sadece ayarlayın:

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

-O + html → çıktı formatı

-o dosya adı


sonuçlar web sayfası olarak :D


Çözüm

:D


Özetle, hizmetinizin saniyede 100'den fazla isteği karşılama veya olası zayıflıkları belirleme becerisinin hızlı bir şekilde değerlendirilmesine ihtiyaç duyduğunuzda, ekipleri içeren karmaşık süreçleri başlatmanıza, AQA'dan yardım istemenize veya altyapı ekibine güvenmenize gerek yoktur.


Geliştiricilerin çoğu zaman küçük bir yük testi gerçekleştirebilecek kapasiteye sahip dizüstü bilgisayarları ve bilgisayarları vardır. Öyleyse devam edin ve bir şans verin; kendinize biraz zaman kazandırın!


Bu kısa makaleyi faydalı bulacağınıza inanıyorum.

Okumanızı Tavsiye Ettiğim Değerli Belgeler:

Yandex Tankı: dokümanlar bağlantısı

Yandex Tankı GitHub: GitHub bağlantısı

Yandex Tank Ayarı: bağlantı

ghz resmi sayfası: bağlantı

GHz ayarı: bağlantı
ghz yapılandırması: bağlantı


Güç seninle olsun!

Bir kez daha teşekkürler ve iyi şanslar! 🍀🕵🏻