当前位置: 代码网 > it编程>编程语言>Java > SpringBoot 实现流控的操作方法

SpringBoot 实现流控的操作方法

2024年12月03日 Java 我要评论
概述限流 简言之就是当请求达到一定的并发数或速率,就对服务进行等待、排队、降级、拒绝服务等操作。限流算法我们先简单捋一捋限流算法spring boot接口限流的常用算法及特点springboot接口限

概述

限流 简言之就是当请求达到一定的并发数或速率,就对服务进行等待、排队、降级、拒绝服务等操作。

限流算法

我们先简单捋一捋限流算法

spring boot接口限流的常用算法及特点

springboot接口限流的实现方法小结

计数器限流

漏桶算法

把水比作是请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流

令牌桶算法

可以简单地理解为医去银行办理业务,只有拿到号以后才可以进行业务办理。

系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

v1.0

上 guava

  <dependency>
            <groupid>com.google.guava</groupid>
            <artifactid>guava</artifactid>
            <version>30.1-jre</version>
        </dependency>
package com.artisan.controller;
import com.artisan.annos.artisanlimit;
import com.google.common.util.concurrent.ratelimiter;
import lombok.sneakythrows;
import lombok.extern.slf4j.slf4j;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
import java.time.localdatetime;
import java.time.format.datetimeformatter;
import java.util.concurrent.timeunit;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@slf4j
@restcontroller
@requestmapping("/ratelimit")
public class ratelimitcontroller {
    /**
     * 限流策略 : 1秒钟1个请求
     */
    private final ratelimiter limiter = ratelimiter.create(1);
    private datetimeformatter dtf = datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss");
    @sneakythrows
    @getmapping("/test")
    public string testlimiter() {
        //500毫秒内,没拿到令牌,就直接进入服务降级
        boolean tryacquire = limiter.tryacquire(500, timeunit.milliseconds);
        if (!tryacquire) {
            log.warn("boom 服务降级,时间{}", localdatetime.now().format(dtf));
            return "系统繁忙,请稍后再试!";
        }
        log.info("获取令牌成功,时间{}", localdatetime.now().format(dtf));
        return "业务处理成功";
    }

我们可以看到ratelimiter的2个核心方法:create()、tryacquire()

  • acquire() 获取一个令牌, 改方法会阻塞直到获取到这一个令牌, 返回值为获取到这个令牌花费的时间
  • acquire(int permits) 获取指定数量的令牌, 该方法也会阻塞, 返回值为获取到这 n 个令牌花费的时间
  • tryacquire() 判断时候能获取到令牌, 如果不能获取立即返回 false
  • tryacquire(int permits) 获取指定数量的令牌, 如果不能获取立即返回 false
  • tryacquire(long timeout, timeunit unit) 判断能否在指定时间内获取到令牌, 如果不能获取立即返回 false
  • tryacquire(int permits, long timeout, timeunit unit) 同上

测试一下

v2.0 自定义注解+aop实现接口限流

1.0的功能实现了,但是业务代码和限流代码混在一起,非常的不美观。

搞依赖

 <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-aop</artifactid>
        </dependency>

搞自定义限流注解

package com.artisan.annos;
import java.lang.annotation.*;
import java.util.concurrent.timeunit;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@retention(retentionpolicy.runtime)
@target(elementtype.method)
@documented
public @interface artisanlimit {
    /**
     * 资源的key,唯一
     * 作用:不同的接口,不同的流量控制
     */
    string key() default "";
    /**
     * 最多的访问限制次数
     */
    double permitspersecond();
    /**
     * 获取令牌最大等待时间
     */
    long timeout();
    /**
     * 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
     */
    timeunit timeunit() default timeunit.milliseconds;
    /**
     * 得不到令牌的提示语
     */
    string message() default "系统繁忙,请稍后再试.";
}

搞aop

使用aop切面拦截限流注解

package com.artisan.aop;
import com.artisan.annos.artisanlimit;
import com.artisan.resp.responsecode;
import com.artisan.resp.responsedata;
import com.artisan.utils.webutils;
import com.google.common.collect.maps;
import com.google.common.util.concurrent.ratelimiter;
import lombok.extern.slf4j.slf4j;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.around;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.reflect.methodsignature;
import org.springframework.stereotype.component;
import org.springframework.web.context.request.requestcontextholder;
import org.springframework.web.context.request.servletrequestattributes;
import javax.servlet.http.httpservletresponse;
import java.lang.reflect.method;
import java.util.map;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@slf4j
@aspect
@component
public class artisanlimitaop {
    /**
     * 不同的接口,不同的流量控制
     * map的key为 artisanlimit.key
     */
    private final map<string, ratelimiter> limitmap = maps.newconcurrentmap();
    @around("@annotation(com.artisan.annos.artisanlimit)")
    public object around(proceedingjoinpoint joinpoint) throws throwable {
        methodsignature signature = (methodsignature) joinpoint.getsignature();
        method method = signature.getmethod();
        //拿artisanlimit的注解
        artisanlimit limit = method.getannotation(artisanlimit.class);
        if (limit != null) {
            //key作用:不同的接口,不同的流量控制
            string key = limit.key();
            ratelimiter ratelimiter = null;
            //验证缓存是否有命中key
            if (!limitmap.containskey(key)) {
                // 创建令牌桶
                ratelimiter = ratelimiter.create(limit.permitspersecond());
                limitmap.put(key, ratelimiter);
                log.info("新建了令牌桶={},容量={}", key, limit.permitspersecond());
            }
            ratelimiter = limitmap.get(key);
            // 拿令牌
            boolean acquire = ratelimiter.tryacquire(limit.timeout(), limit.timeunit());
            // 拿不到命令,直接返回异常提示
            if (!acquire) {
                log.warn("令牌桶={},获取令牌失败", key);
                this.responsefail(limit.message());
                return null;
            }
        }
        return joinpoint.proceed();
    }
    /**
     * 直接向前端抛出异常
     *
     * @param msg 提示信息
     */
    private void responsefail(string msg) {
        httpservletresponse response = ((servletrequestattributes) requestcontextholder.getrequestattributes()).getresponse();
        responsedata<object> resultdata = responsedata.fail(responsecode.limit_error.getcode(), msg);
        webutils.writejson(response, resultdata);
    }
}

用上验证

   @getmapping("/test2")
    @artisanlimit(key = "testlimit2", permitspersecond = 1, timeout = 500, timeunit = timeunit.milliseconds, message = "test2 当前排队人数较多,请稍后再试!")
    public string test2() {
        log.info("令牌桶test2获取令牌成功");
        return "test2 ok";
    }

源码

https://github.com/yangshangwei/boot2

到此这篇关于springboot 实现流控的操作方法的文章就介绍到这了,更多相关springboot流控内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com