分散環境では、Redisに基づいてRestful APIインタフェースのストリーム制限を実現


創作は容易ではありません。もしこの文章があなたに役に立つと思ったら、皆さんの支持を歓迎します。あなたの支持は私の創作の最大の原動力です。

文書ディレクトリ

  • 1前言
  • 2 Apiインタフェースに対するストリーム制限
  • 3限流方式の選択
  • 4 Redisに基づく限流
  • を実現する.
  • 4.1実現の構想
  • 4.2限流
  • を実現
  • 4.2.1定義限流注記
  • 4.2.2切面類を定義し、ストリームを遮断する方法
  • .
  • 4.2.3業務方法追加限流注記
  • 4.3以上の例を簡単に分析する
  • 1はじめに


    時代の発展に伴い、インターネットも大きく変化した.そのうちの重要な変化の1つは、 , , です.
    分散アーキテクチャとは?簡単に言えば、以前の ( )であり、分割によって 、 、 、この , , である.
    本論文では,分散環境におけるインタフェース法のストリーム制限を最も効率的な方法で実現する.

    2なぜApiインタフェースにストリームを制限するのか


    様々なアプリケーションシーンを満たすために、インタフェースApiをストリーム制限しなければならない場合がある.例えば、メールサービスでは、サプライヤーが毎秒400件を超えないように要求する可能性があります.このアクセス量を超えると、要求がサプライヤーに拒否され、メールの送信が漏れてしまいます.
    また、サードパーティのApiはアクセスを制限するため、1分間に20回しかインタフェースを要求できないように設定し、それを超えるとタイムアウトしたり、異常に応答したりします.
    要するに、ストリーム制限は、多くのシーンで使われています.

    3限流方式の選択


    限流、方案はたくさんあって、具体的な典型的な限流方案の紹介、私のもう一つの博文を参考にしてください:分布式環境の下限流方案の思考
    最終的に選択されたスキームはRedisに基づいて実現された制限流であり,このスキームは現在最も流行しており,最も効率的な方法でもある.

    4 Redisベースのストリーム制限


    4.1実現の構想


    構想:RedisINCR操作によってLimit限流を実現する
  • INCR keyに格納されている数値を1つ増やし、keyが存在しない場合、keyの値は0に初期化されてからINCR操作が実行されます.値にエラーのタイプが含まれている場合、または文字列タイプの値が数値として表示されない場合、エラーが返されます.この操作の値は64ビット(bit)の記号付き数値表示に制限されます.
  • APIが呼び出されると、APIが呼び出される前にINCRkeyが行われ、keyはipアドレス相関、ユーザ相関、 、または 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 , , , !
    ブログを書くのは自分の忘れやすいことを覚えるためで、また自分の仕事の総括に対してで、自分の努力を尽くして、もっと良くなることを望んで、みんなはいっしょに努力して進歩します!もし何か問題があれば、皆さんのコメントを歓迎して、一緒に検討して、コードに問題があれば、皆さんの指摘を歓迎します!自分の梦に翼を追加して、空の中で自由自在に飞ぶことができます!