Javaはアーキテクチャのいくつかの側面、インタビューデータ(マイクロサービス、クラスタリング、分散、ミドルウェアなど)をコンパイルし、プログラマ番号[何か]の公共パートナーを心配する必要はほとんどありません、ないルーチン自己を受けていません
まず、電流制限は何ですか?なぜ制限?
あなたはなぜサークルでラウンド行く長い行列を入れて、ラインアップする必要が地下鉄の駅のようなものの中に、ある地下鉄の帝国を行っていない場合は分からないのですか?答えは順序であります限流
!トリップ地下鉄の容量が限られているので、あまりにも多くの人々を絞るための外観は、プラットフォーム満員電車の過負荷が発生します、特定のセキュリティリスクがあります。リクエストを処理する能力が限られている。同様に、私たちのプログラムは同じで、もう一度その限度を超えて処理する要求に崩壊します。最悪の場合は表示されませんをクラッシュさせるために、それはあなたが停止する時間を遅らせることができます。
オンラインの巨大なインターネット企業の交通システムは、システムの流量が流れのスワップ一部を拒否し、一定のしきい値に達したときにシステムが巨大なトラフィックに圧倒されていないことを確実にするためになり、特に様々なスパイクプロモーション活動として、ピーク時のトラフィックを評価するでしょうが原因。
電流制限は、システムが利用できない(この時間はミリ秒である)、我々は一般に、システム容量の測定を短時間でユーザーにつながる毎秒であるQPS
またはTPS
、システムは、毎秒1,000ことを理論的に1秒と仮定すると、流れ閾値最初の要求が入ってきたときに、その要求が制限されます1001があります。
第二に、電流制限スキーム
1、カウンタ
Javaベースの内部カウンタも原子することができAtomicInteger
、Semaphore
セマフォは、単純な流量制限を行います。
// 限流的个数
private int maxCount = 10;
// 指定的时间内
private long interval = 60;
// 原子类计数器
private AtomicInteger atomicInteger = new AtomicInteger(0);
// 起始时间
private long startTime = System.currentTimeMillis();
public boolean limit(int maxCount, int interval) {
atomicInteger.addAndGet(1);
if (atomicInteger.get() == 1) {
startTime = System.currentTimeMillis();
atomicInteger.addAndGet(1);
return true;
}
// 超过了间隔时间,直接重新开始计数
if (System.currentTimeMillis() - startTime > interval * 1000) {
startTime = System.currentTimeMillis();
atomicInteger.set(1);
return true;
}
// 还在间隔时间内,check有没有超过限流的个数
if (atomicInteger.get() > maxCount) {
return false;
}
return true;
}
复制代码
2、リーキーバケットアルゴリズム
リーキーバケットアルゴリズムのアイデアは非常に単純な、私たちは水に例えている请求
になぞらえ、リーキーバケット系统处理能力极限
リーキーバケットによる、流出の割合が少ない流入の割合より大きい場合、一定の割合でバケツから水を排水、バケツに排水する水限られた容量、電流制限を達成するために、(要求を拒絶)オーバーフローに直接水をたどります。
3、トークンバケットアルゴリズム
原理トークンバケットアルゴリズムは、我々は医師の診察をする病院として理解することができ、比較的簡単です、あなただけの医療相談の後に番号を取得することができます。
システムは、トークン(維持token
バケット(INトークンに一定の速度で、)バケットtoken
)要求がある場合、次に処理されることを望まになり、最初の取得する必要がトークンバケット(token
)場合ノートークンバケット(token
)要求はサービスを拒否されます、が望ましいです。制限要求を達成するために、ドラムの容量、発行されたトークンの割合を制御するトークンバケットアルゴリズム。
+テイク4つのRedis
多くの学生が知らないLua
何ですか?理解し、Lua
スクリプトとMySQL
データベースのストアドプロシージャが非常に似ている、彼らは一連のコマンドを実行し、いずれかのすべてのコマンドを実行し、すべて成功するか、失敗し、原子性を達成するためです。また、することができますLua
スクリプトは、いくつかのビジネス・ロジックのコードブロックを有するものとして理解しました。
そしてLua
、それ自体が、プログラミング言語でありredis
、公式には直接、適切な電流制限を提供しなかったAPI
が、彼がサポートするLua
スクリプトの機能を、あなたは複雑なトークンバケットを実装するために使用することができたり、リーキーバケットアルゴリズムは制限メインを達成するための分散型システムであり、いずれかの方法。
比較するとRedis
総務、Lua脚本
利点:
- 使い方:ネットワークのオーバーヘッドを削減
Lua
せずにスクリプトをRedis
実行したら、リクエスト数回、ネットワークトラフィックを削減 - アトミック操作:
Redis
全体のLua
コマンドとしてスクリプト、原子、同時実行を気にせず - 多重化:
Lua
スクリプトを実行したら、永久に保存されるRedis
他のクライアント,,再利用可能に
Lua
スクリプトロジックは、実質的に、次のとおりです。
-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local key = KEYS[1]
-- 获取调用脚本时传入的第一个参数值(限流大小)
local limit = tonumber(ARGV[1])
-- 获取当前流量大小
local curentLimit = tonumber(redis.call('get', key) or "0")
-- 是否超出限流
if curentLimit + 1 > limit then
-- 返回(拒绝)
return 0
else
-- 没有超出 value + 1
redis.call("INCRBY", key, 1)
-- 设置过期时间
redis.call("EXPIRE", key, 2)
-- 返回(放行)
return 1
end
复制代码
- することにより
KEYS[1]
、着信主要なパラメータを取得します - することにより
ARGV[1]
、着信取得limit
パラメータを redis.call
この方法は、キャッシュからget
とkey
あれば、関連する値null
が0を返します。- 次いで超えた場合、キャッシュに記録された値は、サイズ制限よりも大きくなるか否かが判断される戻り、流れが制限されていることを示し、0
- 超えていない場合は、1のキー値のキャッシュ、および第二時間後に期限切れに設定され、キャッシュされた値を返します1
このアプローチは、このペーパー・プログラムに推奨され、私たちは後ろに精巧な具体的な実現を行います。
図5に示すように、ゲートウェイ制限層
制限は、しばしばのような、この層ゲートウェイで行うNginx
、Openresty
、kong
、zuul
、Spring Cloud Gateway
、など、およびなどspring cloud - gateway
の原則基礎となる実装を制限するゲートウェイをベースにRedis + Lua
内蔵されたことで、Lua
スクリプトを制限する方法。
三、Redisの+ Luaは達成制限します
私たちの下には自定义注解
、aop
、Redis + Lua
電流制限のために、手順がより見えると寛容白迅速ここ始める少し長いったらしい、経験豊富なベテランを作るために、詳細に説明されるだろう。
1、環境の準備
springboot
:アドレスを作成するプロジェクトstart.spring.io、非常に便利で実用的なツールを。
2、依存関係の導入
POMファイルには、次の依存関係を追加し、それがより重要であるspring-boot-starter-data-redis
とspring-boot-starter-aop
。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
复制代码
3、設定application.properties
ではapplication.properties
良い構築するために、事前に設定ファイルredis
サービスのアドレスとポートを。
spring.redis.host=127.0.0.1
spring.redis.port=6379
复制代码
4、構成例RedisTemplate
@Configuration
public class RedisLimiterHelper {
@Bean
public RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
复制代码
制限タイプの列挙クラス
/**
* @author fu
* @description 限流类型
* @date 2020/4/8 13:47
*/
public enum LimitType {
/**
* 自定义key
*/
CUSTOMER,
/**
* 请求者IP
*/
IP;
}
复制代码
5、カスタム注釈
私たちは、カスタマイズ@Limit
型へのコメントElementType.METHOD
方法に作用しています。
period
要求の制限時間が、あることを示しcount
ショーはperiod
、リリース期間が要求の数を許可していること。limitType
よると、電流制限の代表的な種類请求的IP
、 自定义key
場合合格していないlimitType
デフォルトのキーのデフォルトの方法として、属性名を。
/**
* @author fu
* @description 自定义限流注解
* @date 2020/4/8 13:15
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {
/**
* 名字
*/
String name() default "";
/**
* key
*/
String key() default "";
/**
* Key的前缀
*/
String prefix() default "";
/**
* 给定的时间范围 单位(秒)
*/
int period();
/**
* 一定时间内最多访问次数
*/
int count();
/**
* 限流的类型(用户自定义key 或者 请求ip)
*/
LimitType limitType() default LimitType.CUSTOMER;
}
复制代码
図6に示すように、切断されたコードを達成するために
/**
* @author fu
* @description 限流切面实现
* @date 2020/4/8 13:04
*/
@Aspect
@Configuration
public class LimitInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);
private static final String UNKNOWN = "unknown";
private final RedisTemplate<String, Serializable> limitRedisTemplate;
@Autowired
public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {
this.limitRedisTemplate = limitRedisTemplate;
}
/**
* @param pjp
* @author fu
* @description 切面
* @date 2020/4/8 13:04
*/
@Around("execution(public * *(..)) && @annotation(com.xiaofu.limit.api.Limit)")
public Object interceptor(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Limit limitAnnotation = method.getAnnotation(Limit.class);
LimitType limitType = limitAnnotation.limitType();
String name = limitAnnotation.name();
String key;
int limitPeriod = limitAnnotation.period();
int limitCount = limitAnnotation.count();
/**
* 根据限流类型获取不同的key ,如果不传我们会以方法名作为key
*/
switch (limitType) {
case IP:
key = getIpAddress();
break;
case CUSTOMER:
key = limitAnnotation.key();
break;
default:
key = StringUtils.upperCase(method.getName());
}
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
try {
String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
logger.info("Access try count is {} for name={} and key = {}", count, name, key);
if (count != null && count.intValue() <= limitCount) {
return pjp.proceed();
} else {
throw new RuntimeException("You have been dragged into the blacklist");
}
} catch (Throwable e) {
if (e instanceof RuntimeException) {
throw new RuntimeException(e.getLocalizedMessage());
}
throw new RuntimeException("server exception");
}
}
/**
* @author fu
* @description 编写 redis Lua 限流脚本
* @date 2020/4/8 13:24
*/
public String buildLuaScript() {
StringBuilder lua = new StringBuilder();
lua.append("local c");
lua.append("\nc = redis.call('get',KEYS[1])");
// 调用不超过最大值,则直接返回
lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
lua.append("\nreturn c;");
lua.append("\nend");
// 执行计算器自加
lua.append("\nc = redis.call('incr',KEYS[1])");
lua.append("\nif tonumber(c) == 1 then");
// 从第一次调用开始限流,设置对应键值的过期
lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
lua.append("\nend");
lua.append("\nreturn c;");
return lua.toString();
}
/**
* @author fu
* @description 获取id地址
* @date 2020/4/8 13:24
*/
public String getIpAddress() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
复制代码
達成するために図7に示すように、制御層
我々がします@Limit
制限する必要がある、下に提供された方法は、我々が与えるインタフェースメソッドの役割についてのコメント@Limit
で注釈を10秒
のみ解放3个
要求、ここで少し視覚的でAtomicInteger
計数が。
/**
* @Author: fu
* @Description:
*/
@RestController
public class LimiterController {
private static final AtomicInteger ATOMIC_INTEGER_1 = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_2 = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_3 = new AtomicInteger();
/**
* @author fu
* @description
* @date 2020/4/8 13:42
*/
@Limit(key = "limitTest", period = 10, count = 3)
@GetMapping("/limitTest1")
public int testLimiter1() {
return ATOMIC_INTEGER_1.incrementAndGet();
}
/**
* @author fu
* @description
* @date 2020/4/8 13:42
*/
@Limit(key = "customer_limit_test", period = 10, count = 3, limitType = LimitType.CUSTOMER)
@GetMapping("/limitTest2")
public int testLimiter2() {
return ATOMIC_INTEGER_2.incrementAndGet();
}
/**
* @author fu
* @description
* @date 2020/4/8 13:42
*/
@Limit(key = "ip_limit_test", period = 10, count = 3, limitType = LimitType.IP)
@GetMapping("/limitTest3")
public int testLimiter3() {
return ATOMIC_INTEGER_3.incrementAndGet();
}
}
复制代码
8、テスト
テストは予想:3つの連続した要求が成功することができ、第四の要求は拒否されます。私たちが望む効果、リクエストアドレスではありませんを見て次へ:http://127.0.0.1:8080/limitTest1
で、postman
テスト、まったくありませんpostman
ブラウザに直接取り付けたとしてもURL。
概要
よりspringboot + aop + Lua
制限の実装は、誰もが制限されたものを知っているように設計された、比較的簡単なのですか?これは何かであることを知っているインタビューを制限する簡単な電流を作る方法。現在のために上記のプログラムのいくつかは、しかし、どのような選挙を限定するものではなく、特定のビジネスシナリオではなく、使用のためにけれども。
小型のメリット:
一部のオタクは、コースの賃金、BOO〜に着く無料の小さな仲間のために。返事はありません[社会的関心のオタク ]自己コレクション