自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

API接口限流竟然如此簡(jiǎn)單

開發(fā) 前端
我們已經(jīng)成功實(shí)現(xiàn)了一個(gè)基于Redisson和Spring AOP的API接口限流功能。這個(gè)方案不僅簡(jiǎn)單易懂,而且非常靈活,可以通過注解輕松地應(yīng)用到任意方法上,并且支持多種限流策略(如全局限流、IP限流、集群限流等)。

簡(jiǎn)介

API接口限流是一種流量控制技術(shù),其目的是通過設(shè)置規(guī)則來(lái)限制客戶端對(duì)API接口的調(diào)用速率或總量,從而避免因過載而導(dǎo)致的服務(wù)性能下降甚至崩潰。

API限流在各種系統(tǒng)上都會(huì)有廣泛的使用場(chǎng)景,本文介紹一種非常簡(jiǎn)單的實(shí)現(xiàn)API限流的方式。

為什么需要API接口限流?

  • 防止惡意攻擊:通過限制請(qǐng)求速率,可以有效抵御DDoS等類型的攻擊。
  • 優(yōu)化資源使用:合理分配有限的計(jì)算資源給所有用戶,避免單個(gè)用戶占用過多資源。
  • 提升服務(wù)質(zhì)量:保持服務(wù)響應(yīng)時(shí)間在一個(gè)合理的范圍內(nèi),提高整體用戶體驗(yàn)。

令牌桶

常見的API限流策略有令牌桶等算法。

令牌桶算法是一種常用的流量控制和限流機(jī)制,它通過模擬一個(gè)存放“令牌”的桶來(lái)控制請(qǐng)求的速率。

這個(gè)算法的核心思想是:系統(tǒng)以恒定的速率向桶中添加令牌,而每個(gè)請(qǐng)求在被處理之前必須從桶中獲取一個(gè)令牌。如果桶中有足夠的令牌,則請(qǐng)求可以繼續(xù)執(zhí)行;如果沒有足夠的令牌(即桶為空),則請(qǐng)求要么等待直到有新的令牌產(chǎn)生,要么直接被拒絕。

實(shí)現(xiàn)API限流

這個(gè)算法很容易理解,但是要想手動(dòng)實(shí)現(xiàn)一個(gè)令牌桶算法,并不是一個(gè)容易的事情。

還需要考慮:時(shí)間精度、并發(fā)處理、存儲(chǔ)管理、可配置性等問題。

Redis是一個(gè)常用的非關(guān)系型數(shù)據(jù)庫(kù),非常適合用于緩存、實(shí)現(xiàn)限流等功能。本文介紹一個(gè)利用redis非常簡(jiǎn)單的實(shí)現(xiàn)限流的功能,采用 AOP + 注解 + Redisson 框架實(shí)現(xiàn)。

1.定義限流注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {
    /**
     * 限流key,支持使用Spring el表達(dá)式來(lái)動(dòng)態(tài)獲取方法上的參數(shù)值
     * 格式類似于  #code.id #{#code}
     */
    String key() default "";

    /**
     * 限流時(shí)間,單位秒
     */
    int time() default 60;

    /**
     * 限流次數(shù)
     */
    int count() default 100;

    /**
     * 限流類型
     */
    LimitType limitType() default LimitType.DEFAULT;

    /**
     * 提示消息
     */
    String message() default "服務(wù)器暫無(wú)資源處理新的請(qǐng)求,請(qǐng)稍后重試";
}
public enum LimitType {
    /**
     * 默認(rèn)策略全局限流
     */
    DEFAULT,

    /**
     * 根據(jù)請(qǐng)求者IP進(jìn)行限流
     */
    IP,

    /**
     * 實(shí)例限流(集群多后端實(shí)例)
     */
    CLUSTER
}

2.注解切面

@Slf4j
@Aspect
@Order(1)
public class RateLimiterAspect {
    private static final String LIMITER_KEY = "global:limiter:";

    /**
     * 定義spel表達(dá)式解析器
     */
    private final ExpressionParser parser = new SpelExpressionParser();
    /**
     * 定義spel解析模版
     */
    private final ParserContext parserContext = new TemplateParserContext();
    /**
     * 方法參數(shù)解析器
     */
    private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();

    /**
     * \@within(rateLimiter) 和 \@annotation(rateLimiter) 必須按照這個(gè)順序,才會(huì)優(yōu)先執(zhí)行方法上的注解
     */
    @Before("@within(rateLimiter) || @annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
        if (rateLimiter == null) {
            // 如果方法上沒有,就從類上獲取注解
            Class<?> targetClass = point.getTarget().getClass();
            rateLimiter = targetClass.getAnnotation(RateLimiter.class);
            if (rateLimiter == null) {
                // 如果還是沒有獲取到注解,直接返回
                return;
            }
        }
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        try {
            String combineKey = getCombineKey(rateLimiter, point);
            RateType rateType = RateType.OVERALL;
            if (rateLimiter.limitType() == LimitType.CLUSTER) {
                rateType = RateType.PER_CLIENT;
            }
            long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
            if (number == -1) {
                throw new RateLimiterException(rateLimiter.message());
            }
            log.debug("限制令牌 => {}, 剩余令牌 => {}, 緩存key => '{}'", count, number, combineKey);
        } catch (Exception e) {
            if (e instanceof RateLimiterException) {
                throw e;
            } else {
                throw new RuntimeException("服務(wù)器限流異常,請(qǐng)稍候再試", e);
            }
        }
    }

    private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        String key = rateLimiter.key();
        // 判斷 key 不為空 和 不是表達(dá)式
        if (StringUtils.hasText(key) && key.contains("#")) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method targetMethod = signature.getMethod();
            Object[] args = point.getArgs();
            MethodBasedEvaluationContext context =
                new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
            context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getBeanFactory()));
            Expression expression;
            if (key.startsWith(parserContext.getExpressionPrefix()) && key.endsWith(parserContext.getExpressionSuffix())) {
                expression = parser.parseExpression(key, parserContext);
            } else {
                expression = parser.parseExpression(key);
            }
            key = expression.getValue(context, String.class);
        }
        StringBuilder str = new StringBuilder(LIMITER_KEY);
        HttpServletRequest request = getRequest();
        str.append(request.getRequestURI()).append(":");
        if (rateLimiter.limitType() == LimitType.IP) {
            // 獲取請(qǐng)求ip
            str.append(ServletUtil.getClientIP(request)).append(":");
        } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
            // 獲取客戶端實(shí)例id
            str.append(RedisUtils.getClient().getId()).append(":");
        }
        return str.append(key).toString();
    }

    /**
     * 獲取request
     */
    private HttpServletRequest getRequest() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes.getRequest();
        } catch (Exception e) {
            return null;
        }
    }
}

3.Redisson 限流工具類

public class RedisUtils {

    private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class);

    /**
     * 限流
     *
     * @param key          限流key
     * @param rateType     限流類型
     * @param rate         速率
     * @param rateInterval 速率間隔
     * @return -1 表示失敗
     */
    public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
        RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
        // 如果限流器存在
        if (rateLimiter.isExists()) {
            // 獲取上次限流的配置信息
            RateLimiterConfig rateLimiterConfig = rateLimiter.getConfig();
            // 如果rateLimiterConfig的配置跟我們注解上面的值不一致,說(shuō)明服務(wù)器重啟過,程序員又修改了限流的配置
            if (TimeUnit.SECONDS.convert(rateLimiterConfig.getRateInterval(), TimeUnit.MILLISECONDS) != rateInterval || rateLimiterConfig.getRate() != rate) {
                rateLimiter.delete();
                rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
            }
        }
        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
        if (rateLimiter.tryAcquire()) {
            return rateLimiter.availablePermits();
        } else {
            return -1L;
        }
    }
    
    /**
     * 獲取客戶端實(shí)例
     */
    public static RedissonClient getClient() {
        return CLIENT;
    }
}

4.捕獲異常

@Data
@EqualsAndHashCode(callSuper = true)
public class RateLimiterException extends RuntimeException {

    /**
     * 錯(cuò)誤提示
     */
    private final String message;

    public RateLimiterException(String message) {
        this.message = message;
    }

}
@Slf4j
@Order(1)
@RestControllerAdvice
public class LimiterExceptionHandler {

    /**
     * 限流異常
     */
    @ExceptionHandler({RateLimiterException.class})
    public Map<String, Object> handleRateLimiterException(RateLimiterException e, HttpServletRequest request) {
        log.error("請(qǐng)求地址'{}', 限流異常'{}'", request.getRequestURI(), e.getMessage());
        return result(e.getMessage());
    }

    private Map<String, Object> result(String msg) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 500);
        result.put("msg", msg);
        return result;
    }
}

到這里,已經(jīng)實(shí)現(xiàn)了一個(gè)完整的API接口限流功能。

可以將之進(jìn)一步封裝,作為一個(gè)springboot的starter,用于任意一個(gè)項(xiàng)目中。

小結(jié)

通過上述步驟,我們已經(jīng)成功實(shí)現(xiàn)了一個(gè)基于Redisson和Spring AOP的API接口限流功能。這個(gè)方案不僅簡(jiǎn)單易懂,而且非常靈活,可以通過注解輕松地應(yīng)用到任意方法上,并且支持多種限流策略(如全局限流、IP限流、集群限流等)。

責(zé)任編輯:武曉燕 來(lái)源: Java技術(shù)指北
相關(guān)推薦

2020-12-28 07:47:35

動(dòng)態(tài)代理AOP

2024-08-28 08:42:21

API接口限流

2021-12-09 09:02:53

JavaPDF文件iText

2021-12-08 10:36:46

JavaPDF文件

2024-09-09 11:35:35

2022-08-12 12:19:13

Cluster檢索集群

2020-06-19 17:49:23

建網(wǎng)

2018-08-27 08:31:25

InnoDBMySQL

2020-02-20 16:07:45

IT需求

2022-01-09 23:38:42

通信協(xié)議網(wǎng)絡(luò)

2022-02-23 20:42:40

HTMLmarkdownturndown

2022-07-08 14:35:05

Java組件LiteFlow

2021-05-14 07:45:07

Sentinel 接口限流

2010-08-25 21:50:36

配置DHCP

2020-11-24 08:02:26

API接口重構(gòu)

2023-08-21 08:01:03

2011-10-11 10:53:29

Ubuntu 11.1Gnome 3.2

2009-04-29 01:39:57

破解美萍萬(wàn)象

2021-03-30 10:46:42

SpringBoot計(jì)數(shù)器漏桶算法

2011-09-15 10:35:12

Android應(yīng)用IOS應(yīng)用著裝搭配
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)