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::
Docker — bunun için tüm hizmetler ve araçlar gereklidir.
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.
Golang — hizmetlerden biri golang hizmetidir =)
Python 3+ — Yandex tankı için.
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
– hisse senedini isme göre al GET__ /api/v1/stock
– stoktan tasarruf edin POST /
Kolay bir hizmet olmalı çünkü yük testine odaklanmamız gerekiyor =)
İç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:
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
İ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.
Dokümanlar:
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:
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.
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
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!
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:
Sonraki adımlar: Hizmetleri olabildiğince hızlı oluşturun.
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"` }
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!
Şimdi bunu biraz değiştirmeliyiz:
protokol dosyalarının bulunduğu klasöre taşıyın.
yöntem ekleme: hisse senetleri.StocksService.Save .
basit gövde ekle: '{“stok”: { “isim”:”APPL”, “fiyat”: “1.3”, “açıklama”: “elma stokları”} }'.
20
goroutine çalışanı arasında 10
bağlantı paylaşılacak. Her 2
goroutin çifti tek bir bağlantıyı paylaşacaktır.
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ı
Ö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.
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ı
Bir kez daha teşekkürler ve iyi şanslar! 🍀🕵🏻