分布式限流のRedis+Lua実現
【転載は出典を明記してください】:https://segmentfault.com/a/1190000022538822
分散型ストリーム制限は、ストリーム制限サービスを原子化することが最も重要であり、ソリューションはredis+luaまたはnginx+lua技術を用いて実現することができ、この2つの技術によって高同時性と高性能を実現することができる.
まずredis+luaを用いてタイムウィンドウ内のあるインタフェースのリクエスト数制限ストリームを実現し,この機能を実現すると制限ストリームの総同時/リクエスト数と制限総リソース数に改造できる.Lua自体はプログラミング言語であり,複雑なトークンバケツやドレインバケツアルゴリズムを実現するためにも使用できる.操作は1つのluaスクリプト(原子操作に相当)であり、Redisは単一スレッドモデルであるため、スレッドは安全である.
Redisトランザクションに比べて、Luaスクリプトには次のような利点があります.ネットワークオーバーヘッドを削減:Luaのコードを使用しないでRedisに複数回の要求を送信する必要があり、スクリプトは1回でネットワーク伝送を削減することができる. 原子操作:Redisはスクリプト全体を原子として実行し、同時実行を心配する必要はなく、トランザクションも必要ありません. 多重化:スクリプトはRedisに永続的に保存され、他のクライアントは引き続き使用できます.
SpringBootプロジェクトを使用して説明します.
Luaスクリプトの準備
req_ratelimit.lua KEYS[1]を介して受信したkeyパラメータ を取得する ARGV[1]を介して受信limitパラメータ を取得する. redis.callメソッドは、キャッシュからgetとkeyに関連する値をgetし、nilの場合は0 を返します.は、次に、キャッシュに記録する値が制限サイズより大きいか否かを判断し、この制限ストリームを超えると0 に戻る.が超えていない場合、keyのキャッシュ値+1は、有効期限が1秒後に設定され、キャッシュ値+1 に戻る.
Javaプロジェクトの準備
pom.xml加入
Redis構成
げんりゅうちゅうしゃく
注記の目的は、ストリーム制限が必要な方法で使用することです.
luaファイル構成およびRedisTemplate構成
せいぎょそう
プロジェクトのテストを開始
url
ここでは簡単なプレゼンテーションのためにRuntimeExceptionを直接投げて、実際にRateLimitExceptionのような周波数制限の異常を上層部で直接処理し、ユーザーに友好的に返すことができます.
【転載は出典を明記してください】:https://segmentfault.com/a/1190000022538822
分散型ストリーム制限は、ストリーム制限サービスを原子化することが最も重要であり、ソリューションはredis+luaまたはnginx+lua技術を用いて実現することができ、この2つの技術によって高同時性と高性能を実現することができる.
まずredis+luaを用いてタイムウィンドウ内のあるインタフェースのリクエスト数制限ストリームを実現し,この機能を実現すると制限ストリームの総同時/リクエスト数と制限総リソース数に改造できる.Lua自体はプログラミング言語であり,複雑なトークンバケツやドレインバケツアルゴリズムを実現するためにも使用できる.操作は1つのluaスクリプト(原子操作に相当)であり、Redisは単一スレッドモデルであるため、スレッドは安全である.
Redisトランザクションに比べて、Luaスクリプトには次のような利点があります.
SpringBootプロジェクトを使用して説明します.
Luaスクリプトの準備
req_ratelimit.lua
local key = "req.rate.limit:" .. KEYS[1] -- KEY
local limitCount = tonumber(ARGV[1]) --
local limitTime = tonumber(ARGV[2]) --
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limitCount then --
return 0
else -- +1, 1
redis.call("INCRBY", key,"1")
redis.call("expire", key,limitTime)
return current + 1
end
Javaプロジェクトの準備
pom.xml加入
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-aop
org.apache.commons
commons-lang3
org.springframework.boot
spring-boot-starter-test
Redis構成
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
# ( )
spring.redis.jedis.pool.max-active=20
# ( )
spring.redis.jedis.pool.max-wait=-1
#
spring.redis.jedis.pool.max-idle=10
#
spring.redis.jedis.pool.min-idle=0
# ( )
spring.redis.timeout=2000
げんりゅうちゅうしゃく
注記の目的は、ストリーム制限が必要な方法で使用することです.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
/**
*
* @return
*/
String key() default "";
/**
*
* @return
*/
int time();
/**
*
* @return
*/
int count();
}
luaファイル構成およびRedisTemplate構成
@Aspect
@Configuration
@Slf4j
public class RateLimiterAspect {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private DefaultRedisScript redisScript;
@Around("execution(* com.sunlands.zlcx.datafix.web ..*(..) )")
public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Class> targetClass = method.getDeclaringClass();
RateLimiter rateLimit = method.getAnnotation(RateLimiter.class);
if (rateLimit != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ipAddress = getIpAddr(request);
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(ipAddress).append("-")
.append(targetClass.getName()).append("- ")
.append(method.getName()).append("-")
.append(rateLimit.key());
List keys = Collections.singletonList(stringBuffer.toString());
Number number = redisTemplate.execute(redisScript, keys, rateLimit.count(), rateLimit.time());
if (number != null && number.intValue() != 0 && number.intValue() <= rateLimit.count()) {
log.info(" :{} ", number.toString());
return joinPoint.proceed();
}
} else {
return joinPoint.proceed();
}
throw new RuntimeException(" ");
}
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
// , IP IP, IP ','
if (ipAddress != null && ipAddress.length() > 15) {
// "***.***.***.***".length()= 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
}
せいぎょそう
@RestController
@Slf4j
@RequestMapping("limit")
public class RateLimiterController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping(value = "/test")
@RateLimiter(key = "test", time = 10, count = 1)
public ResponseEntity
プロジェクトのテストを開始
url
http://127.0.0.1:8090/limit/test
へのアクセスを継続します.効果は次のとおりです.ここでは簡単なプレゼンテーションのためにRuntimeExceptionを直接投げて、実際にRateLimitExceptionのような周波数制限の異常を上層部で直接処理し、ユーザーに友好的に返すことができます.
【転載は出典を明記してください】:https://segmentfault.com/a/1190000022538822