服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Java服务限流算法的6种实现

Java服务限流算法的6种实现

2023-05-13 01:04未知服务器之家 Java教程

服务限流,是指通过控制请求的速率或次数来达到保护服务的目的,在微服务中,我们通常会将它和熔断、降级搭配在一起使用,来避免瞬时的大量请求对系统造成负荷,来达到保护服务平稳运行的目的。下面就来看一看常见的

服务限流,是指通过控制请求的速率或次数来达到保护服务的目的,在微服务中,我们通常会将它和熔断、降级搭配在一起使用,来避免瞬时的大量请求对系统造成负荷,来达到保护服务平稳运行的目的。下面就来看一看常见的6种限流方式,以及它们的实现与使用。

固定窗口算法

固定窗口算法通过在单位时间内维护一个计数器,能够限制在每个固定的时间段内请求通过的次数,以达到限流的效果。

Java服务限流算法的6种实现

进行测试,允许在1000毫秒内通过5个请求:

void test() throws InterruptedException {    FixedWindowRateLimiter fixedWindowRateLimiter            = new FixedWindowRateLimiter(1000, 5);    for (int i = 0; i < 10; i++) {        if (fixedWindowRateLimiter.tryAcquire()) {            System.out.println("执行任务");        }else{            System.out.println("被限流");            TimeUnit.MILLISECONDS.sleep(300);        }    }}复制代码

运行结果:

Java服务限流算法的6种实现

进行一下测试,对第一个例子中的规则进行修改,每1秒允许100个请求通过不变,在此基础上再把每1秒等分为10个0.1秒的窗口。

void test() throws InterruptedException {    SlidingWindowRateLimiter slidingWindowRateLimiter            = new SlidingWindowRateLimiter(1000, 10, 10);    TimeUnit.MILLISECONDS.sleep(800);    for (int i = 0; i < 15; i++) {        boolean acquire = slidingWindowRateLimiter.tryAcquire();        if (acquire){            System.out.println("执行任务");        }else{            System.out.println("被限流");        }        TimeUnit.MILLISECONDS.sleep(10);    }}复制代码

查看运行结果:

Java服务限流算法的6种实现

进行一下测试,先初始化一个漏桶,设置桶的容量为3,每秒放行1个请求,在代码中每500毫秒尝试请求1次:

void test() throws InterruptedException {    LeakyBucketRateLimiter leakyBucketRateLimiter=new LeakyBucketRateLimiter(3,1);    for (int i = 0; i < 15; i++) {        if (leakyBucketRateLimiter.tryAcquire()) {            System.out.println("执行任务");        }else {            System.out.println("被限流");        }        TimeUnit.MILLISECONDS.sleep(500);    }}复制代码

查看运行结果,按规则进行了放行:

Java服务限流算法的6种实现

进行测试,设置每秒产生5个令牌:

void acquireTest(){    RateLimiter rateLimiter=RateLimiter.create(5);    for (int i = 0; i < 10; i++) {        double time = rateLimiter.acquire();        log.info("等待时间:{}s",time);    }}复制代码

运行结果:

Java服务限流算法的6种实现

运行结果:

Java服务限流算法的6种实现

查看运行结果:

Java服务限流算法的6种实现

配置限流规则,这里使用直接编码方式配置,指定QPS到达1时进行限流:

@Componentpublic class SentinelConfig {    @PostConstruct    private void init(){        List<FlowRule> rules = new ArrayList<>();        FlowRule rule = new FlowRule(QueryService.KEY);        rule.setCount(1);        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);        rule.setLimitApp("default");        rules.add(rule);        FlowRuleManager.loadRules(rules);    }}复制代码

application.yml中配置sentinel的端口及dashboard地址:

spring:  application:    name: sentinel-test  cloud:    sentinel:      transport:        port: 8719        dashboard: localhost:8088复制代码

启动项目后,启动sentinel-dashboard

java -Dserver.port=8088 -jar sentinel-dashboard-1.8.0.jar复制代码

在浏览器打开dashboard就可以看见我们设置的流控规则:

Java服务限流算法的6种实现

对gateway进行配置,主要就是配一下令牌的生成速率、令牌桶的存储量上限,以及用于限流的键的解析器。这里设置的桶上限为2,每秒填充1个令牌:

spring:  application:    name: gateway-test  cloud:    gateway:      routes:        - id: limit_route          uri: lb://sentinel-test          predicates:          - Path=/sentinel-test/**          filters:            - name: RequestRateLimiter              args:                # 令牌桶每秒填充平均速率                redis-rate-limiter.replenishRate: 1                # 令牌桶上限                redis-rate-limiter.burstCapacity: 2                # 指定解析器,使用spEl表达式按beanName从spring容器中获取                key-resolver: "#{@pathKeyResolver}"            - StripPrefix=1  redis:    host: 127.0.0.1    port: 6379复制代码

我们使用请求的路径作为限流的键,编写对应的解析器:

@Slf4j@Componentpublic class PathKeyResolver implements KeyResolver {    public Mono<String> resolve(ServerWebExchange exchange) {        String path = exchange.getRequest().getPath().toString();        log.info("Request path: {}",path);        return Mono.just(path);    }}复制代码

启动gateway,使用jmeter进行测试,设置请求间隔为500ms,因为每秒生成一个令牌,所以后期达到了每两个请求放行1个的限流效果,在被限流的情况下,http请求会返回429状态码。

Java服务限流算法的6种实现

除了上面的根据请求路径限流外,我们还可以灵活设置各种限流的维度,例如根据请求header中携带的用户信息、或是携带的参数等等。当然,如果不想用gateway自带的这个Redis的限流器的话,我们也可以自己实现RateLimiter接口来实现一个自己的限流工具。

gateway实现限流的关键是spring-cloud-gateway-core包中的RedisRateLimiter类,以及META-INF/scripts中的request-rate-limiter.lua这个脚本,如果有兴趣可以看一下具体是如何实现的。

总结

总的来说,要保证系统的抗压能力,限流是一个必不可少的环节,虽然可能会造成某些用户的请求被丢弃,但相比于突发流量造成的系统宕机来说,这些损失一般都在可以接受的范围之内。前面也说过,限流可以结合熔断、降级一起使用,多管齐下,保证服务的可用性与健壮性。

那么,这次的分享就到这里,我是Hydra,我们下篇再见。

文中的全部测试代码都传到了我的github,有需要可以自行领取:github.com/trunks2008/…
码字不易~ 欢迎大家给Hydra点个Star啊,谢谢~

作者简介,码农参上,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。欢迎关注、添加好友,进一步交流。

原文地址:

延伸 · 阅读

精彩推荐