ストレステストとは何ですか?
- 負荷テストは、さまざまなシナリオで実際のユーザーの同時実行性、アクセス、または負荷をシミュレートすることによって、テスト対象システムのパフォーマンス、安定性、信頼性をテストするテスト方法です。ストレス テストは、システムの耐荷重を理解し、システム パフォーマンスのボトルネックと潜在的な問題を特定し、システムの最適化のためのデータ サポートを提供するのに役立ちます。
- 圧力測定の意味は次のとおりです。
- システムのパフォーマンスの向上: ストレス テストを通じて、システムのパフォーマンスのボトルネックを特定し、システム アーキテクチャ、コード、データベースを最適化し、システムのパフォーマンスと応答速度を向上させることができます。
- システムの安定性の保証: 圧力テストは、大量の同時リクエストをシミュレートし、システム容量の制限とピーク値を検出し、過剰な圧力によって引き起こされるシステムのダウンタイムやクラッシュなどの問題を回避できます。
- システムの信頼性の確保: ストレス テストでは、さまざまなシナリオでのユーザーの動作をシミュレートし、さまざまな負荷の下でシステムのパフォーマンスをテストして、システムの信頼性と安定性を確保し、ユーザー エクスペリエンスを向上させることができます。
- コストの無駄を削減: ストレス テストを通じてシステムの問題を発見できるため、実稼働環境での深刻な問題が回避され、障害によるコストの無駄が削減されます。
- つまり、ストレス テストは、システムのパフォーマンスと安定性を理解し、ユーザー エクスペリエンスと満足度を向上させ、故障率とコストを削減するのに役立つ非常に重要なテスト方法です。
必要
ファイル内には複数のインターフェイスがあり、これらのインターフェイスに対して圧力テストを実行する必要があります。同時実行数や圧力測定時間を柔軟に指定する必要がある
シーンシミュレーション
- 同時実行数を 100 に指定し、圧力テスト時間を 10 分に指定するとします。
- 100 人がそれぞれインターフェイスを要求し、10 分以内に継続的に要求を開始するシナリオを想像してください。
- 実際には、より速く動作する人もいれば、よりゆっくりと動作する人もいます。
- したがって、実際にはもう 1 つのパラメータ、つまりリクエスト頻度が必要です。リクエスト頻度が 1 秒あたり 5 回、つまり 200 ミリ秒に 1 回の場合
- 対応する実際のシナリオは、100 人がリクエストを続け、リクエスト頻度は 1 秒あたり 5 リクエストです。
開発方法は?
オープンソース リポジトリの go-stress-testing コードからインスピレーションを得ています。開発手順は以下の通りです
ステップ1 シーンを作成する
- まずそのようなシーンを作成する必要があります。このシナリオでは、同時実行数、圧力テスト時間、リクエスト頻度を 3 つのパラメーターとして指定します。
func NewSceneWithInterval(name string, duration int, interval int) *Scene {
return &Scene{
Name: name,
// 设置默认每秒钟执行一次
Frequency: time.Second,
// 设置测试场景执行 duration 分钟
Duration: time.Duration(duration * 60) * time.Second,
// 执行压测频率
Interval: time.Millisecond * interval
}
}
ステップ 2 タスクを作成する
- 同時実行数に応じてタスクを作成する
- ここで、テスト対象のインターフェースの数が で、同時
m
実行数がn
- いくつかのインターフェースを再利用してタスク
n > m
を作成する場合n
func NewTask(task *stress.Task, url string) {
*task = *stress.NewTask("test", func() *stress.TestResult {
var errors uint64
var success uint64
// 开始时间
start := time.Now()
resp, err := http.Get(url)
// 统计任务执行时间,并保存到测试结果中
elapsed := time.Since(start)
if err != nil {
// 发生错误,请求失败
errors = 1
success = 0
}
if resp.StatusCode == 200 {
// 请求成功
success = 1
errors = 0
} else {
// 请求失败
success = 0
errors = 1
}
// 统计任务执行时间,并保存到测试结果中
elapsed := time.Since(start)
result := &stress.TestResult{
Requests: 1,
Errors: errors,
Success: success,
Rps: 0,
Elapsed: elapsed,
}
task.Result = result
return result
})
}
- タスクを作成したら、手順1で作成したシーンに追加します。
ステップ 3 シーンを実行する
- 主な困難は、現実世界のストレス テストをどのようにシミュレートするかにあります。
- ここでの関数の実装では、go 言語の同時実行モデルを使用し、ゴルーチンを最大限に活用しています。
func (s *Scene) RunWitTime() *TestResult {
// 并发数量-即任务的数量
concurrent := len(s.tasks)
// 用于等待所有的任务执行完成
wg := sync.WaitGroup{
}
// 创建一个令牌桶,用于控制并发数
tokens := make(chan bool, concurrent)
for i := 0; i < concurrent; i++ {
tokens <- true
}
// 创建一个停止信号通道,用于控制场景测试的执行时长
stop := make(chan bool)
go func() {
time.Sleep(s.Duration)
close(stop)
}()
// 按照指定的频率执行请求
ticker := time.NewTicker(s.Interval)
// 统计请求次数、错误次数、成功次数、总耗时、成功请求耗时
loop:
for {
select {
case <-stop:
// 停止场景测试
ticker.Stop()
wg.Wait()
break loop
default:
// 遍历所有任务,获取令牌并执行请求
for _, task := range s.tasks {
select {
case <- tokens:
// 执行请求
wg.Add(1)
go func(task *Task) {
// 一个请求执行完毕,释放令牌
defer func() {
tokens <- true
wg.Done()
}()
// 执行任务逻辑 并处理具体逻辑
// xxx
}(task)
default:
// 令牌桶已满,等待重试机制
// 等待令牌桶中有可用令牌
retryTicker := time.NewTicker(time.Millisecond * 50)
defer retryTicker.Stop()
select {
case <-tokens:
retryTicker.Stop()
wg.Add(1)
go func(task *Task) {
defer func() {
tokens <- true
wg.Done()
}()
// 执行任务逻辑 并处理具体逻辑
// xxx
}(task)
case <-stop:
ticker.Stop()
wg.Wait()
break loop
case <-retryTicker.C:
// 随机休眠 几百毫秒
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
}
}
}
}
return 结果集
}
ステップ 4 圧力試験データのテストと処理
- 4 つのインターフェイスを使用、同時に 10 をカウント、ストレス テストを 1 分間、ストレス テストの頻度を 500 ミリ秒
- テスト結果は次のとおりです
- その他の指標の計算
- 加工してjson文字列に変換
{
"total":815,
"success":657,
"error":158,
"successRate":"80.61%",
"rps":11,
"avgRt":"532.00ms",
"minRt":"53.76ms",
"maxRt":"1033.55ms",
"p90Rt":"708.68ms",
"p95Rt":"727.30ms",
"p99Rt":"758.09ms",
"successTime":"349762.00ms",
"allTime":"366977.00ms"
}
- ロジックコード
successRate := fmt.Sprintf("%.2f", float64(ret.Success) / float64(ret.Requests) * 100) + "%"
// 总的成功请求次数 / 总的压测时间
rps := uint64(math.Round(float64(ret.Success) / float64(runTime*60)))
// 平均
avgRt := fmt.Sprintf("%.2f", float64(ret.SuccessTime / (1000000 * ret.Success))) + "ms"
// 对成功响应时间进行排序
sort.Slice(ret.SuccessElapseds, func(i, j int) bool {
return ret.SuccessElapseds[i] < ret.SuccessElapseds[j]
})
minRt := fmt.Sprintf("%.2f", float64(ret.SuccessElapseds[0])/1000000) + "ms"
length := len(ret.SuccessElapseds)
maxRt := fmt.Sprintf("%.2f", float64(ret.SuccessElapseds[length - 1])/1000000) + "ms"
// 计算 90%Rt、95%Rt、99%Rt
TempP90 := float64(ret.SuccessElapseds[int(float64(length)*0.9)])/1000000
p90 := strconv.FormatFloat(TempP90, 'f', 2, 64) + "ms"
TempP95 := float64(ret.SuccessElapseds[int(float64(length)*0.95)])/1000000
p95 := strconv.FormatFloat(TempP95, 'f', 2, 64) + "ms"
TempP99 := float64(ret.SuccessElapseds[int(float64(length)*0.99)])/1000000
p99 := strconv.FormatFloat(TempP99, 'f', 2, 64) + "ms"
allElapsedTime := fmt.Sprintf("%.2f", float64(ret.ElapsedTime / 1000000)) + "ms"
allSuccessTime := fmt.Sprintf("%.2f", float64(ret.SuccessTime / 1000000)) + "ms"
// 打印每一个变量值
fmt.Printf("successRate: %.2f%%\n", successRate)
fmt.Printf("rps: %d\n", rps)
fmt.Printf("avgRt: %v\n", avgRt)
fmt.Printf("minRt: %v\n", minRt)
fmt.Printf("maxRt: %v\n", maxRt)
fmt.Printf("p90: %v\n", p90)
fmt.Printf("p95: %v\n", p95)
fmt.Printf("p99: %v\n", p99)
fmt.Printf("ElapsedTime: %v\n", allElapsedTime)
fmt.Printf("SuccessTime: %v\n", allSuccessTime)
fmt.Printf("Total: %v\n", ret.Requests)
fmt.Printf("Success: %v\n", ret.Success)
fmt.Printf("Error: %v\n", ret.Errors)
report := StressTestReportForAddress {
Total: ret.Requests,
Success: ret.Success,
Error: ret.Errors,
SuccessRate: successRate,
Rps: rps,
AvgRt: avgRt,
MinRt: minRt,
MaxRt: maxRt,
P90Rt: p90,
P95Rt: p95,
P99Rt: p99,
SuccessTime: allSuccessTime,
AllTime: allElapsedTime,
}
jsonBytes, err := json.Marshal(report)
if err != nil {
fmt.Println(err)
return ""
}
jsonString := string(jsonBytes)
fmt.Println(jsonString)