Java培训-怎样通过 Bucket4j 共享速率限制
发布时间:2025-11-01
4.通过 Bucket4j 构建 Rate-Limiter
让我们考量一下 Bucket4j 奎构建的 Token Bucket 演算法。
Bucket4j 是 Java 世界性中所常用构建频率约束的系统的最流行起来的奎。每个月,Bucket4j 从 Maven Central 下载多达 200,000 次,并值得注意在 GitHub 上的 3500 个相反项中所。
让我们考量几个最简单的值得注意(我们将适用 Maven 作为软件数据分析制度和忽略工具)。
对于第一个,我们须要在 pom.xml 中所缓冲一个相反项:
com.github.vladimir-bukhtoyarov
bucket4j-core
7.0.0
创建者 Example.java:
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.ConsumptionProbe;
import java.time.Duration;
public class Example {
public static void main(String args[]) {
//Create the Bandwidth to set the rule - one token per minute
Bandwidth oneCosumePerMinuteLimit = Bandwidth.simple(1, Duration.ofMinutes(1));
//Create the Bucket and set the Bandwidth which we created above
Bucket bucket = Bucket.builder()
.addLimit(oneCosumePerMinuteLimit)
.build();
//Call method tryConsume to set count of Tokens to take from the Bucket,
//returns boolean, if true - consume successful and the Bucket had enough Tokens inside Bucket to execute method tryConsume
System.out.println(bucket.tryConsume(1)); //return true
//Call method tryConsumeAndReturnRemaining and set count of Tokens to take from the Bucket
//Returns ConsumptionProbe, which include much more information than tryConsume, such as the
//isConsumed - is method consume successful performed or not, if true - is successful
//getRemainingTokens - count of remaining Tokens
//getNanosToWaitForRefill - Time in nanoseconds to refill Tokens in our Bucket
ConsumptionProbe consumptionProbe = bucket.tryConsumeAndReturnRemaining(1);
System.out.println(consumptionProbe.isConsumed()); //return false since we have already called method tryConsume, but Bandwidth has a limit with rule - one token per one minute
System.out.println(consumptionProbe.getRemainingTokens()); //return 0, since we have already consumed all of the Tokens
System.out.println(consumptionProbe.getNanosToWaitForRefill()); //Return around 60000000000 nanoseconds
}
好的,我认为它看来最简单释义!
让我们考量一个更困难的值得注意。让我们想像一种情况,您须要考量通过对某个 RESTful API 方法有的乞求计至少来约束(须要通过来自某个用户对某个控制机的乞求codice_计至少来约束,每个 Y 间隔不多达 X 次)。【关注已为科学城,精彩学IT】但是,我们的的系统是分布式的,我们在一个战斗群中所有很多作者;我们适用 Hazelcast(但它可以是任何 JSR107 磁盘、DynamoDB、Redis 或其他东西)。
让我们基于 Spring 方法论来构建我们的示例。
首先,我们须要在 pom.xml 中所缓冲一些相反项:
com.github.vladimir-bukhtoyarov
bucket4j-hazelcast
7.0.0
javax.cache
cache-api
1.0.0
com.hazelcast
hazelcast
4.0.2
org.projectlombok
lombok
1.18.20
provided
对于下一步,我们不该考量在将来在控制机档次上适用释义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RateLimiter {
TimeUnit timeUnit() default TimeUnit.MINUTES;
long timeValue();
long restriction();
}
此外,释义将分组 RateLimiter 释义(如果我们须要为每个控制机适用多个数据传输)。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RateLimiters {
RateLimiter[] value();
}
另外,须要缓冲重上新至少据类型:
public enum TimeUnit {
MINUTES, HOURS
}
而且,以前,我们须要创建者一个类,它将完成释义执行。由于将在控制机档次上新设释义,因此该类亦然 HandlerInterceptorAdapter 适配:
public class RateLimiterAnnotationHandlerInterceptorAdapter extends HandlerInterceptorAdapter {
//You should have already realized class, which returns Authentication context to getting userId
private AuthenticationUtil authenticationUtil;
private final ProxyManager proxyManager;
@Autowired
public RateLimiterAnnotationHandlerInterceptorAdapter(AuthenticationUtil authenticationUtil, HazelcastInstance hazelcastInstance) {
this.authenticationUtil = authenticationUtil;
//To start work with Hazelcast, you also should create HazelcastInstance bean
IMap bucketsMap = hazelcastInstance.getMap(HazelcastFrontConfiguration.RATE_LIMITER_BUCKET);
proxyManager = new HazelcastProxyManager<>(bucketsMap);
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
//if into handlerMethod is present RateLimiter or RateLimiters annotation, we get it, if not, we get empty Optional
Optional> rateLimiters = RateLimiterUtils.getRateLimiters(handlerMethod);
if (rateLimiters.isPresent()) {
//Get path from RequestMapping annotation(respectively we can get annotations such: GetMapping, PostMapping, PutMapping, DeleteMapping, because all of than annotations are extended from RequestMapping)
RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
//To get unique key we use bundle of 2-x values: path from RequestMapping and user id
RateLimiterKey key = new RateLimiterKey(authenticationUtil.getPersonId(), requestMapping.value());
//Further we set key in proxy to get Bucket from cache or create a new Bucket
Bucket bucket = proxyManager.builder().build(key, () -> RateLimiterUtils.rateLimiterAnnotationsToBucketConfiguration(rateLimiters.get()));
//Try to consume token, if we don’t do that, we return 429 HTTP code
if (!bucket.tryConsume(1)) {
response.setStatus(429);
return false;
}
}
}
return true;
}
要适用 Hazelcast,我们须要创建者一个不必可序列化的可选基团:
@Data
@AllArgsConstructor
public class RateLimiterKey implements Serializable {
private String userId;
private String[] uri;
}
此外,不要忘记来由 RateLimiterUtils 的特殊而设计类,常用与 RateLimiterAnnotationHandlerInterceptorAdapter 四人社会活动(Spring 重上新名字誓约样式 - 将您的类或方法有命来由不必易于忽略,www.atguigu.com即使以您的实质上值得注意 10 个单词。这是我的期望格调)。
public final class RateLimiterUtils {
public static BucketConfiguration rateLimiterAnnotationsToBucketConfiguration(List rateLimiters) {
ConfigurationBuilder configBuilder = Bucket4j.configurationBuilder();
rateLimiters.stream().forEach(limiter -> configBuilder.addLimit(buildBandwidth(limiter)));
return configBuilder.build();
}
public static Optional> getRateLimiters(HandlerMethod handlerMethod) {
RateLimiters rateLimitersAnnotation = handlerMethod.getMethodAnnotation(RateLimiters.class);
if(rateLimitersAnnotation != null) {
return Optional.of(Arrays.asList(rateLimitersAnnotation.value()));
}
RateLimiter rateLimiterAnnotation = handlerMethod.getMethodAnnotation(RateLimiter.class);
if(rateLimiterAnnotation != null) {
return Optional.of(Arrays.asList(rateLimiterAnnotation));
}
return Optional.empty();
}
private static final Bandwidth buildBandwidth(RateLimiter rateLimiter) {
TimeUnit timeUnit = rateLimiter.timeUnit();
long timeValue = rateLimiter.timeValue();
long restriction = rateLimiter.restriction();
if (TimeUnit.MINUTES.equals(timeUnit)) {
return Bandwidth.simple(restriction, Duration.ofMinutes(timeValue));
} else if (TimeUnit.HOURS.equals(timeUnit)) {
return Bandwidth.simple(restriction, Duration.ofHours(timeValue));
} else {
return Bandwidth.simple(5000, Duration.ofHours(1));
}
}
}
还有一件有事; 我们须要在适配自 WebMvcConfigurerAdapter 的 Context 中所登记注册我们的可选拦截机:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class ContextConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RateLimiterAnnotationHandlerInterceptorAdapter());
}
}
以前,为了测试我们的机制,我们将创建者 ExampleController 并在控制机的方法有右侧上新设 RateLimiter 以检查它是否正常社会活动:
import com.nibado.example.customargumentspring.component.RateLimiter;
import com.nibado.example.customargumentspring.component.RateLimiters;
import com.nibado.example.customargumentspring.component.TimeUnit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@RateLimiters({@RateLimiter(timeUnit = TimeUnit.MINUTES, timeValue = 1, restriction = 2), @RateLimiter(timeUnit = TimeUnit.HOURS, timeValue = 1, restriction = 5)})
@GetMapping("/example/{id}")
public String example(@PathVariable("id") String id) {
return "ok";
}
}
在@RateLimiters 中所,我们上新设了两个约束:
@RateLimiter(timeUnit = TimeUnit.MINUTES, timeValue = 1,restriction = 2) — 每分钟不多达 2 个乞求。 @RateLimiter(timeUnit = TimeUnit.HOURS, timeValue = 1,restriction = 5) — 每不间断不多达 5 个乞求。这只是 Bucket4j 奎的一小部分。如果你觉得这个奎很好的话,可以去研读更多API。
推荐选读:
Java技术开发技术之Javaweb具体内容jsjava中所json的适用
java技术开发juc并作之AQS入门
Java技术开发之模拟方法论Mockito
java技术开发方法论之JUnit 研读个人
。苏州癫痫检查哪家医院好北京白癜风医院哪家比较好
三亚男科医院哪家比较好
驻马店白癜风哪家医院最好
三亚看男科的医院哪家好

-
直播预告 | 个推TechDay“治数训练营”,背著你入门数据仓库,跑通数据建模流程
4同月13日-14日(周日、晚间),每晚19:30-20:30,不见不散! 为引动企业高质量数据流促使较快,尽力大数据库传播媒体加速提升数据库开发新和系统性职业技能,数据库智能服务