如何为Spring Boot应用程序实现速率限制器

495 阅读5分钟

为Spring Boot应用程序实施速率限制器

在本教程中,我们将学习API的安全性和可用性。我们将了解Bucket4j库的效率,以及如何用它来限制Spring REST API的速率。

我们将实施一个简单的计算器项目来探索速率限制的内部运作,获得对Bucket4j库的理解,并将其用于对实际的Spring boot应用程序进行速率限制。

让我们开始吧。

前提条件

  • 有过REST APIs的知识。
  • 使用[Spring Boot]的知识。
  • Java编程语言的基本知识。

什么是速率限制?

速率限制是一种软件工程策略,允许API基础设施的创建者和维护者控制对其API的访问。在一个特定的时间内,任何消费者可以进行的调用数量都会被检查。

通过这样做,可以防止API的滥用和不必要的使用。费率限制器也可以作为一种手段,通过控制消费者选择的计划可以进行多少次呼叫,从而将消费者置于支付计划中。

费率限制库 - Bucket4j

Bucket4j库是一个基于Java的库,使用令牌桶算法构建。这意味着它在线程上是安全的,既可以在集群环境中也可以在孤立的Java虚拟机(JVM)环境中采用。

令牌桶算法

这个简单而强大的算法背后的想法是简单明了的。想象一下,有一个桶,可以容纳x 数量的令牌。任何时候,客户希望访问一个资源或一个端点,他都必须从这个桶中获得一个令牌来实现。

我们只需拿出一个令牌,递给他,然后他就可以成功地进行请求。反之,如果没有令牌,我们就直接拒绝他的请求。

因此,请求和令牌的数量是成反比的。

在类似的情况下,考虑一个速率限制为每小时500个请求的应用程序。我们可以建立一个可以容纳500个代币的桶,并设置每小时500个的补给率。

如果我们在一小时内收到450个请求,少于500个可用代币的总数,我们将把剩余的50个代币结转到下一小时;提高桶的容量。

如果我们在45分钟内用完了所有的500个请求,我们将需要再等15分钟才能再次访问资源。

对一个应用程序进行速率限制

现在,我们将使用Bucket4j为一个小型应用实现速率限制。我们将通过建立一个 "周长计算器 "应用程序来学习。它可以简单地计算出一个形状的周长。

设置

首先,建立一个普通的spring boot项目,并在你的pom.xml 文件中添加这个依赖关系。

<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>4.10.0</version>
</dependency>

然后,创建三个文件,如图所示。

  1. 一个名为 "请求 "的类Dimension
  2. 一个名为 "模型 "的类Perimeter
  3. 一个名为Controller 的类
@Getter
@Setter
public class Dimension{
    private int length;
    private int breadth;
}

上面的Dimension 类是作为与前端的数据传输对象使用的。

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Perimeter {
    private String shape;
    private Double perimeter;
}

Perimeter 类是实际的对象,我们在这里计算一个形状的周长。

@RestController
public class Controller {
    @PostMapping(value = "/api/v1/perimeter/rectangle")
    public ResponseEntity<Perimeter> rectangle(@RequestBody Dimension dimensions) {
        return ResponseEntity.ok(new Perimeter("rectangle",
                (double) 2 * (dimensions.getLength() + dimensions.getBreadth())));
    }
}

实施

现在,让我们写出一个简单的速率限制代码。

有了这个,API应该在一分钟内只允许50个请求。因此,在一分钟内的第50次API调用后,API会拒绝该调用。

我们的控制器将被修改以反映这一变化--建立一个桶并引入带宽,如图所示。

@RestController
public class Controller {

    private final Bucket bucket;

    public Controller() {
        Bandwidth limit = Bandwidth.classic(50, Refill.greedy(50, Duration.ofMinutes(1)));
        this.bucket = Bucket4j.builder()
                .addLimit(limit)
                .build();
    }

    @PostMapping(value = "/api/v1/perimeter/rectangle")
    public ResponseEntity<Perimeter> rectangle(@RequestBody Dimension dimensions) {
        return ResponseEntity.ok(new Perimeter("rectangle",
                (double) 2 * (dimensions.getLength() + dimensions.getBreadth())));
    }
}

测试应用程序

我们可以通过向端点发送请求来测试工作情况,看看它是否利用了桶中的令牌,使用Bucket4j库的tryConsume 方法(Controller class modified)。

当我们达到极限时,调用会被拒绝,状态码和响应分别为49 – Too many requests

@PostMapping(value = "/api/v1/perimeter/rectangle")
public ResponseEntity<Perimeter> rectangle(@RequestBody Dimension dimensions) {

    if (bucket.tryConsume(1)) {
        return ResponseEntity.ok(new Perimeter("rectangle",
                (double) 2 * (dimensions.getLength() + dimensions.getBreadth())));
    }

    return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();
}

pre-limit

从上面的图片中,我们看到我们的请求是成功的,有一个相应的正确响应。

现在,为了简洁起见,我将再送入50个,并显示在第51个时,该计数的API调用被拒绝,并有一个正确的拒绝响应,如图。

post-limit

要再次使用资源,我们必须等到下一分钟的开始。

需要注意的几点

随着时间的推移,API调用正变得越来越流行。你一定使用过一种服务,要求你从一个计划升级到更高的计划--比如说,从免费层到基本计划。

有了速率限制,我们可以根据用户所在的计划来控制他们的API使用量。你只需写一个方法,与他们所处的计划的一些指标相匹配,并指出他们在一个时间框架内可以拨打的电话数量。

例如,一个免费计划的用户可以每小时打1000个电话,而一个基本用户可以有每小时100000个电话的带宽,以此类推。

每个客户都需要有一个独特的API Key(或某种标识符),他们必须与他们的请求一起发送,以帮助我们识别他们所属的计划。

你可以写一些方法,可以为客户生成新的API密钥,并相应地增加他们的API调用带宽。

主要收获

在本教程中,读者对限制其应用程序的速率有了更好的理解。通过教程,他们学会了如何将其添加到他们的项目中。

结论

利用本教程中的知识,开发者可以更好地保护他们的API,并通过控制对它们的点击量使它们更可用。由于它允许一些很好的控制措施,它是一个非常有用的赚钱途径。