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つのクラウドトレンド

推薦する

外部リンクの本質:本性を明かさないようにする

SEO を行っている友人の多くは、外部リンクの重要性を知っていると思います。いわゆる SEO の教科...

マルチクラウドとハイブリッドクラウドのどちらを選択すべきか迷っていますか?まず概念を明確にしましょう

クラウド コンピューティングは、今日の企業が収益を上げるために競争している分野です。 AWS などの...

ブランドプロモーション+ユーザー集客+企画スタッフの旅行サイト開発「鉄のトライアングル」

ルールがなければ秩序は生まれません。この発言はまさに正しいです。私たちは何をするにも根拠を持たなけれ...

ビリビリと知乎のメディアの運命

この記事では、メディアコンテンツの観点から見た知乎とビリビリの発展の違いと、情報化時代における公共メ...

A5年末レビュー: 2013年のモバイルインターネットで何が起こったか

はじめに:いつの間にか、時間は静かに過ぎていきます。あっという間に、2013 年も終わりを迎えます。...

spinservers: サンノゼの「半仮想サーバー」の簡単なレビューで、これらの「大規模 VPS」がどれだけ効果的かを確認します

スピンサーバーの主な製品は、スタンドアロン サーバーと半仮想化サーバーの 2 つだけです。今回は、ウ...

[RSA2019 イノベーション サンドボックス] CloudKnox: ハイブリッド クラウド環境向けの ID および認証管理プラットフォーム

すべての RSA カンファレンスにおけるイノベーション サンドボックス セッションは常に注目の的とな...

個人ウェブマスターは「問題を抱えて生まれ、安楽に死ぬ」ことに注意を払っている

このタイトルを見てすぐに意味が分かる人も多いと思いますが、今日は検索エンジン最適化の観点からこの文章...

sparkvps: $25/年/KVM/2GB メモリ/25GB SSD/5TB トラフィック/ダラス/ニューヨーク

LEB からのメールを読んで、VPS ベンダーの sparkvps が、米国中部のダラスと米国東部の...

ユーザーエクスペリエンスに影響を与える4つの要素

検索エンジンにウェブサイトを評価してもらいたいなら、まずウェブサイトのユーザーエクスペリエンスを向上...

複雑でランダムなドメイン名サフィックス

HEXONETからメールを受け取ったとき、インターネット上の混沌とし​​たドメイン名サフィックスが登...

#ニュース# Virmach が仮想プラットフォームに切り替えようとしています。注意すべき点は次のとおりです。

virmach からの最新ニュース: OpenVZ 仮想化は廃止され、KVM に切り替えられる予定で...

ファーウェイクラウドの鄭葉来氏:優位性はトレンドを止めることはできない、技術革新が主なテーマ

2020年7月20日、Huawei CloudはTechWaveテクノロジーサミットを開催しました。...

クラウド ストレージの詳細な説明: 企業データをクラウドに移動するにはどうすればよいでしょうか?

Google の副社長であるケント・ウォーカー氏はかつて、2000 年の時点で人類史上保存されたデー...

ウェブサイト広告システムの概要:権限管理設計と構造設計

さまざまな無料広告システムがあります。それらの共通点は何でしょうか? どのシステムが適切に設計されて...