分散サービス電流制限の実践、私たちはすでにあなたのためにピットを手配しました

分散サービス電流制限の実践、私たちはすでにあなたのためにピットを手配しました

[[273022]]

1. 電流制限の役割

API インターフェースは呼び出し側の動作を制御できないため、リクエスト数が瞬間的に急増すると、インターフェースがサーバーのリソースを過剰に占有し、他のリクエストの応答が遅くなったり、タイムアウトになったり、さらに悪いことにサーバーがクラッシュしたりします。

レート制限とは、アプリケーション サービスの要求を制限することを指します。たとえば、特定のインターフェースのリクエスト制限は 1 秒あたり 100 であり、制限を超えるリクエストはすぐに失敗するか、破棄されます。

電流制限は以下を処理できます:

  • 人気企業からの突然の依頼。
  • 呼び出し側のバグによって発生したバースト要求。
  • 悪意のある攻撃要求。

したがって、パブリックインターフェースには電流制限対策を講じる必要があります。

2. 分散電流制限はなぜ必要なのでしょうか?

アプリケーションが単一ポイント アプリケーションである場合、アプリケーションが調整されている限り、アプリケーションが依存するさまざまなサービスも保護されます。

しかし、さまざまな理由から、オンライン ビジネスは主に分散型システムになっています。現在、単一ノードの制限では、そのノード自体しか保護できず、アプリケーションが依存するさまざまなサービスを保護することはできません。また、ノード容量の拡大や縮小時にサービス全体のリクエスト制限を正確に制御することができません。

分散電流制限を実装すると、サービス クラスター全体の要求制限を簡単に制御できます。クラスター全体に対するリクエスト数は制限されているため、サービスが依存するさまざまなリソースも電流制限によって保護されます。

3. 電流制限アルゴリズム

電流制限を実装する方法は多数あります。プログラムでは、インターフェースのトラフィックは通常、1 秒あたりに処理されるトランザクション数 (トランザクション/秒) に基づいて測定されます。

この記事では、一般的に使用される電流制限アルゴリズムをいくつか紹介します。

  • 固定ウィンドウカウンター。
  • 引き戸カウンター;
  • 漏れやすいバケツ;
  • トークンバケット。

1. ウィンドウカウンタアルゴリズムを修正

固定ウィンドウ カウンター アルゴリズムの概念は次のとおりです。

  • 時間を複数のウィンドウに分割します。
  • 各ウィンドウ内のリクエストごとにカウンターを 1 ずつ増加します。
  • カウンターが制限を超えると、このウィンドウ内のすべてのリクエストが破棄されます。時間が次のウィンドウに達すると、カウンターはリセットされます。

固定ウィンドウ カウンターは最も単純なアルゴリズムですが、制限の 2 倍を超えることが許可される場合があります。次の状況を検討してください: 1 秒以内に最大 5 つのリクエストが通過でき、最初のウィンドウの最後の 0.5 秒以内に 5 つのリクエストが許可され、2 番目のウィンドウの最初の 0.5 秒以内に 5 つのリクエストが許可されます。これは、1 秒以内に 10 件のリクエストが行われたことを意味します。

2. スライディングウィンドウカウンタアルゴリズム

スライディング ウィンドウ カウンター アルゴリズムの概念は次のとおりです。

  • 時間を複数の間隔に分割します。
  • 各間隔でリクエストが行われるたびに、カウンターが 1 ずつ増加し、複数の間隔を占める時間ウィンドウが維持されます。
  • 各間隔の後、最も古い間隔が破棄され、最新の間隔が含まれます。
  • 現在のウィンドウ内のリクエストの合計数が制限を超えると、このウィンドウ内のすべてのリクエストが破棄されます。

スライディング ウィンドウ カウンターは、ウィンドウを細分化し、時間の経過とともに「スライド」させるアルゴリズムです。このアルゴリズムは、固定ウィンドウ カウンターによって発生する二重バースト要求を回避しますが、時間間隔の精度が高くなるほど、アルゴリズムに必要なスペース容量が大きくなります。

3. リーキーバケットアルゴリズム

リーキーバケットアルゴリズムの概念は次のとおりです。

各リクエストを「水滴」として扱い、「漏れやすいバケツ」に入れて保管します。

「リーキー バケット」は、実行するために一定の速度でリクエストを「漏らします」。 「漏れやすいバケツ」が空であれば、「漏れ」は止まります。

「バケツ」がいっぱいになった場合、余分な「水滴」はそのまま捨てられます。

リーキー バケット アルゴリズムは、多くの場合、キューを使用して実装されます。サービス要求はキューに保存され、サービス プロバイダーはキューから要求を取り出して一定の速度で実行します。過剰なリクエストはキューに入れられるか、直接拒否されます。

リーキーバケットアルゴリズムの欠陥も明らかです。短期間に大量のバースト要求が発生した場合、その時点でサーバーに負荷がかかっていなくても、各要求は応答されるまでしばらくキュー内で待機する必要があります。

4. トークンバケットアルゴリズム

トークン バケット アルゴリズムの概念は次のとおりです。

  • トークンは固定レートで生成されます。
  • 生成されたトークンはトークン バケットに配置されます。トークン バケットがいっぱいになると、余分なトークンは破棄されます。リクエストが到着すると、トークン バケットからトークンを取得しようとします。トークンを取得するリクエストを実行できます。
  • バケットが空の場合、トークンを取得する試みはすべて破棄されます。

トークン バケット アルゴリズムは、すべての要求を時間間隔内に均等に分散し、サーバーが耐えられる範囲内でバースト要求を受け入れることができます。そのため、広く使用されている電流制限アルゴリズムです。

4. コードの実装

このように重要な機能であるため、Java には当然、電流制限を実装するクラス ライブラリが多数存在します。たとえば、Google のオープンソース プロジェクト guava は、単一ポイント トークン バケットの電流制限を実装する RateLimiter クラスを提供します。

分散電流制限によく使用されるフレームワークには、Hystrix、resilience4j、Sentinel などがありますが、これらのフレームワークではすべてサードパーティ ライブラリの導入が必要です。国有企業などの保守的な企業では、外部ライブラリの導入には何段階もの承認が必要となり、さらに面倒なことになります。

分散電流制限は本質的にクラスターの同時実行性の問題です。広く使用されているミドルウェアである Redis は、単一プロセスと単一スレッドという特性を備えており、分散クラスターの同時実行の問題を自然に解決できます。この記事では、Redis を使って単一リクエスト判定と電流制限を実装する機能について簡単に紹介します。

1. スクリプト

上記の比較の結果、最も適切な電流制限アルゴリズムはトークン バケット アルゴリズムです。電流制限アルゴリズムを実装するには、クエリと計算のために Redis を繰り返し呼び出す必要があります。 1 回の電流制限判定には複数のリクエストが必要となり、時間がかかります。そこで、計算処理をRedis側に置いてLuaスクリプトを書いて実行し、Redisへのリクエスト1回で電流制限判定が完了する方法を採用しました。

トークン バケット アルゴリズムでは、バケット サイズと現在のトークン数を Redis に保存し、定期的に新しいトークンを追加する必要があります。もちろん、最も簡単な方法は、時々 Redis をリクエストして、保存されているトークンの数を増やすことです。

しかし実際には、現在の制限リクエスト間の時間とトークン追加の速度を計算することで、最後のリクエストからこのリクエストまでにトークン バケットに追加する必要があるトークンの数を計算できます。したがって、Redis のトークン バケットには、最後のリクエストの時間とトークンの数を保存するだけでよく、バケット サイズとトークンの追加速度は、パラメータを渡すことで動的に変更できます。

スクリプトを初めて実行したとき、トークン バケットはデフォルトでいっぱいになっているため、データの有効期限をトークン バケットがいっぱいになるまでの時間に設定して、時間内にリソースを解放することができます。

完成した Lua スクリプトは次のとおりです。

  1. ローカルratelimit_info = redis.pcall( 'HMGET' 、KEYS[1]、 'last_time' 'current_token' )
  2. ローカルlast_time = ratelimit_info[1]
  3. ローカルのcurrent_token = tonumber(ratelimit_info[2])
  4. ローカルmax_token = tonumber(ARGV[1])
  5. ローカルトークンレート = tonumber(ARGV[2])
  6. 地元 現在の時刻= tonumber(ARGV[3])
  7. ローカルリバースタイム = 1000/トークンレート
  8. current_token == nilの場合 
  9. 現在のトークン = 最大トークン
  10. 最終時刻 =現在の時刻 
  11. それ以外 
  12. ローカル過去時刻 =現在時刻- 最終時刻
  13. ローカルreverse_token = math.floor(past_time/reverse_time)
  14. 現在のトークン = 現在のトークン + 逆トークン
  15. last_time = 逆時刻*逆トークン+last_time
  16. current_token>max_tokenの場合 
  17. 現在のトークン = 最大トークン
  18. 終わり 
  19. 終わり 
  20. ローカル結果 = 0
  21. 現在のトークン>0の場合 
  22. 結果 = 1
  23. 現在のトークン = 現在のトークン-1
  24. 終わり  
  25. redis.call( 'HMSET' 、KEYS[1]、 'last_time' 、last_time、 'current_token' 、current_token)
  26. redis.call( 'pexpire' 、KEYS[1]、math.ceil(reverse_time*(max_token-current_token)+( current_time -last_time)))
  27. 結果を返す

2.電流制限を実行する

ここでは、Redis スクリプトを呼び出すために Spring Data Redis が使用されます。

Redis スクリプト クラスを記述します。

  1. パブリッククラスRedisReteLimitScriptはRedisScript<String>を実装します。
  2. プライベート静的最終文字列 SCRIPT =
  3. "local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token') local last_time = ratelimit_info[1] local current_token = tonumber(ratelimit_info[2]) local max_token = tonumber(ARGV[1]) local token_rate = tonumber(ARGV[2]) local current_time = tonumber(ARGV[3]) local reverse_time = 1000/token_rate current_token == nil の場合、current_token = max_token last_time = current_time それ以外の場合、local past_time = current_time-last_time; local reverse_token = math.floor(past_time/reverse_time) current_token = current_token+reverse_token; last_time = river_time*reverse_token+last_time current_token>max_token の場合、current_token = max_token end end local result = '0' if(current_token>0) then result = '1' current_token = current_token-1 end redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_toke redis.call('pexpire',KEYS[1],math.ceil(reverse_time*(max_tokencurrent_token)+(current_time-last_time))) return result" ;
  4.  
  5. @Overrideパブリック文字列 getSha1() {
  6. DigestUtils.sha1Hex(SCRIPT)を返します
  7. }
  8.  
  9. @Override public Class<String> getResultType() { return String.class;
  10. }
  11.  
  12. @Override public String getScriptAsString() { return SCRIPT;
  13. }
  14. }

RedisTemplate オブジェクトを通じてスクリプトを実行します。

  1. パブリックブール値 rateLimit(文字列キー, int  最大 intレート){
  2. リスト<文字列> keyList = 新しいArrayList<>(1);
  3. keyList.add (キー);
  4. 戻る  "1" .equals(文字列RedisTemplate
  5. 実行(新しい RedisReteLimitScript()、keyList、 Integer .toString( max )、 Integer .toString(rate)、
  6. Long.toString(System.currentTimeMillis())));
  7. }

rateLimit メソッドに渡されるキーは、現在の制限インターフェースの ID、max はトークン バケットの最大サイズ、rate は 1 秒あたりに復元されるトークンの数、返されるブール値は、リクエストが現在の制限を超えたかどうかを示します。 Redis スクリプトの電流制限が正しく機能するかどうかをテストするために、それをテストするためのユニット テストを作成します。

  1. オートワイヤード
  2. プライベート RedisManager redisManager;
  3.  
  4. @テスト
  5. パブリックvoid rateLimitTest() は InterruptedException をスローします {
  6. 文字列キー= "test_rateLimit_key" ;
  7. 整数 最大= 10; //トークンバケットサイズ
  8. 整数レート = 10; // 1秒あたりのトークン回復率
  9. AtomicInteger 成功数 = 新しい AtomicInteger(0);
  10. エグゼキュータ executor = Executors.newFixedThreadPool(10);
  11. カウントダウンラッチ countDownLatch = 新しい CountDownLatch(30);
  12. ( int i = 0; i < 30; i++) {
  13. executor.execute (() -> {
  14. ブール値 isAllow = redisManager.rateLimit(キー最大値、レート );
  15. 許可する場合
  16. 成功回数。addAndGet(1);
  17. }
  18. ログ情報(Boolean.toString(isAllow));
  19. countDownLatch.countDown();
  20. });
  21. }
  22. countDownLatch.await();
  23. log.info( "リクエストが{}回成功しました" , successCount.get());
  24. }

トークン バケット サイズを 10 に設定し、1 秒あたり 10 個のトークンを復元し、10 個のスレッドを開始して短時間で 30 件のリクエストを実行し、現在の制限クエリごとに結果を出力します。ログ出力:

  1. [19:12:50,283]本当だ  
  2. [19:12:50,284]本当だ  
  3. [19:12:50,284]本当だ  
  4. [19:12:50,291]本当だ  
  5. [19:12:50,291]本当だ  
  6. [19:12:50,291]本当だ  
  7. [19:12:50,297]本当だ  
  8. [19:12:50,297]本当だ  
  9. [19:12:50,298]本当だ  
  10. [19:12:50,305]本当だ  
  11. [19:12:50,305]間違い  
  12. [19:12:50,305]本当だ  
  13. [19:12:50,312]間違い  
  14. [19:12:50,312]間違い  
  15. [19:12:50,312]間違い  
  16. [19:12:50,319]間違い  
  17. [19:12:50,319]間違い  
  18. [19:12:50,319]間違い  
  19. [19:12:50,325]間違い  
  20. [19:12:50,325]間違い  
  21. [19:12:50,326]間違い  
  22. [19:12:50,380]間違いです  
  23. [19:12:50,380]間違いです  
  24. [19:12:50,380]間違いです  
  25. [19:12:50,387]間違いです  
  26. [19:12:50,387]間違いです  
  27. [19:12:50,387]間違いです  
  28. [19:12:50,392]間違いです  
  29. [19:12:50,392]間違いです  
  30. [19:12:50,392]間違いです  
  31. [19:12:50,393] リクエストは11回成功しました

0.1 秒以内に行われた 30 件のリクエストのうち、最初の 10 個のトークンと時間の経過とともに復元された 1 個のトークンを除き、トークンを取得できなかった残りの 19 件のリクエストはすべて false を返したことがわかります。現在の制限スクリプトは、制限を超えたリクエストを正しく識別しました。このとき、ビジネス側は、システムがビジー状態である、またはインターフェース要求が頻繁すぎるなどのプロンプトを直接返すことができます。

3. 開発中に遭遇した問題

1) Lua変数フォーマット

Lua の文字列と数値は、tonumber() と tostring() を介して変換する必要があります。

2) Redis入力

pexpire などの Redis コマンドは小数をサポートしていませんが、Lua の Number 型は小数を格納できます。したがって、Number 型を Redis に渡すときは、小数点によるコマンドの失敗を回避するために、math.ceil() などのメソッドを使用して変換するのが最適です。

3) 時間コマンド

Redis はスクリプトとパラメータをクラスター内のすべてのノードにコピーするため、不確実性のあるコマンドの後に書き込みコマンドを実行することはできません。したがって、リクエスト時に時間を渡すことしかできず、Redis Time コマンドを使用して時間を取得することはできません。

バージョン 3.2 以降の Redis スクリプトは redis.replicate_commands() をサポートしており、代わりに Time コマンドを使用して現在の時刻を取得できます。

4) 潜在的な危険

この Lua スクリプトはリクエスト中に渡された時間に基づいて計算を実行するため、分散ノードで取得された時間が同期されていることを確認することが重要です。時刻が同期されていない場合、電流制限機能が正しく動作しません。

著者について

オレンジ ファイナンシャル イノベーション センターの開発エンジニアである Duan Ran 氏は、現在、同社のプラットフォーム構築とメディア機能の集約を担当しています。

<<:  20 年経った今でも、Salesforce は SaaS の王者ですが、私たちはどうでしょうか?

>>:  注目すべき 5 つの重要なマルチクラウド トレンド

推薦する

MetOウェブサイト構築システムのSEO効果の分析

月収10万元の起業の夢を実現するミニプログラム起業支援プランMetInfoの企業サイト構築システムは...

生成AIシステムの導入により、企業のクラウドアーキテクチャが変化する可能性がある

データの可用性とセキュリティから大規模な言語モデルとその選択と監視まで、企業による生成 AI の導入...

簡単な説明: モバイルサイトに最も適した 5 つの業界

少し前に、PC ベースの Web サイト用のモバイル サイトを構築しました。わずか 1 週間で、We...

アマゾンウェブサービス(AWS)の収益は2020年に454億ドルに達した

アマゾンは2月2日、2020年12月31日までの第4四半期の決算を発表した。2020年第4四半期のア...

いかにソフトな製品を効率よく継続的に生み出すかが第一条件

インターネットには、初心者のウェブマスターが分析し、高品質のオリジナルコンテンツを作成するためのさま...

高品質の外部リンクを作成するには、まず外部リンクプラットフォームの本質的な価値を尊重する必要があります。

私は沈心玲という名の台湾の少女の物語についての記事を読みました。彼女は12歳の時にすでにウェブサイト...

Google 画像 SEO のヒント

今は万全の態勢を整える時代です。検索エンジン最適化は、Web ページから、doc ドキュメントの最適...

マルチクラウド管理を克服する6つのツール

クラウド コンピューティングが登場すると、企業のプログラマーと運用チームの生活は劇的に変化しました。...

ウェブサイトのページと機能の主な目的:ユーザーの心理的ニーズを調整する(パート 1)

ユーザーはなぜクリックしてウェブサイトに入るのでしょうか?検索エンジンはなぜインターネット環境を絶え...

Baidu はオリジナルコンテンツについてどう考えていますか?

「Baiduオリジナルコンテンツ」は、ウェブマスターの間で常に話題になっています。Baiduのアルゴ...

VULTRはどうですか?南米ブラジルデータセンターのクラウドサーバーの簡易評価

Vultr は南米ブラジルに独自のデータセンターを持ち、ブラジルのクラウド サーバー サービスを提供...

約2年間、地域ポータルサイトに携わってきた感想

ウェブサイトの起源私は会社員で、現在30歳です。2012年以前は、いつもオンラインゲームをするのが好...

インターネットプロジェクト運営:生きたければまず死ななければならない

この話題は少々極端で恣意的ですが、全く不合理というわけではありません。ジャック・マーがインターネット...

貴州省のビッグデータとクラウドコンピューティングの学部卒業生第1期生が卒業、就職の心配なし

[[235895]] 「入社2日目から業務に慣れ、開発業務に取り組み始めました。」貴州大学省実証ソフ...

OpenStack仮想マシンにデータボリュームをマウントするプロセスの分析

1 OpenStackについてこれは、パブリック クラウドとプライベート クラウドの展開と管理を実装...