استفاده از Goroutines در تست دود (smoke) سرور

golang11 فروردین 1399
Goroutines

سلام به همه ی دوستان Golang کارعزیز. در این مطلب قلاب قراره در مورد نقش Goroutines در تست دود نرم افزار واستون بگه. پس تا آخر این مطلب با ما همراه باشین!

همه ی ما دوست داریم که سرورهامون و تاخیرهای ناشی از افزایش مقیاس رو تست کنیم. روش های مختلفی برای انجام دادن این کار وجود داره، یکیش اینه که از postman برای ارسال چندین درخواست استفاده کنیم. اما موضوع مهم اینه که چطور درخواست های هم زمان بفرستیم؟ فرض کنین بخوایم یک میلیون درخواست با سطوح هم زمانی مختلف رو به صورت هم زمان ارسال کنیم. اینجاست که پای قدرت Goroutines به میان می آد!

نگاهی کلی به Goroutines

برای درک بهتر این موضوع میخوام از microlibrary ساده ای با نام concurrent-HTTP استفاده کنم.

اول یه HTTP webserver تستی (mock) برای لاگ کردن درخواست ها ایجاد میکنم. این همون وب سروری هست که قراره درخواست ها رو بهش بفرستیم.

package main

import (
	"net/http"
	"time"
)

var count int64

func handle(w http.ResponseWriter, r *http.Request) {
	time.Sleep(500 * time.Millisecond)
	w.WriteHeader(200)
	r.Body.Close()
	count++
}

func main() {
	http.HandleFunc("/", handle)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		panic(err)
	}
}

خب به جاهای خوبش رسیدیم! حالا شروع می کنیم درخواست های HTTP با سطوح هم زمانی مختلف رو با استفاده از کتابخانه concurrent-http ارسال می کنیم. توجه داشته باشین که این کتابخانه، اینطور عمل نمیکنه که به سادگی هر درخواست رو به یک نخ (thread) اختصاص بده. بلکه هر نخ یک درخواست رو از صف برمیداره و اطمینان حاصل میکنه که بار به صورت تقریبا مساوی تقسیم شده باشه. منطق اصلی کار، توی قطعه کدی که در ادامه اومده قرار داره.

دقت کنین که به منظور کسب اطمینان از اینکه همه ی Goroutines کامل بشه و تمام درخواست ها ارسال بشن، از یک Waitgroup استفاده شده.

هم چنین از Mutex برای دسترسی، به روشی امن، به متغیر شمارنده در یک نخ استفاده شده.

package main

import (
	"concurrent"
	"fmt"
	"net/http"
	"time"
)

func main() {
	url := "http://localhost:8080/"
	httpRequest, _ := http.NewRequest("GET", url, nil)

	// Parallelism of the request
	concurrency := 1000

	// Total number of requests to be made.
	numberOfRequests := int64(10000)
	concurrentRequest := concurrent.NewRequest(httpRequest, numberOfRequests, concurrency)

	startTime := time.Now()
	go func() {
		concurrentRequest.MakeSync()
		completetionTime := time.Now().Sub(startTime)
		fmt.Printf("%v time required to complete all requests", completetionTime)
	}()

	tick := time.NewTicker(500 * time.Millisecond)
	for range tick.C {
		status := concurrentRequest.Status()
		timeElapsed := time.Now().Sub(startTime)
		fmt.Printf("%f% requests sent, Time elapsed: %v", status, timeElapsed)
	}
}

حالا شروع می کنیم به ارسال درخواست ها و یک ticker رو به منظور شمردن تعداد درخواست های کامل شده در هر ثانیه، استارت می زنیم. آنچه در ادامه می بینین نتیجه ای هست که به دست آوردیم:

  1. ارسال 10 درخواست با هم زمانی سطح 1. این یعنی تمام درخواست ها به صورت سریالی انجام می شن. مشاهده می کنین که برای ارسال 10 درخواست تقریبا 5 ثانیه زمان نیاز هست. با یک نسبت بندی ساده متوجه می شیم که ارسال 1000 درخواست به صورت سریالی تقریبا 8.5 دقیقه طول خواهد کشید.
نتایج به دست آمده در ارسال سریالی درخواست ها

2. اما قرار نیست به صورت سریالی درخواست ها رو ارسال کنیم بلکه می خوایم از قدرت هم زمانی استفاده کنیم. اون 1000 تا درخواستی که گفتم رو با هم زمانی 500 ارسال می کنیم. مشاهده می کنیم که زمان مورد نیازاز 8.5 دقیقه (در ارسال سریالی) به 2.3 ثانیه (ارسال همزمانی) کاهش پیدا می کنه!

نتایج به دست آمده در ارسال هم زمان درخواست ها

3.حالا یه پله میریم بالاتر. 200000 تا درخواست رو با هم زمانی 100000، ارسال می کنیم. چیزی که می بینیم اینه که مقیاس به صورت خطی رشد نمیکنه و سرور کمی کند میشه. ارسال درخواست های بیشتر، نیازمند کلاینت های بیشتر و سرور کارامدتری هست.

ارسال 200000 درخواست با هم زمانی 100000

نتیجه گیری

در این مقاله قدرت Goroutines و کاربرد اون رودر تست دود سرور HTTP دیدیم. هم چنین متوجه شدیم که افزایش هم زمانی از جایی به بعد منجر به کاهش بازدهی میشه!

ممنون که این مقاله رو هم با قلاب همراه بودین. امیدوارم واستون مفید بوده باشه.

Please Post Your Comments & Reviews

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*