1.電流制限の基本的な知識の紹介
なぜ電流を制限したいのですか、私はこれ以上言う必要はないと思います。
-
たとえば、週末に夕食にレストランに行くのですが、人が多すぎます。フロントに行って番号を取得し、番号が届くまで待ってからレストランで食事をすることができます。ホテルに現在の制限がない場合はどうなりますか?食事が到着すると、人が殺到し、人の流れに対応できないため、事故が発生しやすくなります(レストランは人でいっぱいで行き方がありません。ホテルのスタッフが倒れ、彼らはできませんでした。 tそれを処理する)
-
同じことがコードの世界にも当てはまります。サーバーが処理できるリクエストの数は限られています。リクエストの量が特に多い場合は、フローを制限する必要があります(リクエストを待機させるか、リクエストをスローします)。
制限
コードの世界では、電流制限のための2つの一般的なアルゴリズムがあります。
-
トークンバケットアルゴリズム
-
漏出バケットアルゴリズム
1.1リーキーバケットアルゴリズムとは
たとえば、今はバケツがあり、緑色のバケツは水を保持できる容量です。保持できる容量を超えてからバケツに水を注ぐと、オーバーフローします(現在の制限)。
バケツ
現在私たちが知ることができるのは:
-
バケットの容量は固定されています(写真の緑色のものです)
-
バケットの容量を超えると、オーバーフローします(待機するか直接破棄します)
OK、今度はバケツに穴を掘って、水が穴から流れ出るようにします。
穴を掘ると穴から水が出てきました
バケツの開口部のサイズは固定されているため、開口部から流出する水の速度も固定されています。
したがって、要約すると、アルゴリズムに必要なパラメータは2つだけです。
-
バケット容量
-
漏れ率
漏出バケットアルゴリズムには2つの実装があります。
-
突然の流れの状況は許されません:水の流入の速度が水の流出の速度よりも大きい場合、余分な水は直接廃棄されます。たとえば、私のバケツは100Lを保持できますが、私のバケツの水出力速度は10L / sです。このとき、100L / sの水が入ってくると、バケツに10Lの水しか入れず、残りは制限されます。(リクエストの速度を制限します)
-
トラフィックの特定のバーストを許可します。バケツは100Lを保持できます。現在バケツが空の場合、100Lの水が即座にバケツに入ることができます。私は10L / sの速度で水を流出させます。それでも100Lの水が入ってくる場合、私は流れを制限することしかできません。
上記の分析の後、次のことがわかります。
漏出バケットアルゴリズムは、ネットワーク上のバーストトラフィックをスムーズにすることができます(漏水率が固定されているため)
1.2トークンバケットアルゴリズムとは
今、私は別のバケツを持っています。このバケツは水ではなく、トークンに使用されます:
バケツはトークンでいっぱいです
トークンは特定の速度でバケットにスローされます。たとえば、1秒あたり10個のトークンをバケットにスローします。
トークンを一定の割合でバケットに投げます
バケットに保存できるトークンの数には上限があります。たとえば、私のバケットは最大で1000個のトークンしか保持できません。
リクエストが届くたびに、バケットに移動してトークンを取得します
-
たとえば、この2番目に1001のリクエストがある場合、バケットに移動して1001のトークンを取得します。現時点では、次の2つの状況が考えられます。
-
バケットには1001トークンがなく、1000トークンしかないため、トークンを取得しないリクエストはブロック(待機中)のみが可能です。
-
バケットには1001個のトークンがあり、すべてのリクエストを実行できます。
-
トークンバケットアルゴリズムは、ネットワーク上のバーストトラフィックをサポートします
漏出バケットとトークンバケットの違い:上記の例からわかるように、漏出バケットは固定レートでのみリクエストを処理できますが、トークンバケットはバケット内のトークンの最大数でリクエストを処理できます。
二、RateLimiter使用
RateLimiterはGuavaの電流制限コンポーネントであり、私の側のシステムはこの電流制限コンポーネントを使用しているため、非常に便利です。
pom依存関係を導入します:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
RateLimiterはトークンバケットアルゴリズムに基づいており、APIは非常にシンプルです。次のデモを参照してください。
public static void main(String[] args) {
//线程池
ExecutorService exec = Executors.newCachedThreadPool();
//速率是每秒只有3个许可
final RateLimiter rateLimiter = RateLimiter.create(3.0);
for (int i = 0; i < 100; i++) {
final int no = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
//获取许可
rateLimiter.acquire();
System.out.println("Accessing: " + no + ",time:"
+ new SimpleDateFormat("yy-MM-dd HH:mm:ss").format(new Date()));
} catch (Exception e) {
e.printStackTrace();
}
}
};
//执行线程
exec.execute(runnable);
}
//退出线程池
exec.shutdown();
}
結果から、1秒間に実行できるのは3つだけであることがわかります。
1秒あたり3回実行
3つの分散電流制限
RateLimiterは、単一マシンの電流制限コンポーネントです。分散アプリケーションの場合、どうすればよいですか?
これは、Redis + Luaを使用して実現できます。おおよそのluaスクリプトコードは次のとおりです。
local key = "rate.limit:" .. KEYS[1] --限流KEY
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
return 0
else --请求数+1,并设置1秒过期
redis.call("INCRBY", key,"1")
redis.call("expire", key,"1")
return current + 1
end
Javaコードは次のとおりです。
public static boolean accquire() throws IOException, URISyntaxException {
Jedis jedis = new Jedis("127.0.0.1");
File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua");
String luaScript = FileUtils.readFileToString(luaFile);
String key = "ip:" + System.currentTimeMillis()/1000; // 当前秒
String limit = "5"; // 最大限制
List<String> keys = new ArrayList<String>();
keys.add(key);
List<String> args = new ArrayList<String>();
args.add(limit);
Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执行lua脚本,传入参数
return result == 1;
}
説明:
-
Javaコードは、キーと最大制限パラメーターをluaスクリプトに渡します
-
luaスクリプトを実行します(luaスクリプトは、現在のキーが最大制限を超えているかどうかを判別します)
-
を超える場合は、0(現在の制限)を返します。
-
超えていない場合は1を返します(プログラムは実行を継続します)
-