golang tollbooth 中间件 & 压测工具 vegeta

参考:

1、[译] Go 中基于 IP 地址的 HTTP 限流

2、Tollbooth - Fasthttp integration layer

3、didip/tollbooth Simple middleware to rate-limit HTTP requests

4、valyala / fasthttp Fast HTTP package for Go

5、uber-go / ratelimit

6、ulule / limiter-examples - 法国众筹公司🤦🏻‍♀️

7、tsenart / vegeta

8、推荐一款高性能 HTTP 负载测试工具 Vegeta

9、如何理解Latency和Throughput: 吞吐量和延迟

10、TPS、QPS、并发数、Throughput等概念

安装 fasthttp

go get -u github.com/valyala/fasthttp

限流 fasthttp -> didi开源的包,注意版本哦 (uber-go / ratelimit 也挺牛逼)

go get github.com/didip/tollbooth@v1.0.0
go get github.com/didip/tollbooth_fasthttp

测试代码,注意写入写出json,没啥技巧,就硬抄😂

package main

import (
	"encoding/json"
	"fmt"
	"github.com/didip/tollbooth"
	"github.com/didip/tollbooth_fasthttp"
	"github.com/valyala/fasthttp"
	"log"
	"strings"
	"time"
)

var TLogger *log.Logger
var (
	strContentType     = []byte("Content-Type")
	strApplicationJSON = []byte("application/json")
)

func Index(ctx *fasthttp.RequestCtx) {
	fmt.Fprint(ctx, "Welcome!\n")
}

func Hello(ctx *fasthttp.RequestCtx) {
	fmt.Fprintf(ctx, "hello, niubi")
}

type Output struct {
	Message string `json:"message"`
	Result  string `json:"result"`
}

type Input struct {
	Source string `json:"source"`
	Mode   string `json:"mode"`
}

func doJSONWrite(ctx *fasthttp.RequestCtx, code int, obj interface{}) {
	ctx.Response.Header.SetCanonical(strContentType, strApplicationJSON)
	ctx.Response.SetStatusCode(code)
	if err := json.NewEncoder(ctx).Encode(obj); err != nil {
		TLogger.Println(err)
		ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
	}
}

var builder strings.Builder

func Hello_fix(ctx *fasthttp.RequestCtx) {

	var output_one Output
	var input_one Input
	var paramErrMsg string
	var statusCode int

	body := ctx.PostBody()
	if err := json.Unmarshal(body, &input_one); err != nil {
		paramErrMsg = "请求参数错误,请检查输入格式"
		statusCode = fasthttp.StatusBadRequest
		// 这里的过程有点问题,无法解析
	} else {
		if len(strings.TrimSpace(input_one.Source)) > 0 {

			// 连续的字符串拼接,虽然很快,但不适用这里
			//builder.Write([]byte(input_one.Source))
			//builder.Write([]byte("-->"))
			//builder.WriteString("add-->")
			//builder.Write([]byte(input_one.Mode))
			//output_one.Result = builder.String()

			output_one.Result = input_one.Source + "->" + input_one.Mode // 字符串拼接,简单粗暴,慢一点,跟python很像
			output_one.Message = "ok"
			doJSONWrite(ctx, fasthttp.StatusOK, output_one)
			return
		} else {
			paramErrMsg = "长度为0"
			statusCode = fasthttp.StatusBadRequest
		}
		output_one.Result = string(body)
		output_one.Message = paramErrMsg
		doJSONWrite(ctx, statusCode, output_one)
	}
}

func main() {

	//router := fasthttprouter.New()  // 这里没有使用 fasthttprouter, 直接上,注意func中需要传入参数ctx
	requestHandler := func(ctx *fasthttp.RequestCtx) {
		switch string(ctx.Path()) {
		case "/hello":
			Hello(ctx)
		case "/":
			Index(ctx)
		case "/hello_post":
			//router.POST("/hello_post", Hello_fix)
			Hello_fix(ctx)
		default:
			ctx.Error("Unsupporterd path", fasthttp.StatusNotFound)
		}
	}
	limiter := tollbooth.NewLimiter(1, time.Second)
	fasthttp.ListenAndServe(":8080", tollbooth_fasthttp.LimitHandler(requestHandler, limiter))
}


// 不启动 rate limit 的情况下
//func main() {
//	router := fasthttprouter.New()
//	router.GET("/hello", Hello)
//	router.POST("/hello_post", Hello_fix)
//	if err := fasthttp.ListenAndServe("0.0.0.0:8080", router.Handler); err != nil {
//		TLogger.Panic("HTTP 服务启动失败")
//	}
//}

压测效果:

global flags:
  -cpus int
    使用 CPU 的数量 (默认为 4 个)
  -profile string
    指定在执行期间启用哪个分析器,支持 cpu 和 heap。
  -version
    打印版本并退出。
attack command:
  -body string
    指定请求主体文件里的内容。
  -cert string
    指定用于 HTTPS 请求的 PEM 格式的客户端证书文件。如果 -key 未指定,它会被设置为这个标志的值。
  -connections int
    指定每个目标主机打开的空闲连接的最大数目,默认值为 10000。
  -duration duration
    指定发送请求到目标主机的时长,用 0 表示永久。
  -header value
    指定目标的请求头,可以重复指定多个请求头。
  -http2
    指定是否向支持的服务器发送 HTTP/2 请求,默认为:true。
  -insecure
    指定是否忽略无效的服务器 TLS 证书。
  -keepalive
    指定是否使用持久链接,默认值为:true。
  -key string
    指定 HTTPS 请求中使用的 PEM 编码的 SSL 客户端证书私钥文件。
  -laddr value
    指定要使用的本地 I P地址,默认值为:0.0.0.0。
  -lazy
    指定是否使用延迟模式读取目标。
  -output string
    指定输出文件的位置,默认为标准输出。
  -rate uint
    指定每秒钟对目标发送的请求数,默认值为:50。
  -redirects int
    指定每个请求的重定向的最大次数,默认为 10 次。当值为 -1, 不会遵循重定向但响应标记为成功。
  -root-certs value
    指定可信的 TLS 根证书文件,多个的情况下使用逗号分隔。如果未指定,使用系统默认的 CA 证书。
  -targets string
    指定目标文件,默认为标准输入。
  -timeout duration
    指定每个请求的超时时间,默认值为 30s。
  -workers uint
    指定初始化进程数量,默认值为 10。
report command:
  -inputs string
    指定报告输入文件,默认为标准输入。
  -output string
    指定报告输出文件,默认为标准输出。
  -reporter string
    指定要生成的报告的格式,支持 text,json, plot, hist[buckets]。默认为文本。
dump command:
  -dumper string
    指定转存文件,支持 json, csv 格式。默认为 json 格式。
  -inputs string
    指定要转存的输入文件,默认为标准输入,指定多个用逗号分隔。
  -output string
    指定要转存的输出文件,默认为标准输出。

搞一个 post  hello_post 进行测试:


# 测试案例 POST - hello_post, 未进行流量限制
# 电脑默认开启的是 cpu4个一起搞
jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=100000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         50183, 50167.94, 24945.60
Duration      [total, attack, wait]             1.005s, 1s, 5.048ms
Latencies     [min, mean, 50, 90, 95, 99, max]  10.911µs, 6.136ms, 2.801ms, 16.89ms, 24.38ms, 38.505ms, 64.408ms
Bytes In      [total, mean]                     1053318, 20.99
Bytes Out     [total, mean]                     727291, 14.49
Success       [ratio]                           49.98%
Status Codes  [code:count]                      0:25104  200:25079

jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 2 attack  -format=json -rate=100000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         40945, 37548.48, 8717.79
Duration      [total, attack, wait]             1.152s, 1.09s, 61.785ms
Latencies     [min, mean, 50, 90, 95, 99, max]  10.596µs, 45.69ms, 5.067ms, 137.514ms, 201.844ms, 249.92ms, 386.753ms
Bytes In      [total, mean]                     421890, 10.30
Bytes Out     [total, mean]                     291305, 7.11
Success       [ratio]                           24.53%
Status Codes  [code:count]                      0:30900  200:10045

jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=50000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         49999, 49957.40, 28333.98
Duration      [total, attack, wait]             1.017s, 1.001s, 16.356ms
Latencies     [min, mean, 50, 90, 95, 99, max]  10.719µs, 5.633ms, 2.491ms, 15.593ms, 22.69ms, 41.062ms, 90.625ms
Bytes In      [total, mean]                     1210482, 24.21
Bytes Out     [total, mean]                     835809, 16.72
Success       [ratio]                           57.64%
Status Codes  [code:count]                      0:21178  200:28821

jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=30000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         30000, 30003.12, 29837.60
Duration      [total, attack, wait]             1s, 999.896ms, 251.244µs
Latencies     [min, mean, 50, 90, 95, 99, max]  27.814µs, 847.483µs, 228.297µs, 1.702ms, 2.832ms, 12.973ms, 26.124ms
Bytes In      [total, mean]                     1253364, 41.78
Bytes Out     [total, mean]                     865418, 28.85
Success       [ratio]                           99.47%
Status Codes  [code:count]                      0:158  200:29842


jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=20000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         20000, 20002.51, 20000.21
Duration      [total, attack, wait]             999.99ms, 999.874ms, 115.368µs
Latencies     [min, mean, 50, 90, 95, 99, max]  55.093µs, 202.688µs, 115.003µs, 276.439µs, 569.742µs, 2.254ms, 5.523ms
Bytes In      [total, mean]                     840000, 42.00
Bytes Out     [total, mean]                     580000, 29.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:20000


# 测试如果进行 ratelimit 限制后的 压测效果
# 10w
jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=100000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         50743, 50463.75, 1.98
Duration      [total, attack, wait]             1.011s, 1.006s, 5.626ms
Latencies     [min, mean, 50, 90, 95, 99, max]  10.971µs, 4.655ms, 2.322ms, 12.943ms, 19.858ms, 31.756ms, 53.951ms
Bytes In      [total, mean]                     1142355, 22.51
Bytes Out     [total, mean]                     849439, 16.74
Success       [ratio]                           0.00%
Status Codes  [code:count]                      0:21452  200:2  429:29289
Error Set:
429 Too Many Requests


# 1w
jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=10000 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         10000, 10006.90, 1.00
Duration      [total, attack, wait]             999.409ms, 999.31ms, 98.71µs
Latencies     [min, mean, 50, 90, 95, 99, max]  56.248µs, 166.948µs, 112.375µs, 201.068µs, 285.87µs, 1.203ms, 6.917ms
Bytes In      [total, mean]                     390003, 39.00
Bytes Out     [total, mean]                     290000, 29.00
Success       [ratio]                           0.01%
Status Codes  [code:count]                      200:1  429:9999


# 100
jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=100 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         100, 100.85, 1.01
Duration      [total, attack, wait]             992.305ms, 991.556ms, 748.715µs
Latencies     [min, mean, 50, 90, 95, 99, max]  255.202µs, 748.089µs, 604.875µs, 1.097ms, 1.151ms, 3.835ms, 5.859ms
Bytes In      [total, mean]                     3903, 39.03
Bytes Out     [total, mean]                     2900, 29.00
Success       [ratio]                           1.00%
Status Codes  [code:count]                      200:1  429:99
Error Set:
429 Too Many Requests

# 10  
# 确实效果感人,因为我们的 rate limt 中 用的是令牌桶算法 -> 限制了每秒需要通过请求最多是1个,不管来多少花里胡哨的并发,所以这里请求10个通过1个,请求100个也是通过1个
jq -ncM '{method: "POST", url: "http://localhost:8080/hello_post", body: {"source":"haha",   "mode":"v2"} | @base64, header: {"Content-Type": ["application/json"]}}' |
  vegeta -cpus 4 attack  -format=json -rate=10 -duration=1s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         10, 11.11, 1.11
Duration      [total, attack, wait]             901.188ms, 900.12ms, 1.068ms
Latencies     [min, mean, 50, 90, 95, 99, max]  483.77µs, 1.529ms, 888.565µs, 4.442ms, 7.646ms, 7.646ms, 7.646ms
Bytes In      [total, mean]                     393, 39.30
Bytes Out     [total, mean]                     290, 29.00
Success       [ratio]                           10.00%
Status Codes  [code:count]                      200:1  429:9

报告字段解释:

 所以重点关注下,并发情况下的吞吐量 - throughput (单位时间内的)

还能输出报告:

vegeta plot --title HelloGitHub results.bin > plot.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tollbooth 是一个用 Go 语言编写的用来限制 HTTP 访问速度的中间件,可用来限制每个 HTTP 请求的传输速率。例如你可以不限制 / 的访问速率,但是可以针对 /login 限制每个 IP 每秒最多 POST 多少个请求。Go 程序中使用的方法:package main import (     "github.com/didip/tollbooth"     "net/http"     "time" ) func HelloHandler(w http.ResponseWriter, req *http.Request) {     w.Write([]byte("Hello, World!")) } func main() {     // You can create a generic limiter for all your handlers     // or one for each handler. Your choice.     // This limiter basically says: allow at most 1 request per 1 second.     limiter := tollbooth.NewLimiter(1, time.Second)     // This is an example on how to limit only GET and POST requests.     limiter.Methods = []string{"GET", "POST"}     // You can also limit by specific request headers, containing certain values.     // Typically, you prefetched these values from the database.     limiter.Headers = make(map[string][]string)     limiter.Headers["X-Access-Token"] = []string{"abc123", "xyz098"}     // And finally, you can limit access based on basic auth usernames.     // Typically, you prefetched these values from the database as well.     limiter.BasicAuthUsers = []string{"bob", "joe", "didip"}     // Example on how to wrap your request handler.     http.Handle("/", tollbooth.LimitFuncHandler(limiter, HelloHandler))     http.ListenAndServe(":12345", nil) 标签:Tollbooth

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值