分散環境では、Redisに基づいてRestful APIインタフェースのストリーム制限を実現
33598 ワード
創作は容易ではありません。もしこの文章があなたに役に立つと思ったら、皆さんの支持を歓迎します。あなたの支持は私の創作の最大の原動力です。
文書ディレクトリ
1はじめに
時代の発展に伴い、インターネットも大きく変化した.そのうちの重要な変化の1つは、 , ,
です.
分散アーキテクチャとは?簡単に言えば、以前の ( )
であり、分割によって 、 、
、この , ,
である.
本論文では,分散環境におけるインタフェース法のストリーム制限を最も効率的な方法で実現する.
2なぜApiインタフェースにストリームを制限するのか
様々なアプリケーションシーンを満たすために、インタフェースApiをストリーム制限しなければならない場合がある.例えば、メールサービスでは、サプライヤーが毎秒400件を超えないように要求する可能性があります.このアクセス量を超えると、要求がサプライヤーに拒否され、メールの送信が漏れてしまいます.
また、サードパーティのApiはアクセスを制限するため、1分間に20回しかインタフェースを要求できないように設定し、それを超えるとタイムアウトしたり、異常に応答したりします.
要するに、ストリーム制限は、多くのシーンで使われています.
3限流方式の選択
限流、方案はたくさんあって、具体的な典型的な限流方案の紹介、私のもう一つの博文を参考にしてください:分布式環境の下限流方案の思考
最終的に選択されたスキームはRedisに基づいて実現された制限流であり,このスキームは現在最も流行しており,最も効率的な方法でもある.
4 Redisベースのストリーム制限
4.1実現の構想
構想:Redis
のINCR
操作によってLimit限流を実現する
様々なアプリケーションシーンを満たすために、インタフェースApiをストリーム制限しなければならない場合がある.例えば、メールサービスでは、サプライヤーが毎秒400件を超えないように要求する可能性があります.このアクセス量を超えると、要求がサプライヤーに拒否され、メールの送信が漏れてしまいます.
また、サードパーティのApiはアクセスを制限するため、1分間に20回しかインタフェースを要求できないように設定し、それを超えるとタイムアウトしたり、異常に応答したりします.
要するに、ストリーム制限は、多くのシーンで使われています.
3限流方式の選択
限流、方案はたくさんあって、具体的な典型的な限流方案の紹介、私のもう一つの博文を参考にしてください:分布式環境の下限流方案の思考
最終的に選択されたスキームはRedisに基づいて実現された制限流であり,このスキームは現在最も流行しており,最も効率的な方法でもある.
4 Redisベースのストリーム制限
4.1実現の構想
構想:Redis
のINCR
操作によってLimit限流を実現する
4.1実現の構想
構想:
Redis
のINCR
操作によってLimit限流を実現する
、または key
であってもよい.戻り値が1の場合、呼び出しが開始されたばかりでkeyの有効期限が与えられ、戻り値が設定されたLimit制限ストリーム数より大きいかどうか、投げ異常またはブロック再試行より大きいかどうかを判断します.4.2実現限流
4.2.1制限フロー注釈の定義
コードの例は次のとおりです.
/**
*
* , ( , 500 )
*
*
* @author smilehappiness
* @Date 2020/7/5 20:05
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ApiLimit {
/**
* , 500
*/
int limitCounts() default 500;
/**
* @return
* @Description: , , 1
*/
int timeSecond() default 60;
}
4.2.2切面類を定義し、流れを遮断する方法
コードの例は次のとおりです.
package cn.smilehappiness.aspect;
import cn.smilehappiness.annotation.ApiLimit;
import cn.smilehappiness.model.SmsMessage;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* Api Limit
*
* @author smilehappiness
* @Date 2020/7/5 19:55
*/
@Aspect
@Component
public class ApiLimitAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* , redisTemplate
*/
private final RedisTemplate<String, Object> redisTemplate;
public ApiLimitAspect(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
*
* ( , )
*
*
* @param
* @return void
* @Date 2020/7/5 20:07
*/
@Pointcut("execution(* cn.smilehappiness..service..*Impl.*(..))")
private void myPointCut() {
}
/**
*
* ApiLimit ,Limit ( : )
*
*
* @param joinPoint
* @return void
* @Date 2020/7/5 20:10
*/
//@Around("myPointCut()")
public void requestLimit(ProceedingJoinPoint joinPoint) throws Throwable {
ApiLimit apiLimit = this.getAnnotation(joinPoint);
//
if (apiLimit != null) {
dealLimit(apiLimit, joinPoint, false);
}
}
/**
*
* Limit , , 10 , ,
*
*
* @param apiLimit
* @param joinPoint
* @param flag , ,
* @return void
* @Date 2020/7/5 20:30
*/
private void dealLimit(ApiLimit apiLimit, ProceedingJoinPoint joinPoint, Boolean flag) throws Throwable {
String msgKey = checkParam(apiLimit, joinPoint);
// , key
String cacheKey = "smsService:sendLimit:" + msgKey;
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
long methodCounts = valueOperations.increment(cacheKey, 1);
// key , 0 , count 1 ,
if (methodCounts == 1) {
redisTemplate.expire(cacheKey, apiLimit.timeSecond(), TimeUnit.SECONDS);
}
// redis count , 10
if (methodCounts > apiLimit.limitCounts()) {
if (!flag) {
// 10 ,
Thread.sleep(10 * 1000);
logger.warn(" 10 , ...");
// ,
dealLimit(apiLimit, joinPoint, true);
} else {
// , 10 , , , , ,
throw new RuntimeException(" Api , 30 !");
}
} else {
//
joinPoint.proceed();
}
}
/**
*
* ,
*
*
* @param apiLimit
* @param joinPoint
* @return java.lang.String
* @Date 2020/7/5 20:40
*/
private String checkParam(ApiLimit apiLimit, ProceedingJoinPoint joinPoint) {
if (apiLimit == null) {
throw new RuntimeException(" dealLimit !");
}
// , key
Object[] args = joinPoint.getArgs();
if (args == null) {
throw new RuntimeException(" !");
}
SmsMessage smsMessage = null;
if (args[0] instanceof SmsMessage) {
smsMessage = (SmsMessage) args[0];
}
if (smsMessage == null) {
throw new RuntimeException("HttpServletRequest , !");
}
// HttpRequest
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new RuntimeException("HttpServletRequest ,attributes !");
}
HttpServletRequest request = attributes.getRequest();
// request
if (request == null) {
throw new RuntimeException("HttpServletRequest , !");
}
String ip = request.getRemoteAddr();
String uri = request.getRequestURI();
logger.debug(" ip:【{}】, uri:【{}】, :【{}】", ip, uri, JSON.toJSONString(smsMessage));
return smsMessage.getMsgKey();
}
/**
*
* , ApiLimit , ( : )
*
*
* @param joinPoint
* @return void
* @Date 2020/7/5 21:03
*/
@Around("@annotation(cn.smilehappiness.annotation.ApiLimit)")
public void requestLimitByAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method == null) {
return;
}
if (method.isAnnotationPresent(ApiLimit.class)) {
dealLimit(method.getAnnotation(ApiLimit.class), joinPoint, false);
}
}
/**
*
*
*
*
* @param joinPoint
* @return cn.smilehappiness.annotation.ApiLimit
* @Date 2020/7/5 21:45
*/
private ApiLimit getAnnotation(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method == null) {
return null;
}
return method.getAnnotation(ApiLimit.class);
}
}
4.2.3業務方法フロー制限注釈の追加
/**
*
* ,
* , 500 ,
*
*
* @param smsMessage
* @return void
* @Date 2020/7/6 21:35
*/
@ApiLimit(limitCounts = 10, timeSecond = 120)
@Override
public void sendSmsMessage(SmsMessage smsMessage) {
// : ,400/s, 400 , , , , , 400
// , ,
// TODO
System.out.println(" , !");
}
4.3以上の例の簡単な分析
主にストリーム制限を必要とするトラフィックメソッドについて,
@ApiLimit(limitCounts = 10, timeSecond = 120)
注記を追加し,2分でApiメソッドが10回しか呼び出されないことを示し,そうでなければストリーム制限を行う.1回目のストリーム制限は再試行され、10秒後に呼び出されない場合は例外が投げ出され、ビジネス・エンドで処理されます.以上の例では、2つの方法を用いてブロックを行い、1つは
の方法であり、1つはApiLimit
の注釈を直接ブロックする方法であり、いずれも可能である.完全なコードの例、私はGitHubに共有して、必要な子供靴たちはダウンロードすることができます:distributed-limit-api
, , , !
ブログを書くのは自分の忘れやすいことを覚えるためで、また自分の仕事の総括に対してで、自分の努力を尽くして、もっと良くなることを望んで、みんなはいっしょに努力して進歩します!もし何か問題があれば、皆さんのコメントを歓迎して、一緒に検討して、コードに問題があれば、皆さんの指摘を歓迎します!自分の梦に翼を追加して、空の中で自由自在に飞ぶことができます!