記事ディレクトリ
電流制限アルゴリズムとは何ですか
電流制限とは、システム サービスのセキュリティを確保するために、同時実行性が高く大量のトラフィック要求が発生した場合に、システムへの新しいトラフィックのアクセスを制限することを指します。
アプリケーションシナリオ
過剰な要求の理由:
- ホットスポット サービスによってもたらされるバースト リクエスト
- 呼び出し元のバグによって引き起こされるバーストリクエスト
- 悪意のある攻撃リクエスト
一般的に、seckill システムにはこのようなシナリオがあり、seckill を有効にすると、頻繁なアクセス要求によりサーバーに過大な負荷がかかり、クラッシュが発生する可能性があります。
- したがって、ウィンドウ電流制限アルゴリズムを使用して、各ユーザーが一定期間内に N 回訪問することを制限し、訪問回数を超えた場合、一時的にブラックリストに追加され、有効期限を設定できます。 Redisと組み合わせて実装可能
電流制限アルゴリズムの原理
固定ウィンドウ電流制限アルゴリズム
固定ウィンドウ: メッセージは時間間隔内で固定されるため、バースト トラフィックを防ぐことができません。
ウィンドウを超えるリクエストは破棄されます。
コード:
/**
* 固定窗口限流算法
* 阈值为 3
* 时间窗口为 1 s
*/
static final int THRESHOLD = 3;
static final long window = 1000;
static long startTime = System.currentTimeMillis();
static int count = 0;
static boolean fixedWindow(){
// 当前时间
long curTime = System.currentTimeMillis();
// 判断当前时间是否还在区间内
if(curTime - window > startTime){
// 更新开始时间
startTime = curTime;
// 重置计数
count = 0;
}
// 判断请求是否达到阈值
if(count < THRESHOLD){
count++;
return true;
}
// 请求已经达到阈值
return false;
}
/**
* 简单测试
*/
public static void main(String[] args) throws InterruptedException {
// 模拟 1秒 内无限请求
long startTime = System.currentTimeMillis();
int index = 0;
while(true){
long endTime = System.currentTimeMillis();
// 如果提前跳出
if(endTime - startTime >= 900){
break;
}
Thread.sleep(50);
System.out.println( index++ + (fixedWindow()? "允许请求" : "拒绝请求"));
}
// 1 秒后再请求
Thread.sleep(100);
System.out.println("一秒后" + (fixedWindow()? "允许请求" : "拒绝请求"));
}
試験結果:
0允许请求
1允许请求
2允许请求
3拒绝请求
4拒绝请求
5拒绝请求
6拒绝请求
7拒绝请求
8拒绝请求
9拒绝请求
10拒绝请求
11拒绝请求
12拒绝请求
13拒绝请求
14拒绝请求
一秒后允许请求
バーストトラフィックの状況:これも重大な問題です。つまり、1秒
現在前後に 3 つのメッセージが送信されており、明らかに規定に違反しています。
スライディングウィンドウ電流制限アルゴリズム
スライディング ウィンドウ アルゴリズムは固定ウィンドウ アルゴリズムに似ていますが、一度に 1 つのウィンドウのみが更新されます。ただし全部更新
、部分更新
問題は、現在の制限に達するとリクエストが激しく拒否され、ウィンドウのスライディング距離が減少することです。比較的遅い
より柔軟で、突然の渋滞の問題にも対応できます。
- 以下の図は、0.5 秒ごとにウィンドウを更新します。明らかに、
1 ~ 1.5
この期間内の 3 つのリクエストがしきい値を超えることはありませんが、同時トラフィックの1.9 ~ 2
更新のしきい値をわずかに超えるリクエストは、より細かく設定することで小さくすることができます。 - 固定ウィンドウと比較して、スライディング ウィンドウはバースト トラフィックの問題に対する圧力を明らかに軽減します。
グラフィック:
実装コード:
/**
* 滑动窗口限流算法
* 窗口初始为 10
* 时间窗口为 0.5 s
*/
// 窗口大小
static int size = 10;
// 窗口 大小:10
static boolean[] window = new boolean[size];
// 窗口指针索引
static int index = 0;
// 上一个开始时间
static long startTime = System.currentTimeMillis();
// 时间区间 1s
static long time = 1000;
// 当前窗口计数总和
static long counter = 0;
static boolean slideWindow(){
// 当前时间
long curTime = System.currentTimeMillis();
// 更新窗口(这个更新窗口应该是异步更新才对)
if(curTime - startTime > time){
// 更新开始时间
startTime = curTime;
// 更新窗口为可使用
window[index] = false;
counter--;
}
// 判断当前窗口是否可用
if(!window[index]){
// 记录窗口已被使用
window[index] = true;
// 移动到下一个节点 (超出大小求余)
index++;
index = index % size;
// 计数
counter++;
return true;
}
// 请求已经达到阈值
return false;
}
/**
* 简单测试
*/
public static void main(String[] args) throws InterruptedException {
// 模拟 1秒 内无限请求
long startTime = System.currentTimeMillis();
int index = 0;
while(true){
long endTime = System.currentTimeMillis();
// 提前跳出,以防最后一个方法调用的时候实际计时已经超过 1秒
if(endTime - startTime >= 900){
break;
}
// 睡眠 50ms
Thread.sleep(50);
System.out.println( index++ + (slideWindow()? "允许请求" : "拒绝请求"));
}
// 1 秒后再请求
Thread.sleep(100);
System.out.println("一秒后" + (slideWindow()? "允许请求" : "拒绝请求"));
}
テスト:
0允许请求
1允许请求
2允许请求
3允许请求
4允许请求
5允许请求
6允许请求
7允许请求
8允许请求
9允许请求
10拒绝请求
11拒绝请求
12拒绝请求
13拒绝请求
14拒绝请求
一秒后允许请求
リーキーバケット電流制限アルゴリズム
バケット リーク アルゴリズムは、前の 2 つのアルゴリズムとは異なり、全体的なレートを確保するためにスムーズな処理が必要です。
原理:バケツに水を入れることですが、この注水速度は任意
速くてもよく、バケツの容量を超えた場合は廃棄する必要があります。バケットの容量が不变
最大であるため、処理速度も固定
最大となり、全体の速度が保証されます
- 前の2つと比較すると、窓を使用しない可能性があり、割合がゼロになる可能性がありますが、バケツの水滴は確実に漏れるので、バケツの漏れは一定の割合です
バケットのサイズは時間のバーストに関係なく固定されており、リクエスト数が増加しても処理速度は変わらないため、バーストトラフィックの問題も解決できます。
グラフィック:
実装コード:
/**
* 桶漏限流算法
*/
// 桶的容量
static long capacity = 10;
// 当前水量
static long currWater = 0;
// 上次的结束时间(初始化)
static long startTime = System.currentTimeMillis();
// 出水速率 (1 滴/s)
static long outputRate = 1;
static boolean bucketLimit(){
// 获取当前时间
long curTime = System.currentTimeMillis();
// 更新出水量
if(curTime - startTime > 1000){
// 计算出水量 = (结束时间 - 开始时间) / 1000 * 出水速率
System.out.println(curTime - startTime);
long outputWater = (curTime - startTime) / 1000 * outputRate;
// 计算剩余水量 = 当前水量 - 出水量
currWater = Math.max(0, currWater - outputWater);
// 更新时间
startTime = curTime;
}
// 判断桶是否满了
if(currWater < capacity){
// 未满,请求通过,当前水量 + 1
currWater++;
return true;
}
// 桶满,拒绝请求
return false;
}
public static void main(String[] args) throws InterruptedException {
// 模拟 1秒 内无限请求
long startTime = System.currentTimeMillis();
int index = 0;
while(true){
long endTime = System.currentTimeMillis();
// 如果提前跳出
if(endTime - startTime >= 900){
break;
}
Thread.sleep(50);
System.out.println( index++ + (bucketLimit()? "允许请求" : "拒绝请求"));
}
// 1 秒后再请求
Thread.sleep(100);
System.out.println("一秒后" + (bucketLimit()? "允许请求" : "拒绝请求"));
}
試験結果:
0允许请求
1允许请求
2允许请求
3允许请求
4允许请求
5允许请求
6允许请求
7允许请求
8允许请求
9允许请求
10拒绝请求
11拒绝请求
12拒绝请求
13拒绝请求
14拒绝请求
15拒绝请求
1047
一秒后允许请求
トークンバケット電流制限アルゴリズム
トークンバケットアルゴリズムとバケットリーキーアルゴリズムは両方とも同じ主人公を持っています桶
が、バケットに含まれるものは令牌
バケットリークの逆であり、トークンが発行される場所です。
- 一定のレートでトークンを生成する
- 処理速度は任意
トークンバケットには一定数のトークンを格納できますが、この数を超えた場合も同様にトークンを破棄する必要があります。リクエストが来た際に、バケットからトークンを取得できるかどうかを判断する必要があります。取得できるということは、アクセスできることを意味します。
バーストトラフィックの問題も解決でき、バーストトラフィックが来た場合、トークンバケットの数に応じたリクエストしか処理できなくなり、対応するトークンがないと余分なリクエストを処理できなくなります。
グラフィック:
実装コード:
/**
* 令牌桶限流算法
*/
// 令牌桶的容量
static long capacity = 10;
// 当前令牌数
static long tokens = 10;
// 上次的结束时间(初始化)
static long startTime = System.currentTimeMillis();
// 产生令牌速率 (1 个/s)
static long inputRate = 1;
static boolean tokenBucketLimit(){
// 获取当前时间
long curTime = System.currentTimeMillis();
// 更新令牌数
if(curTime - startTime > 1000){
// 计算出水量 = (结束时间 - 开始时间) / 1000 * 出水速率
System.out.println(curTime - startTime);
long token = (curTime - startTime) / 1000 * inputRate;
// 计算剩余水量 = 当前水量 - 出水量
tokens = Math.min(capacity, tokens + token);
// 更新时间
startTime = curTime;
}
// 判断令牌桶中是否有令牌
if(tokens > 0){
// 颁发令牌,请求通过
tokens--;
return true;
}
// 没有令牌,拒绝请求
return false;
}
/**
* 简单测试
*/
public static void main(String[] args) throws InterruptedException {
// 模拟 1秒 内无限请求
long startTime = System.currentTimeMillis();
int index = 0;
while(true){
long endTime = System.currentTimeMillis();
// 如果提前跳出
if(endTime - startTime >= 900){
break;
}
Thread.sleep(50);
System.out.println( index++ + (tokenBucketLimit()? "允许请求" : "拒绝请求"));
}
// 1 秒后再请求
Thread.sleep(100);
System.out.println("一秒后" + (tokenBucketLimit()? "允许请求" : "拒绝请求"));
}
試験結果:
0允许请求
1允许请求
2允许请求
3允许请求
4允许请求
5允许请求
6允许请求
7允许请求
8允许请求
9允许请求
10拒绝请求
11拒绝请求
12拒绝请求
13拒绝请求
14拒绝请求
15拒绝请求
1018
一秒后允许请求