Spring MVCの簡単な電流制限デザイン

まず、概念

電流制限の目的は、同時アクセスによって保護されている/レート要求、または時間ウィンドウシステム速度内の要求を制限、レート制限は、サービスが拒否される可能性が一度に達し、またはキュー、降格プロセスを待っています。

一般的に次の2つの方法で使用制限アルゴリズム:リーキーバケットトークンバケットアルゴリズム

リーキーバケットアルゴリズムは、非常にシンプルなアイデア、最初のバケットに排出する水(リクエスト)、一定速度で水のリーキーバケット、水の速度がオーバーフローを介して直接流れるときアセンブリは、トークンバケットアルゴリズムは、データ伝送レートに制限を課すことができることがわかるです。

多くのシナリオでは、平均データ転送速度に加えて、特許請求の範囲を限定することは可能であるが、許容バースト送信のある程度が必要です。この時間は、不適切なリーキーバケットアルゴリズムであってもよいし、トークンバケットアルゴリズムは、より適しています。

トークンバケットアルゴリズムの原理は、その後、トークンバケットが望ましくないとき、システムはバケット内のトークンに一定の速度になりますが、要求を処理する場合は、トークンバケットを取得する必要が始まるということですサービス拒否。

第二に、アプリケーション

Googleのオープンソースツールキットグアバは、電流制限ツールRateLimiter、非常に使いやすい、電流制限を完了するために、クラストークンバケットアルゴリズムを提供します。RateLimiter APIは、並行プログラミングネットワーク見ることができますグアバRateLimiterを導入しました。

私たちは、MVCインターセプタ+グァバRateLimiterが私たちの制限スキームを達成するために使用します。

@Slf4j
public class RequestLimitInterceptor extends HandlerInterceptorAdapter implements BeanPostProcessor {

    private static final Integer GLOBAL_RATE_LIMITER = 10;

    private static Map<PatternsRequestCondition, RateLimiter> URL_RATE_MAP;

    private Properties urlProperties;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (URL_RATE_MAP != null) {
            String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
            for (PatternsRequestCondition patternsRequestCondition : URL_RATE_MAP.keySet()) {
                //使用spring DispatcherServlet的匹配器PatternsRequestCondition进行匹配
                //spring 3.x 版本
                //Set<String> matches = patternsRequestCondition.getMatchingCondition(request).getPatterns();
                //spring 4.x 版本
                List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
                if (CollectionUtils.isEmpty(matches)){
                    continue;
                }
                //尝试获取令牌
                if (!URL_RATE_MAP.get(patternsRequestCondition).tryAcquire(1000, TimeUnit.MILLISECONDS)) {
                    log.info(" 请求'{}'匹配到 mathes {},超过限流速率,获取令牌失败。", lookupPath, Joiner.on(",").join(patternsRequestCondition.getPatterns()));
                    return false;
                }
                log.info(" 请求'{}'匹配到 mathes {} ,成功获取令牌,进入请求。", lookupPath, Joiner.on(",").join(patternsRequestCondition.getPatterns()));
            }
        }
        return super.preHandle(request, response, handler);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (RequestMappingHandlerMapping.class.isAssignableFrom(bean.getClass())) {
            if (URL_RATE_MAP == null) {
                URL_RATE_MAP = new ConcurrentHashMap<>(16);
            }
            log.info("we get all the controllers's methods and assign it to urlRateMap");
            RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) bean;
            Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
            for (RequestMappingInfo mappingInfo : handlerMethods.keySet()) {
                PatternsRequestCondition requestCondition = mappingInfo.getPatternsCondition();
                // 默认的 url 限流方案设定
                URL_RATE_MAP.put(requestCondition, RateLimiter.create(GLOBAL_RATE_LIMITER));
            }
            // 自定义的限流方案设定
            if (urlProperties != null) {
                for (String urlPatterns : urlProperties.stringPropertyNames()) {
                    String limit = urlProperties.getProperty(urlPatterns);
                    if (!limit.matches("^-?\\d+$")){
                        log.error("the value {} for url patterns {} is not a number ,please check it ", limit, urlPatterns);
                    }
                    URL_RATE_MAP.put(new PatternsRequestCondition(urlPatterns), RateLimiter.create(Integer.parseInt(limit)));
                }
            }
        }
        return bean;
    }

    /**
     * 限流的 URL与限流值的 K/V 值
     *
     * @param urlProperties
     */
    public void setUrlProperties(Properties urlProperties) {
        this.urlProperties = urlProperties;
    }
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public RequestLimitInterceptor requestLimitInterceptor(){
        RequestLimitInterceptor limitInterceptor = new RequestLimitInterceptor();
        // 设置自定义的 url 限流方案
        Properties properties = new Properties();
        properties.setProperty("/admin/**", "10");
        limitInterceptor.setUrlProperties(properties);
        return limitInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 限流方案
        registry.addInterceptor(requestLimitInterceptor());
    }
}

ヒント:ここでは合理的ではないリストurlPropertiesを制限するカスタムプログラムは、コンフィギュレーション・センター(ナコス、春の雲コンフィグなど)で考慮することができますが、動的に必要性を制限するURLを更新します。

参考博文:

  1. https://blog.csdn.net/Lili429/article/details/79236819
  2. https://blog.csdn.net/valleychen1111/article/details/78038366

おすすめ

転載: www.cnblogs.com/jmcui/p/11281788.html