Go 分散トークン バケットの電流制限 + 最終利益の保証

Go 分散トークン バケットの電流制限 + 最終利益の保証

この記事は、Ouyang An が執筆した WeChat パブリックアカウント「Microservice Practice」から転載したものです。この記事を転載する場合は、Microservice Practice のパブリック アカウントにお問い合わせください。

前の記事では、固定時間ウィンドウの電流制限では突然の要求のピークに対応できないことを説明しました。この記事で説明するトークン バケット回路アルゴリズムは、このシナリオをより適切に処理できます。

仕組み

単位時間あたりに生成されるトークンは、バケット容量の上限に達するまで一定の割合でバケットに入れられます。

リクエストを処理します。そのたびに 1 つ以上のトークンを取得しようとします。取得できた場合はリクエストを処理し、取得できなかった場合はリクエストを拒否します。

長所と短所

アドバンテージ

瞬間的なバーストトラフィックを効果的に処理でき、バケット内のトークンをトラフィックバッファとして使用してバーストトラフィックをスムーズに処理できます。

欠点

実装はより複雑です。

コードの実装

  1. コア/制限/トークン制限.go

分散環境でバケットとトークンのストレージ コンテナーとして Redis を使用することを検討し、Lua スクリプトを使用してアルゴリズム プロセス全体を実装します。

Redis Lua スクリプト

  1. -- 1秒あたりに生成されるトークンの数、つまりトークン生成速度 
  2. ローカルレート = tonumber(ARGV[1])
  3. -- バケット容量 
  4. ローカル容量 = tonumber(ARGV[2])
  5. -- 現在のタイムスタンプ 
  6. ローカルnow = tonumber(ARGV[3])
  7. -- 現在のリクエストトークン番号 
  8. ローカル要求 = tonumber(ARGV[4])
  9. -- バケツを満たすのに何秒かかりますか 
  10. ローカルfill_time = 容量/レート
  11. -- 切り捨て、ttl は fill 時間の 2 倍になります 
  12. ローカルttl = math.floor(fill_time*2)
  13. -- 現在のタイムバケット容量 
  14. ローカルlast_tokens = tonumber(redis.call( "get" , KEYS[1]))
  15. -- 現在のバケット容量が0の場合、初めて入力することを意味し、デフォルトの容量はバケットの最大容量です。  
  16. last_tokens == nilの場合 
  17. last_tokens = 容量
  18. 終わり 
  19. -- 最終更新時刻 
  20. ローカルlast_refreshed = tonumber(redis.call( "get" , KEYS[2]))
  21. -- 最初のエントリの更新時間を 0 に設定します 
  22. last_refreshed == nilの場合 
  23. 最終更新日 = 0
  24. 終わり 
  25. -- 最後のリクエストからの経過時間 
  26. ローカルデルタ = 数学。最大(0, 現在 - 最終更新日)
  27. -- 最後のリクエストからの時間間隔、生成できるトークンの総数、最大容量を超えた場合、超過したトークンは破棄されます 
  28. ローカルfilled_tokens = math.最小(容量、last_tokens+(delta*rate))
  29. -- このリクエストのトークンの数は十分ですか?  
  30. ローカルで許可 = filled_tokens >= 要求
  31. -- 残りのバケット数 
  32. ローカルnew_tokens = filled_tokens
  33. -- このトークンの適用を許可し、残りの数量を計算します 
  34. 許可されれば 
  35. new_tokens = filled_tokens - リクエスト済み
  36. 終わり 
  37. -- 残りのトークンの数を設定する 
  38. redis.call( "setex" , KEYS[1], ttl, new_tokens)
  39. -- 更新時間を設定する 
  40. redis.call( "setex" , KEYS[2], ttl, now)
  41.  
  42. 返品可能

トークン バケット リミッターの定義

  1. TokenLimiter構造体型{
  2. // 1秒あたりの生産率
  3. レートint  
  4. // バケット容量
  5. バースト整数 
  6. // ストレージコンテナ
  7. *redis.Redis を保存します。
  8. // Redisキー 
  9. トークンキー文字列
  10. // バケット更新時間キー 
  11. タイムスタンプキー文字列
  12. // ロック
  13. rescueLock sync.Mutex
  14. // Redis ヘルスフラグ
  15. redisAlive uint32
  16. // Redis が失敗した場合は、インプロセス トークン バケットの現在のリミッターを使用します
  17. レスキューリミッター *xrate.リミッター
  18. // Redis 監視検出タスク識別子
  19. monitorStarted ブール値
  20. }
  21.  
  22. func NewTokenLimiter(rate, burst int , store *redis.Redis, key string) *TokenLimiter {
  23. tokenKey := fmt.Sprintf(tokenFormat, key )
  24. timestampKey := fmt.Sprintf(timestampFormat, key )
  25.  
  26. &TokenLimiter{を返す
  27. レート: レート、
  28. バースト:バースト、
  29. ストア: ストア、
  30. トークンキー: トークンキー、
  31. タイムスタンプキー: タイムスタンプキー、
  32. ライブ: 1,
  33. レスキューリミッター: xrate.NewLimiter(xrate.Every( time . Second / time .Duration(rate)), burst),
  34. }
  35. }

トークンを取得する

  1. func (lim *TokenLimiter) reserveN(now time . Time , n int ) bool {
  2. // Redis が正常かどうかを判定する
  3. // Redis が失敗したときにプロセス内電流制限を使用する
  4. // ボトムライン保護
  5. atomic.LoadUint32(&lim.redisAlive) == 0 の場合 {
  6. lim.rescueLimiter.AllowN(now, n)を返します
  7. }
  8. // スクリプトを実行してトークンを取得します
  9. 応答、err := lim.store.Eval(
  10. スクリプト、
  11. []弦{
  12. limit.tokenKey、
  13. lim.timestampKey、
  14. },
  15. []弦{
  16. strconv.Itoa(制限率)、
  17. strconv.Itoa(limit.burst)、
  18. strconv.FormatInt(now.Unix(), 10)、
  19. strconv.Itoa(n)、
  20. })
  21. // redis 許可 == false  
  22. // Lua ブール値false -> r Nil 一括返信
  23. //キーが存在しないケースの特別な処理
  24. err == redis.Nilの場合{
  25. 戻る 間違い 
  26. }そうでなければ err != nil {
  27. logx.Errorf( "レートリミッターの使用に失敗しました: %s、レスキューにはインプロセスリミッターを使用してください" , ​​err)
  28. // 実行例外、Redis ヘルス検出タスクを開始
  29. // フォールバックとしてインプロセス電流リミッターも使用する
  30. lim.startMonitor()
  31. lim.rescueLimiter.AllowN(now, n)を返します
  32. }
  33.  
  34. コード、ok := resp.(int64)
  35. !okの場合{
  36. logx.Errorf( "redis スクリプトの評価に失敗しました: %v、レスキューにはインプロセス リミッターを使用してください" , ​​resp)
  37. lim.startMonitor()
  38. lim.rescueLimiter.AllowN(now, n)を返します
  39. }
  40.  
  41. // redis 許可 == true  
  42. // Lua ブール値true -> r整数応答1
  43. 戻りコード == 1
  44. }

Redis 障害フォールバック戦略

バックアップ戦略の設計は非常に詳細です。 redis が利用できない場合、基本的なフロー制限が利用可能であり、サービスが過負荷にならないようにするために、ratelimit のスタンドアロン バージョンがバックアップ フロー制限として起動されます。

  1. // Redis のヘルス検出を有効にする
  2. func (lim *TokenLimiter) startMonitor() {
  3. lim.rescueLock.Lock()
  4. lim.rescueLock.Unlock() を延期する
  5. // 繰り返し開くのを防ぐ
  6. lim.monitorStartedの場合{
  7. 戻る 
  8. }
  9.  
  10. // タスクとヘルスフラグを設定する
  11. lim.monitorStarted = true  
  12. アトミック.StoreUint32(&lim.redisAlive, 0)
  13. // ヘルス検出
  14. lim.waitForRedis() を実行する
  15. }
  16.  
  17. // Redis ヘルス検出スケジュールタスク
  18. func (lim *TokenLimiter) waitForRedis() {
  19. ティッカー:=時間.NewTicker(pingInterval)
  20. // この関数はヘルス検出が成功したときにコールバックされます
  21. 遅延関数() {
  22. ticker.Stop()
  23. lim.rescueLock.Lock()
  24. lim.monitorStarted = false  
  25. lim.rescueLock.Unlock()
  26. }()
  27.  
  28. 範囲ticker.C {
  29. // pingはRedisの組み込みヘルス検出コマンドです
  30. lim.store.Ping() の場合 {
  31. //ヘルス検出が成功したので、ヘルスフラグを設定します
  32. アトミック.StoreUint32(&limit.redisAlive, 1)
  33. 戻る 
  34. }
  35. }
  36. }

プロジェクトギャラリー

https://github.com/zeromicro/go-zero

go-zero をご利用いただき、ぜひスターを付けてください!

<<:  クラウド コンピューティングがビジネスを成功に導く 4 つの方法

>>:  2022年に注目すべき5つのクラウドトレンド

推薦する

マンツアジアと中国の大手ディスプレイメーカーが共同で力を発揮し、テレビパネル照明の新記録を樹立

幅広い技術ポートフォリオを持ち、世界的に活躍するハイテク機器メーカーである Manz AG は、実践...

ウェブサイト最適化の初心者がローカルSEOブログをホームページに素早く掲載するための重要なポイント

以前、A5 では SEO に関する知識を皆さんとたくさん共有しました。今日、SEO Qibing は...

教えてください: BandwagonHost cn2 gia vps はどうですか? 買う価値はあるでしょうか?

BandwagonHost cn2 giaはどうですか?この質問に答える前に、まずなぜ cn2 gi...

AWS が AWS PrivateLink を発表

[51CTO.com からのオリジナル記事] 本日の AWS re:Invent カンファレンスで、...

SEOを行うには、絶対的な優位性を維持するために競合他社を注意深く監視することを学ぶ必要があります。

ウェブサイトのランキングは相対的なプロセスです。最高のウェブサイトは存在せず、より良いウェブサイトが...

主流の検索エンジンの原則

今日は検索エンジンの原理を紹介します。まずは写真を見てみましょう…次に、階層ごとに説明します。 1....

2018年上半期、これらのマーケティング事例は注目を集めている

月給5,000~50,000のこれらのプロジェクトはあなたの将来ですテクノロジーの発展に伴い、新しい...

Vultr - 6 月の VPS の第 1 波では 10 ドルを無料でプレゼントします。

Vultr は API 機能を強化し、新しい 10 ドルの割引コード GIVEME10 を発表しまし...

ウェブサイトのコンバージョン率を向上させるには、まず3つの詳細から始めましょう

ウェブサイトでSEOを行う目的はトラフィックを増やすことであり、トラフィックを増やすことの最終的な目...

産業分野におけるエッジコンピューティングの 4 つのユースケース

エッジコンピューティングと IoT の関係を考慮すると、IoT のサブカテゴリである産業用 IoT ...

私のウェブサイト 私の夢 DZ 地域コミュニティ収益方法

おそらく私はコンピュータとインターネットに特別な親和性があるのでしょう。2006 年、中学生の頃はコ...

Inmaise マーケティング ディレクター You Yajie との独占インタビュー: 学びを深め、自分を超えよう!

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますInmai...

bluevm-3周年記念/4つの特別VPSプロモーション

bluevm(別名「Bu Lu」)は設立から3年目を迎え、bluevmが開発したFeathurオープ...

Go分散リンクトレーシングについての簡単な説明

現代の複雑な分散システム環境では、アプリケーションやシステムのパフォーマンスを診断することは非常に困...