成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

API接口限流竟然如此簡單

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

簡介

API接口限流是一種流量控制技術,其目的是通過設置規則來限制客戶端對API接口的調用速率或總量,從而避免因過載而導致的服務性能下降甚至崩潰。

API限流在各種系統上都會有廣泛的使用場景,本文介紹一種非常簡單的實現API限流的方式。

為什么需要API接口限流?

  • 防止惡意攻擊:通過限制請求速率,可以有效抵御DDoS等類型的攻擊。
  • 優化資源使用:合理分配有限的計算資源給所有用戶,避免單個用戶占用過多資源。
  • 提升服務質量:保持服務響應時間在一個合理的范圍內,提高整體用戶體驗。

令牌桶

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

令牌桶算法是一種常用的流量控制和限流機制,它通過模擬一個存放“令牌”的桶來控制請求的速率。

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

實現API限流

這個算法很容易理解,但是要想手動實現一個令牌桶算法,并不是一個容易的事情。

還需要考慮:時間精度、并發處理、存儲管理、可配置性等問題。

Redis是一個常用的非關系型數據庫,非常適合用于緩存、實現限流等功能。本文介紹一個利用redis非常簡單的實現限流的功能,采用 AOP + 注解 + Redisson 框架實現。

1.定義限流注解

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

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

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

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

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

    /**
     * 根據請求者IP進行限流
     */
    IP,

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

2.注解切面

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

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

    /**
     * \@within(rateLimiter) 和 \@annotation(rateLimiter) 必須按照這個順序,才會優先執行方法上的注解
     */
    @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("服務器限流異常,請稍候再試", e);
            }
        }
    }

    private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        String key = rateLimiter.key();
        // 判斷 key 不為空 和 不是表達式
        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) {
            // 獲取請求ip
            str.append(ServletUtil.getClientIP(request)).append(":");
        } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
            // 獲取客戶端實例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的配置跟我們注解上面的值不一致,說明服務器重啟過,程序員又修改了限流的配置
            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;
        }
    }
    
    /**
     * 獲取客戶端實例
     */
    public static RedissonClient getClient() {
        return CLIENT;
    }
}

4.捕獲異常

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

    /**
     * 錯誤提示
     */
    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("請求地址'{}', 限流異常'{}'", 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;
    }
}

到這里,已經實現了一個完整的API接口限流功能。

可以將之進一步封裝,作為一個springboot的starter,用于任意一個項目中。

小結

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

責任編輯:武曉燕 來源: Java技術指北
相關推薦

2020-12-28 07:47:35

動態代理AOP

2024-08-28 08:42:21

API接口限流

2021-12-08 10:36:46

JavaPDF文件

2021-12-09 09:02:53

JavaPDF文件iText

2024-09-09 11:35:35

2022-08-12 12:19:13

Cluster檢索集群

2020-06-19 17:49:23

建網

2018-08-27 08:31:25

InnoDBMySQL

2020-02-20 16:07:45

IT需求

2022-01-09 23:38:42

通信協議網絡

2022-02-23 20:42:40

HTMLmarkdownturndown

2022-07-08 14:35:05

Java組件LiteFlow

2010-08-25 21:50:36

配置DHCP

2021-05-14 07:45:07

Sentinel 接口限流

2011-10-11 10:53:29

Ubuntu 11.1Gnome 3.2

2020-11-24 08:02:26

API接口重構

2009-04-29 01:39:57

破解美萍萬象

2023-08-21 08:01:03

2011-09-15 10:35:12

Android應用IOS應用著裝搭配

2024-04-07 00:00:00

億級數據ES
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产黄色网 | 欧美日韩免费一区二区三区 | 激情六月丁香婷婷 | 成人福利网站 | 日韩av一区二区在线观看 | 特级毛片 | 国产精品欧美一区二区三区不卡 | 日韩字幕一区 | 国产精品久久久久久一区二区三区 | 午夜精品久久久久久久久久久久久 | 亚洲一区二区三区在线免费 | 国产综合久久 | 久久精品16 | 国产精品亚洲一区 | 成人欧美一区二区三区在线播放 | 91婷婷韩国欧美一区二区 | 在线视频国产一区 | 蜜桃精品视频在线 | 欧美午夜激情在线 | 国产午夜精品一区二区三区嫩草 | 精品一区国产 | 九九热这里只有精品在线观看 | 青娱乐自拍 | 北条麻妃一区二区三区在线视频 | 精品一区二区在线观看 | 毛片免费观看视频 | 放个毛片看看 | 欧美乱操 | 久久久成人免费一区二区 | 特黄特黄a级毛片免费专区 av网站免费在线观看 | 亚洲一本 | 久久久久久久电影 | 国产高清免费视频 | 高清视频一区二区三区 | 2020亚洲天堂 | 久久99深爱久久99精品 | 久久精品91久久久久久再现 | 99精品国产一区二区三区 | 国产精品精品视频一区二区三区 | 日韩在线观看一区 | 一级毛片免费完整视频 |