当前位置: 首页 > 知识库问答 >
问题:

Spring云网关中基于用户计划的速率限制

甄越
2023-03-14

假设我的用户订阅了一个计划。那么,是否可以使用SpringCloudGateway根据订阅计划对用户请求进行分级限制?考虑到白银和黄金的计划,它是否会让白银订阅的充值率/容量为5/10,黄金订阅的充值率/容量为50/100?

我天真地想到传递RedisRateLimiter的新实例(见下文,我用5/10设置构建了一个新实例)到过滤器,但我需要从请求中以某种方式获取用户的信息,以便能够找出它是否是白银和黄金计划。

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route(p -> p
            .path("/get")
            .filters(f ->
              f.requestRateLimiter(r -> {
                  r.setRateLimiter(new RedisRateLimiter(5, 10))
              })
            .uri("http://httpbin.org:80"))
            .build();
}

我是否正在尝试通过SpringCloudGateway实现一些甚至是可能的东西?如果有,您建议检查其他哪些产品?

谢谢

共有1个答案

金阳曜
2023-03-14

好的,可以在RedisRateLimiter类的顶部创建一个自定义速率限制器。不幸的是,该类的体系结构不具备可扩展性,因此该解决方案有点“黑客”,我只能装饰普通的redistreLimitor,并在其中复制一些代码:

@Primary
@Component
public class ApiKeyRateLimiter implements RateLimiter {

    private Log log = LogFactory.getLog(getClass());

    // How many requests per second do you want a user to be allowed to do?
    private static final int REPLENISH_RATE = 1;
    // How much bursting do you want to allow?
    private static final int BURST_CAPACITY = 1;

    private final RedisRateLimiter rateLimiter;
    private final RedisScript<List<Long>> script;
    private final ReactiveRedisTemplate<String, String> redisTemplate;

    @Autowired
    public ApiKeyRateLimiter(
        RedisRateLimiter rateLimiter,
        @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> script,
        ReactiveRedisTemplate<String, String> redisTemplate) {

        this.rateLimiter = rateLimiter;
        this.script = script;
        this.redisTemplate = redisTemplate;
    }

    // These two methods are the core of the rate limiter
    // Their purpose is to come up with a rate limits for given API KEY (or user ID)
    // It is up to implementor to return limits based up on the api key passed
    private int getBurstCapacity(String routeId, String apiKey) {
        return BURST_CAPACITY;
    }
    private int getReplenishRate(String routeId, String apiKey) {
        return REPLENISH_RATE;
    }

    public Mono<Response> isAllowed(String routeId, String apiKey) {

        int replenishRate = getReplenishRate(routeId, apiKey);
        int burstCapacity = getBurstCapacity(routeId, apiKey);

        try {
            List<String> keys = getKeys(apiKey);

            // The arguments to the LUA script. time() returns unixtime in seconds.
            List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
                Instant.now().getEpochSecond() + "", "1");
            Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);

            return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
                .reduce(new ArrayList<Long>(), (longs, l) -> {
                    longs.addAll(l);
                    return longs;
                }) .map(results -> {
                    boolean allowed = results.get(0) == 1L;
                    Long tokensLeft = results.get(1);

                    Response response = new Response(allowed, getHeaders(tokensLeft, replenishRate, burstCapacity));

                    if (log.isDebugEnabled()) {
                        log.debug("response: " + response);
                    }
                    return response;
                });
        }
        catch (Exception e) {
            /*
             * We don't want a hard dependency on Redis to allow traffic. Make sure to set
             * an alert so you know if this is happening too much. Stripe's observed
             * failure rate is 0.01%.
             */
            log.error("Error determining if user allowed from redis", e);
        }
        return Mono.just(new Response(true, getHeaders(-1L, replenishRate, burstCapacity)));
    }

    private static List<String> getKeys(String id) {
        String prefix = "request_rate_limiter.{" + id;
        String tokenKey = prefix + "}.tokens";
        String timestampKey = prefix + "}.timestamp";
        return Arrays.asList(tokenKey, timestampKey);
    }

    private HashMap<String, String> getHeaders(Long tokensLeft, Long replenish, Long burst) {
        HashMap<String, String> headers = new HashMap<>();
        headers.put(RedisRateLimiter.REMAINING_HEADER, tokensLeft.toString());
        headers.put(RedisRateLimiter.REPLENISH_RATE_HEADER, replenish.toString());
        headers.put(RedisRateLimiter.BURST_CAPACITY_HEADER, burst.toString());
        return headers;
    }

    @Override
    public Map getConfig() {
        return rateLimiter.getConfig();
    }

    @Override
    public Class getConfigClass() {
        return rateLimiter.getConfigClass();
    }

    @Override
    public Object newConfig() {
        return rateLimiter.newConfig();
    }
}

所以,路线是这样的:

@Component
public class Routes {

    @Autowired
    ApiKeyRateLimiter rateLimiter;

    @Autowired
    ApiKeyResolver apiKeyResolver;

    @Bean
    public RouteLocator theRoutes(RouteLocatorBuilder b) {
        return b.routes()
            .route(p -> p
                    .path("/unlimited")
                    .uri("http://httpbin.org:80/anything?route=unlimited")
            )
            .route(p -> p
                    .path("/limited")
                    .filters(f ->
                            f.requestRateLimiter(r -> {
                                r.setKeyResolver(apiKeyResolver);
                                r.setRateLimiter(rateLimiter);
                            } )
                    )
                    .uri("http://httpbin.org:80/anything?route=limited")
            )
            .build();
    }

}

希望这能为某人节省一天的工作时间...

 类似资料:
  • 我不能玩春云之门的限速器。我在这里配置网关: 我使用JMeter调用API循环1000次,但网关没有响应429响应中的请求太多。我哪里错了?

  • 我试图在Spring云网关中设置速率限制。 我试图在应用程序中配置过滤器。yaml如下所示 我得到下面的错误 如何在spring云网关中实现速率限制?

  • 让我们假设我有一个这样的结构化数组: 我将这个结构称为“categories”,所以,我在这个数组中有六个类别。我的目标是根据一个类别随机挑选一个产品。 我想做一个基于速率的类别选择,据我所知,我必须计算这个类别在数组中代表多少百分比,例如: 这会给我类似的东西: 好的,现在我要做一个简单的算法,根据这些比率得到类别;我想我现在需要在范围之间选择一个随机数,并制作一些“切片”,例如: 如果随机数介

  • 我正在使用SpringWebFlux和netty构建一个微服务。在内部,我使用web客户端进行RESTAPI调用。如何控制通过webclient调用RESTAPI的速率?我猜backnpressure只适用于单个请求/回复,不适用于对我的微服务的多个请求。Amy pointers将不胜感激。谢谢

  • 我需要创建一个反向代理,接收传入的请求,并基于请求正文的内容,将请求路由到特定的URI。 这是一个路由微服务,它类似于反向代理,根据来自每个请求主体的一些信息进行路由。这意味着对于每个请求,我需要解析请求正文并获得“username”字段,然后建立JDBC连接以从数据库中获取附加信息。根据数据库中的信息,它最终将请求重定向到正确的URI。 从我现在所拥有的,我有2个阻止方法。第一个是请求主体的解析

  • 我想为提供的一组服务实现节流。在峰值负载下,我想限制或拒绝向长时间运行的用户或发送大量数据或其他规则的用户提供服务,这些规则甚至可以在以后的阶段进行配置。我计划使用java和spring框架来编写代码。 我读过关于利率限制模式的文章。堆栈溢出帖子建议的一个可能的实现是Token_bucket。这篇文章感觉很复杂。有人能提出其他替代方法或模式吗。以及描述它们的好文章。 我还发现了另一个来自微软的pd