Redisson多策略註解限流

来源:https://www.cnblogs.com/weloe/archive/2022/09/20/16709718.html
-Advertisement-
Play Games

限流:使用Redisson的RRateLimiter進行限流 多策略:map+函數式介面優化if判斷 自定義註解 /** * aop限流註解 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.R ...


限流:使用Redisson的RRateLimiter進行限流

多策略:map+函數式介面優化if判斷

自定義註解

/**
 * aop限流註解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisLimit {


    String prefix() default "rateLimit:";

    //限流唯一標示
    String key() default "";

    //限流單位時間(單位為s)
    int time() default 1;

    //單位時間內限制的訪問次數
    int count();

    //限流類型
    LimitType type() default LimitType.CUSTOM;

}

定義限流類型

public enum LimitType {

    /**
     * 自定義key
     */
    CUSTOM,

    /**
     * 請求者IP
     */
    IP,

    /**
     * 方法級別限流
     * key = ClassName+MethodName
     */
    METHOD,

    /**
     * 參數級別限流
     * key = ClassName+MethodName+Params
     */
    PARAMS,

    /**
     * 用戶級別限流
     * key = ClassName+MethodName+Params+UserId
     */
    USER,

    /**
     * 根據request的uri限流
     * key = Request_uri
     */
    REQUEST_URI,

    /**
     * 對requesturi+userId限流
     * key = Request_uri+UserId
     */
    REQUESTURI_USERID,


    /**
     * 對userId限流
     * key = userId
     */
    SINGLEUSER,

    /**
     * 對方法限流
     * key = ClassName+MethodName
     */
    SINGLEMETHOD,

    /**
     * 對uri+params限流
     * key = uri+params
     */
    REQUEST_URI_PARAMS,

    /**
     * 對uri+params+userId限流
     * key = uri+params+userId
     */
    REQUEST_URI_PARAMS_USERID;
    
}

生成key的工具類

根據類型生成鎖的對象(key)的工具類,使用map+函數式介面優化if,其中BaseContext是一個獲取用戶唯一標識userId的工具類

@Component
public class ProceedingJoinPointUtil {
    @Autowired
    private HttpServletRequest request;

    private Map<LimitType, Function<ProceedingJoinPoint,String>> functionMap = new HashMap<>(9);

    @PostConstruct
    void initMap(){
        //初始化策略
        functionMap.put(LimitType.METHOD, this::getMethodTypeKey);
        functionMap.put(LimitType.PARAMS, this::getParamsTypeKey);
        functionMap.put(LimitType.USER, this::getUserTypeKey);
        functionMap.put(LimitType.REQUEST_URI,proceedingJoinPoint ->
                request.getRequestURI());
        functionMap.put(LimitType.REQUESTURI_USERID, proceedingJoinPoint ->
                request.getRequestURI()+BaseContext.getUserId());
        functionMap.put(LimitType.REQUEST_URI_PARAMS,proceedingJoinPoint ->
                request.getRequestURI()+getParams(proceedingJoinPoint));
        functionMap.put(LimitType.REQUEST_URI_PARAMS_USERID,proceedingJoinPoint ->
                request.getRequestURI()+getParams(proceedingJoinPoint)+BaseContext.getUserId());
        functionMap.put(LimitType.SINGLEUSER,(proceedingJoinPoint)->
                String.valueOf(BaseContext.getUserId()));
        functionMap.put(LimitType.SINGLEMETHOD,(proceedingJoinPoint -> {
            StringBuilder sb = new StringBuilder();
            appendMthodName(proceedingJoinPoint,sb);
            return sb.toString();
        }));
    }

    public Object getKey(ProceedingJoinPoint joinPoint, RedisLimit redisLimit) {
        //根據限制類型生成key
        Object generateKey = "";
        //自定義
        if(redisLimit.type() != LimitType.CUSTOM){
            generateKey = generateKey(redisLimit.type(), joinPoint);
        }else {
            //非自定義
            generateKey = redisLimit.key();
        }
        return generateKey;
    }

    /**
     * 根據LimitType生成key
     * @param type
     * @param joinPoint
     * @return
     */
    private Object generateKey(LimitType type , ProceedingJoinPoint joinPoint) {
        Function function = functionMap.get(type);
        Object result = function.apply(joinPoint);
        return result;
    }

    /**
     * 方法級別
     * key = ClassName+MethodName
     * @param joinPoint
     * @return
     */
    private String getMethodTypeKey(ProceedingJoinPoint joinPoint){
        StringBuilder sb = new StringBuilder();
        appendMthodName(joinPoint, sb);
        return sb.toString();
    }



    /**
     * 參數級別
     * key = ClassName+MethodName+Params
     * @param joinPoint
     * @return
     */
    private String getParamsTypeKey(ProceedingJoinPoint joinPoint){
        StringBuilder sb = new StringBuilder();
        appendMthodName(joinPoint, sb);
        appendParams(joinPoint, sb);
        return sb.toString();
    }



    /**
     * 用戶級別
     * key = ClassName+MethodName+Params+UserId
     */
    private String getUserTypeKey(ProceedingJoinPoint joinPoint){
        StringBuilder sb = new StringBuilder();
        appendMthodName(joinPoint, sb);
        appendParams(joinPoint, sb);
        //獲取userId
        appendUserId(sb);
        return sb.toString();
    }


    /**
     * StringBuilder添加類名和方法名
     * @param joinPoint
     * @param sb
     */
    private void appendMthodName(ProceedingJoinPoint joinPoint, StringBuilder sb) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        sb.append(joinPoint.getTarget().getClass().getName())//類名
                .append(method.getName());//方法名
    }

    /**
     * StringBuilder添加方法參數值
     * @param joinPoint
     * @param sb
     */
    private void appendParams(ProceedingJoinPoint joinPoint, StringBuilder sb) {
        for (Object o : joinPoint.getArgs()) {
            sb.append(o.toString());
        }
    }

    private String getParams(ProceedingJoinPoint joinPoint) {
        StringBuilder sb = new StringBuilder();
        for (Object o : joinPoint.getArgs()) {
            if(o instanceof MultipartFile){
                try {
                    ImageTypeCheck.getImgHeightAndWidth(((MultipartFile) o).getInputStream());
                } catch (IOException e) {
                    throw new BusinessException("MultipartFile輸入流獲取失敗,source:ProceedingJoinPointUtils.149",USER_PRINCIPAL_EMAIL);
                }
            }else {
                sb.append(o.toString());
            }
        }
        return sb.toString();
    }

    /**
     * StringBuilder添加UserId
     * @param sb
     */
    private void appendUserId(StringBuilder sb) {
        sb.append(BaseContext.getUserId());
    }
}

定義aop具體邏輯

@Aspect
@Component
@Slf4j
public class RedisLimitAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ProceedingJoinPointUtil proceedingJoinPointUtil;

    @Pointcut("@annotation(com.cat.www.aop.limit.anno.RedisLimit)")
    private void pointCut() {
    }

    @Around("pointCut() && @annotation(redisLimit)")
    private Object around(ProceedingJoinPoint joinPoint, RedisLimit redisLimit) {
        Object generateKey = proceedingJoinPointUtil.getKey(joinPoint, redisLimit);
        //redis key
        String key = redisLimit.prefix() +generateKey.toString();
        //聲明一個限流器
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);

        //設置速率,time秒中產生count個令牌
        rateLimiter.trySetRate(RateType.OVERALL, redisLimit.count(), redisLimit.time(), RateIntervalUnit.SECONDS);

        // 試圖獲取一個令牌,獲取到返回true
        boolean tryAcquire = rateLimiter.tryAcquire();
        if (!tryAcquire) {
            return new ResultData<>().FAILED().setResultIns("訪問過於頻繁");
        }
        Object obj = null;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException();
        }

        return obj;
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 筆者最近在看 你不知道的JavaScript上捲,裡面關於 this 的講解個人覺得非常精彩。JavaScript 中的 this 算是一個核心的概念,有一些同學會對其有點模糊和小恐懼,究其原因,現在對 this 討論的文章很多,讓我們覺得 this 無規律可尋,就像一個幽靈一樣 如果你還沒弄懂 t ...
  • 在面向對象的程式設計中,模塊之間交互採用介面編程,通常情況下調用方不需要知道被調用方的內部實現細節,因為一旦涉及到了具體實現,如果需要換一種實現就需要修改代碼,這違反了程式設計的"開閉原則"。所以我們一般有兩種選擇:一種是使用API(Application Programming Interface ...
  • 我的設計模式之旅。本節學習了適配器模式。從程式調用第三方庫時遇到的問題著手,思考如何讓兩個不關聯的類一起工作。並嘗試使用C#運用適配器模式解決方釘與圓孔問題。 ...
  • 上個月我寫的一篇文章《關於技術能力的思考和總結》引起了大家的關註,好多讀者的評論“以寫代想、以想促真、以講驗真”,大家的感受很深刻,基於上次的文章,這篇文章我其實更想跟大家聊聊一些常用的思考方法,思考問題的方式對了,往往可以幫助大家少走彎路。 ...
  • 現在的很多程式應用,基本上都是需要多端覆蓋,因此基於一個Web API的後端介面,來構建多端應用,如微信、H5、APP、WInForm、BS的Web管理端等都是常見的應用。本篇隨筆概括性的介紹基於HBuilderX+UniApp+ThorUI的手機端前端開發處理,總結一下開發工具的設置,以及常見的H... ...
  • 前言 秒殺請求在高度集中在某一個時間點。這樣一來,就會導致一 個特別高的流量峰值,它對資源的消耗是瞬時的 。能夠搶到商品的人數是有限的,也就是說10人和1000人發 起請求的結果都是一樣的。也就是說真正開始下單時,秒殺請求並不是越多越好。 一、秒殺中的削峰 猶豫伺服器的處理資源是恆定的,用或者不用它 ...
  • 隨著需求開發迭代,代碼庫規模逐漸變大,新的團隊成員引入等諸多因素,系統起初制定的架構規則不可避免遭到破壞。不僅僅是破壞團隊的統一開發規範,更為重要的是隨著代碼庫規模逐漸增長,大大降低系統的可維護性、擴展性,增加評審複雜度和重構成本,也最終導致團隊生產力下降以及研發成本增長。 在敏捷開發環境下,系統... ...
  • 摘要:零售企業就需要安全、可信、開放、能力強大的PaaS集成平臺支撐自身的雲業務,同樣也需要一個強大的業務系統來承載業務。 疫情又來了,買買買,趕緊囤。 這麼快沒貨了? 疫情反覆態勢之下,消費者體驗到的商品到達速度和多樣的產品選擇,以及平臺面臨的跨區調貨和各種渠道的線上流量突增等現狀,使得消費品及零 ...
一周排行
    -Advertisement-
    Play Games
  • 一個自定義WPF窗體的解決方案,借鑒了呂毅老師的WPF製作高性能的透明背景的異形視窗一文,併在此基礎上增加了滑鼠穿透的功能。可以使得透明窗體的滑鼠事件穿透到下層,在下層窗體中響應。 ...
  • 在C#中使用RabbitMQ做個簡單的發送郵件小項目 前言 好久沒有做項目了,這次做一個發送郵件的小項目。發郵件是一個比較耗時的操作,之前在我的個人博客裡面回覆評論和友鏈申請是會通過發送郵件來通知對方的,不過當時只是簡單的進行了非同步操作。 那麼這次來使用RabbitMQ去統一發送郵件,我的想法是通過 ...
  • 當你使用Edge等瀏覽器或系統軟體播放媒體時,Windows控制中心就會出現相應的媒體信息以及控制播放的功能,如圖。 SMTC (SystemMediaTransportControls) 是一個Windows App SDK (舊為UWP) 中提供的一個API,用於與系統媒體交互。接入SMTC的好 ...
  • 最近在微軟商店,官方上架了新款Win11風格的WPF版UI框架【WPF Gallery Preview 1.0.0.0】,這款應用引入了前沿的Fluent Design UI設計,為用戶帶來全新的視覺體驗。 ...
  • 1.簡單使用實例 1.1 添加log4net.dll的引用。 在NuGet程式包中搜索log4net並添加,此次我所用版本為2.0.17。如下圖: 1.2 添加配置文件 右鍵項目,添加新建項,搜索選擇應用程式配置文件,命名為log4net.config,步驟如下圖: 1.2.1 log4net.co ...
  • 之前也分享過 Swashbuckle.AspNetCore 的使用,不過版本比較老了,本次演示用的示例版本為 .net core 8.0,從安裝使用開始,到根據命名空間分組顯示,十分的有用 ...
  • 在 Visual Studio 中,至少可以創建三種不同類型的類庫: 類庫(.NET Framework) 類庫(.NET 標準) 類庫 (.NET Core) 雖然第一種是我們多年來一直在使用的,但一直感到困惑的一個主要問題是何時使用 .NET Standard 和 .NET Core 類庫類型。 ...
  • WPF的按鈕提供了Template模板,可以通過修改Template模板中的內容對按鈕的樣式進行自定義。結合資源字典,可以將自定義資源在xaml視窗、自定義控制項或者整個App當中調用 ...
  • 實現了一個支持長短按得按鈕組件,單擊可以觸發Click事件,長按可以觸發LongPressed事件,長按鬆開時觸發LongClick事件。還可以和自定義外觀相結合,實現自定義的按鈕外形。 ...
  • 一、WTM是什麼 WalkingTec.Mvvm框架(簡稱WTM)最早開發與2013年,基於Asp.net MVC3 和 最早的Entity Framework, 當初主要是為瞭解決公司內部開發效率低,代碼風格不統一的問題。2017年9月,將代碼移植到了.Net Core上,併進行了深度優化和重構, ...