Netflix Zuul 自定义限流

上面介绍的 spring-cloud-zuul-ratelimit 限流均是通过某个 KEY 进行限流,如基于请求方法、请求 URL 地址、用户等等信息进行限流。

自定义限流是通过实现 RateLimitKeyGenerator 接口,创建自己的 KEY,然后 spring-cloud-zuul-ratelimit 基于该 KEY 进行限流,如:你可以将某个请求参数作为 KEY。

RateLimitKeyGenerator 接口

RateLimitKeyGenerator接口是 spring-cloud-zuul-ratelimit 中的一个重要接口,主要用于生成限流的键(key)。

接口源码如下:

package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import org.springframework.cloud.netflix.zuul.filters.Route;
import javax.servlet.http.HttpServletRequest;

/**
 * Key generator for rate limit control.
 *
 * @author Liel Chayoun
 */
public interface RateLimitKeyGenerator {

    /**
     * Returns a key based on {@link HttpServletRequest}, {@link Route} and
     * {@link RateLimitProperties.Policy}
     *
     * @param request The {@link HttpServletRequest}
     * @param route   The {@link Route}
     * @param policy  The {@link RateLimitProperties.Policy}
     * @return Generated key
     */
    String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy);
}

该接口只有一个方法 key(RequestContext context),它接收一个 RequestContext 对象作为参数,该对象包含了当前请求的上下文信息。其目的是根据请求的上下文生成一个唯一的键,这个键将用于在限流存储中进行请求计数和限流控制。

spring-cloud-zuul-ratelimit 提供了该接口的默认实现 DefaultRateLimitKeyGenerator 类。

默认实现 DefaultRateLimitKeyGenerator

DefaultRateLimitKeyGenerator 是 RateLimitKeyGenerator 接口的默认实现,主要负责生成用于限流的唯一键(key)。这个键在限流操作中起着关键作用,因为它决定了限流的范围和粒度。通过不同的键可以对不同类型的请求或用户进行精确的限流控制。

DefaultRateLimitKeyGenerator 主要功能:

  • 生成限流键:根据不同的请求属性和配置的限流策略,DefaultRateLimitKeyGenerator 会生成一个唯一的键。这个键会被存储在存储库(如 Redis)中,用于记录请求的限流信息,例如请求的次数、时间等。

  • 与限流策略关联:它与不同的限流策略(如基于用户、IP、URL 等)紧密相关。根据不同的策略,生成的键将有所不同,以确保限流规则的准确性和有效性。

源码如下:

package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitKeyGenerator;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties.Policy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.Route;

import javax.servlet.http.HttpServletRequest;
import java.util.StringJoiner;

/**
 * Default KeyGenerator implementation.
 *
 * @author roxspring (github user)
 * @author Marcos Barbero
 * @author Liel Chayoun
 */
public class DefaultRateLimitKeyGenerator implements RateLimitKeyGenerator {

    private final RateLimitProperties properties;
    private final RateLimitUtils rateLimitUtils;

    public DefaultRateLimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
        this.properties = properties;
        this.rateLimitUtils = rateLimitUtils;
    }

    @Override
    public String key(final HttpServletRequest request, final Route route, final Policy policy) {
        // KEY 的多个部分使用 “:” 进行拼接
        final StringJoiner joiner = new StringJoiner(":");
        // 前缀:@Value("${spring.application.name:rate-limit-application}")
        // 应用名称
        joiner.add(properties.getKeyPrefix());
        // 路由IP
        if (route != null) {
            joiner.add(route.getId());
        }
        // 匹配的类型
        policy.getType().forEach(matchType -> {
            // 调用 key() 方法,生成 key
            String key = matchType.key(request, route, rateLimitUtils);
            if (StringUtils.isNotEmpty(key)) {
                joiner.add(key);
            }
        });
        return joiner.toString();
    }
}

policy.getType() 源码:

public static class Policy {
    //...
    public List<MatchType> getType() {
        return type;
    }
    //...
}

MatchType 类源码:

public static class MatchType {
    @Valid
    @NotNull
    private RateLimitType type;
    private String matcher;
    //...
    public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils) {
        return type.key(request, route, rateLimitUtils, matcher) +
        (StringUtils.isEmpty(matcher) ? StringUtils.EMPTY : (":" + matcher));
    }
    //...
}

RateLimitType 类源码:

public enum RateLimitType {
    /**
     * Rate limit policy considering the user's origin.
     */
    ORIGIN {
        @Override
        public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
            if (matcher.contains("/")) {
                SubnetUtils subnetUtils = new SubnetUtils(matcher);
                return subnetUtils.getInfo().isInRange(rateLimitUtils.getRemoteAddress(request));
            }
            return matcher.equals(rateLimitUtils.getRemoteAddress(request));
        }

        @Override
        public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
            // 生成KEY
            return rateLimitUtils.getRemoteAddress(request);
        }
    },

    /**
     * Rate limit policy considering the authenticated user.
     */
    USER {
        //...
    },

    /**
     * Rate limit policy considering the request path to the downstream service.
     */
    URL {
        //...
    },

    /**
     * Rate limit policy considering the authenticated user's role.
     */
    ROLE {
        //...
    },

    /**
     * @deprecated See {@link #HTTP_METHOD}
     */
    @Deprecated
    HTTPMETHOD {
        //...
    },

    /**
     * Rate limit policy considering the HTTP request method.
     */
    HTTP_METHOD {
        //...
    },

    /**
     * Rate limit policy considering an URL Pattern
     */
    URL_PATTERN {
        //...
    };

    public abstract boolean apply(HttpServletRequest request, Route route,
                                  RateLimitUtils rateLimitUtils, String matcher);

    public abstract String key(HttpServletRequest request, Route route,
                               RateLimitUtils rateLimitUtils, String matcher);

    /**
     * Helper method to validate specific cases per type.
     *
     * @param matcher The type matcher
     * @return The default behavior will always return true.
     */
    public boolean isValid(String matcher) {
        return true;
    }
}

当一个请求到达 Zuul 网关,DefaultRateLimitKeyGenerator会根据当前的限流策略和请求的各种属性开始生成键。

它会从请求中提取所需的信息,例如,使用 HttpServletRequest 来获取 IP 地址、请求的 URL、用户信息(如果有)等。

对于不同的策略,会调用不同的逻辑来生成相应的键,确保键的唯一性和符合限流策略的要求。

生成的键将用于后续的限流操作,包括从存储库中读取限流数据、更新限流数据和判断是否超过限流阈值。

自定义限流

下面通过继承 DefaultRateLimitKeyGenerator 类,重写 key() 方法实现自定义限流 KEY,实现限流。

(1)在 application.yml 或 application.properties 文件中配置限流类型。以下是一个配置示例:

zuul:
  ratelimit:
    enabled: true
    default-policy:
      limit: 10
      quota: 100
      refresh-interval: 60
      type:
        - user

上述配置中,limit 表示每个用户的限流次数,quota 表示每个用户的配额,refresh-interval 表示刷新间隔(以秒为单位)。

(2)自定义限流 Key 生成器:

package com.hxstrive.hystrix_demo.custom;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.DefaultRateLimitKeyGenerator;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

/**
 * 自定义限流 key 生成器
 * @author hxstrive.com
 */
@Component
public class RatelimitKeyGenerator extends DefaultRateLimitKeyGenerator {

    public RatelimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
        super(properties, rateLimitUtils);
    }

    /**
     * 限流逻辑
     * @param request 请求对象
     * @param route 路由对象
     * @param policy 策略对象
     * @return 限流 key
     */
    @Override
    public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        System.out.println("自定义限流 key 生成器,id=" + request.getParameter("id"));
        // 对请求中相同 id 参数的请求进行限制
        return super.key(request, route, policy) + ":" + request.getParameter("id");
    }

}

重启 Zuul 服务,短时间内连续访问 http://localhost:9000/api/product/product/get?id=1 地址,将输出如下错误信息:

2a05f1790957a13e1c48c81c7e334740_1734944796403-ffdcd861-004b-4c43-b011-22c52a038638.png

后端日志信息:

d8148f5b7e507ecbca7af04f7c47032d_1734945115569-eb63d79a-9404-47e6-8106-e2e0a7d7d7dc.png

从上图可知,成功触发了限流,并且也调用了自定义的限流 KEY 生成器。

 

点击下载/查看本教程相关资料或者源代码。
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号