go-kit 微服務 限流 (uber/ratelimit 和 golang/rate 實現)

hwholiday發表於2020-02-12

golang/rate 簡介(golang.org/x/time/rate)

  • golang 標準庫中就自帶的限流演算法
  • 該限流器是基於 Token Bucket(令牌桶) 實現的
//第一個引數是 r Limit。代表每秒可以向 Token 桶中產生多少 token
//第二個引數是 b int。b 代表 Token 桶的容量大小
golangLimit := rate.NewLimiter(10, 1)
//golangLimit 其令牌桶大小為 1, 以每秒 10 個 Token 的速率向桶中放置 Token。

  • Wait/WaitN
    • Wait 實際上就是 WaitN(ctx,1)
    • 當使用 Wait 方法時,如果令牌桶內 Token(大於 or 等於 N)直接返回,如果當時令牌桶內 Token 不足 (小於 N),那麼 Wait 方法將會阻塞,直到能從令牌桶內獲得 Token
  • Allow/AllowN
    • Allow 實際上就是 AllowN(time.Now(),1)。
    • 當使用使用 AllowN 方法時,截止到 time.Now() 這一時刻 (time 可以自己傳入) 令牌桶中數目必須(大於 or 等於 N),滿足則返回正確,同時從令牌桶中消費 N 個 Token
    • 應用場景請求速度太快就直接丟掉一些請求
  • Reserve/ReserveN
    • Reserve 相當於 ReserveN(time.Now(), 1)
    • 當使用使用 ReserveN 方法時,不管能不能從令牌桶內獲取到 Token 都會返回一個 Reservation 物件
    • Reservation 物件的 Ok() 方法返回了令牌桶是否可以在最大等待時間內提供請求的令牌數量,如果 OK 為 false,則 Delay() 返回 InfDuration
    • Reservation 物件的 Delay() 方法返回了需要等待的時間,如果時間為 0 則不需要等待
    • 如果不想等待就呼叫 Reservation 物件的 Cancel()

uber/ratelimit 簡介(go.uber.org/ratelimit)

  • uber 開源的限流演算法,
  • 該元件基於 Leaky Bucket(漏桶) 實現的
//一秒請求一次
uberLimit := ratelimit.New(1)         
//使用方法
uberLimit.Take()        

Endpoint 層修改

  • 新增基於 golang.org/x/time/rate 的限流中介軟體
func NewGolangRateAllowMiddleware(limit *rate.Limiter) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (response interface{}, err error) {
            if !limit.Allow() {
                return "", errors.New("limit req  Allow")
            }
            return next(ctx, request)
        }
    }
}

  • 新增基於 go.uber.org/ratelimit 的限流中介軟體
func NewUberRateMiddleware(limit ratelimit.Limiter) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (response interface{}, err error) {
            limit.Take()
            return next(ctx, request)
        }
    }
}
  • 對於需要限流的方法新增限流中介軟體
func NewEndPointServer(svc v4_service.Service, log *zap.Logger, limit *rate.Limiter, limiter ratelimit.Limiter) EndPointServer {
    var addEndPoint endpoint.Endpoint
    {
        addEndPoint = MakeAddEndPoint(svc)
        addEndPoint = LoggingMiddleware(log)(addEndPoint)
        addEndPoint = AuthMiddleware(log)(addEndPoint)
        addEndPoint = NewUberRateMiddleware(limiter)(addEndPoint)
    }
    var loginEndPoint endpoint.Endpoint
    {
        loginEndPoint = MakeLoginEndPoint(svc)
        loginEndPoint = LoggingMiddleware(log)(loginEndPoint)
        loginEndPoint = NewGolangRateAllowMiddleware(limit)(loginEndPoint)
    }
    return EndPointServer{AddEndPoint: addEndPoint, LoginEndPoint: loginEndPoint}
}

激動人心的時刻來了,修改 main 方法

utils.NewLoggerServer()
golangLimit := rate.NewLimiter(10, 1) //每秒產生10個令牌,令牌桶的可以裝1個令牌
uberLimit := ratelimit.New(1)         //一秒請求一次
server := v4_service.NewService(utils.GetLogger())
endpoints := v4_endpoint.NewEndPointServer(server, utils.GetLogger(), golangLimit, uberLimit)
httpHandler := v4_transport.NewHttpHandler(endpoints, utils.GetLogger())
utils.GetLogger().Info("server run 0.0.0.0:8888")
_ = http.ListenAndServe("0.0.0.0:8888", httpHandler))

日誌(一秒請求 100 次介面)

  • 日誌比較多這裡我減少了一些日誌以便於觀看

  • go.uber.org/ratelimit 的限流中介軟體

2020-01-03 09:46:30     DEBUG   v4_transport/transport.go:70    5fd6eae3-32ea-5a24-9507-ed3c865f2e50    {" 開始解析請求資料": {"a":1,"b":1}}
2020-01-03 09:46:31     DEBUG   v4_transport/transport.go:70    ca39186b-c4e1-5976-bb02-f5e484aeca48    {" 開始解析請求資料": {"a":1,"b":1}}
2020-01-03 09:46:32     DEBUG   v4_transport/transport.go:70    691bfdf1-6701-553f-ae78-d443eff6fb6b    {" 開始解析請求資料": {"a":1,"b":1}}
......
可以看到請求是按照我們控制的一秒一秒請求的
  • golang.org/x/time/rate 的限流中介軟體
2020-01-03 09:42:17     DEBUG   v4_transport/transport.go:75    cc74b82d-4fa9-558c-acd3-ad2ea292dfdd    {"請求結束封裝返回值":"token"}}
2020-01-03 09:42:17     WARN    v4_transport/transport.go:20    c1a00c56-edad-55b0-b546-076a0d070086    {"error": "limit req  Allow"}
2020-01-03 09:42:17     DEBUG   v4_transport/transport.go:75    2c8ab986-c300-5df1-99fc-0d88c8db8c40    {"請求結束封裝返回值":"token"}}
2020-01-03 09:42:17     WARN    v4_transport/transport.go:20    8e5dc5d4-e048-56c7-9a42-33863843cd67    {"error": "limit req  Allow"}
......

結語

  • 在請求數量過多且我們的服務處理不完時,新增限流能保證我們服務的健康狀態
  • 在文章中我只展示了下限流在 go-kit 裡面的簡單使用
  • 歡迎新增 QQ 一起討論

完整程式碼地址

聯絡 QQ: 3355168235

更多原創文章乾貨分享,請關注公眾號
  • go-kit 微服務 限流 (uber/ratelimit 和 golang/rate 實現)
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章