日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

redis + AOP + 自定义注解实现接口限流

發(fā)布時(shí)間:2024/1/3 windows 29 coder
生活随笔 收集整理的這篇文章主要介紹了 redis + AOP + 自定义注解实现接口限流 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

限流介紹

限流(rate limiting)

? 是指在一定時(shí)間內(nèi),對(duì)某些資源的訪問(wèn)次數(shù)進(jìn)行限制,以避免資源被濫用或過(guò)度消耗。限流可以防止服務(wù)器崩潰、保證用戶體驗(yàn)、提高系統(tǒng)可用性。

限流的方法有很多種,常見(jiàn)的有以下幾種:

  • 漏桶算法:

    ? 漏桶算法通過(guò)一個(gè)固定大小的漏桶來(lái)模擬流量,當(dāng)流量進(jìn)入漏桶時(shí),會(huì)以恒定的速率從漏桶中流出。如果流量超過(guò)漏桶的容量,則會(huì)被丟棄。

  • 令牌桶算法:

    ? 令牌桶算法通過(guò)一個(gè)固定大小的令牌桶來(lái)模擬流量,當(dāng)流量進(jìn)入令牌桶時(shí),會(huì)從令牌桶中取出一個(gè)令牌。如果令牌桶中沒(méi)有令牌,則會(huì)拒絕該流量。

  • 滑動(dòng)窗口算法:

    ? 滑動(dòng)窗口算法通過(guò)一個(gè)固定大小的滑動(dòng)窗口來(lái)模擬流量,當(dāng)流量進(jìn)入滑動(dòng)窗口時(shí),會(huì)統(tǒng)計(jì)窗口內(nèi)流量的數(shù)量。如果窗口內(nèi)流量的數(shù)量超過(guò)了一定的閾值,則會(huì)拒絕該流量。

限流可以應(yīng)用在很多場(chǎng)景,例如:

  • 防止服務(wù)器崩潰:當(dāng)服務(wù)器的請(qǐng)求量過(guò)大時(shí),可以通過(guò)限流來(lái)防止服務(wù)器崩潰。

  • 保證用戶體驗(yàn):當(dāng)用戶請(qǐng)求某個(gè)資源的頻率過(guò)高時(shí),可以通過(guò)限流來(lái)降低用戶的等待時(shí)間。

  • 提高系統(tǒng)可用性:當(dāng)系統(tǒng)的某些資源被濫用或過(guò)度消耗時(shí),可以通過(guò)限流來(lái)提高系統(tǒng)的可用性。

?

? 限流是一個(gè)非常重要的技術(shù),它可以幫助我們提高系統(tǒng)的穩(wěn)定性和可用性。在實(shí)際開(kāi)發(fā)中,我們可以根據(jù)不同的場(chǎng)景選擇合適的限流算法。

我們定義的注解使用到技術(shù):redis,redisson,AOP,自定義注解等

依賴

用到的部分依賴,這里沒(méi)有指定版本,可根據(jù)市場(chǎng)上的版本進(jìn)行配置

        <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
        </dependency>

       <!-- Spring框架基本的核心工具 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <!-- SpringWeb模塊 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>

        <!-- 自定義驗(yàn)證注解 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--常用工具類 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!-- servlet包 -->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
        </dependency>

      <!-- hutool 工具類 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-http</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-extra</artifactId>
        </dependency>


       <!-- lombok 工具類 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--  自動(dòng)生成YML配置關(guān)聯(lián)JSON文件  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>


        <!--  版本升級(jí)  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-properties-migrator</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--  代碼生產(chǎn)工具  -->
        <dependency>
            <groupId>io.github.linpeilie</groupId>
            <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        </dependency>

        <!-- 離線IP地址定位庫(kù) -->
        <dependency>
            <groupId>org.lionsoul</groupId>
            <artifactId>ip2region</artifactId>
        </dependency>

1,定義限流類型

這里定義限流枚舉類:LimitType

public enum LimitType {
    /**
     * 默認(rèn)策略全局限流
     */
    DEFAULT,

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

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

2,定義注解 RateLimiter

定義注解,在后續(xù)的代碼中使用進(jìn)行限流

import java.lang.annotation.*;

/**
 * 限流注解
 *
 * @author Lion Li
 */
@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;

    /**
     * 提示消息 支持國(guó)際化 格式為 {code}
     */
    String message() default "{rate.limiter.message}";
}

redis 工具類

這里提供一下第 3 步需要的redis 工具類,可以根據(jù)自己的需求進(jìn)行部分方法進(jìn)行復(fù)制。

@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedisUtils {

    private static final RedissonClient CLIENT = SpringUtils.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);
        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
        if (rateLimiter.tryAcquire()) {
            return rateLimiter.availablePermits();
        } else {
            return -1L;
        }
    }

    /**
     * 獲取客戶端實(shí)例
     */
    public static RedissonClient getClient() {
        return CLIENT;
    }

    /**
     * 發(fā)布通道消息
     *
     * @param channelKey 通道key
     * @param msg        發(fā)送數(shù)據(jù)
     * @param consumer   自定義處理
     */
    public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
        RTopic topic = CLIENT.getTopic(channelKey);
        topic.publish(msg);
        consumer.accept(msg);
    }

    public static <T> void publish(String channelKey, T msg) {
        RTopic topic = CLIENT.getTopic(channelKey);
        topic.publish(msg);
    }

    /**
     * 訂閱通道接收消息
     *
     * @param channelKey 通道key
     * @param clazz      消息類型
     * @param consumer   自定義處理
     */
    public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
        RTopic topic = CLIENT.getTopic(channelKey);
        topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
    }

    /**
     * 緩存基本的對(duì)象,Integer、String、實(shí)體類等
     *
     * @param key   緩存的鍵值
     * @param value 緩存的值
     */
    public static <T> void setCacheObject(final String key, final T value) {
        setCacheObject(key, value, false);
    }

    /**
     * 緩存基本的對(duì)象,保留當(dāng)前對(duì)象 TTL 有效期
     *
     * @param key       緩存的鍵值
     * @param value     緩存的值
     * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后還是為90)
     * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
     */
    public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
        RBucket<T> bucket = CLIENT.getBucket(key);
        if (isSaveTtl) {
            try {
                bucket.setAndKeepTTL(value);
            } catch (Exception e) {
                long timeToLive = bucket.remainTimeToLive();
                setCacheObject(key, value, Duration.ofMillis(timeToLive));
            }
        } else {
            bucket.set(value);
        }
    }

    /**
     * 緩存基本的對(duì)象,Integer、String、實(shí)體類等
     *
     * @param key      緩存的鍵值
     * @param value    緩存的值
     * @param duration 時(shí)間
     */
    public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
        RBatch batch = CLIENT.createBatch();
        RBucketAsync<T> bucket = batch.getBucket(key);
        bucket.setAsync(value);
        bucket.expireAsync(duration);
        batch.execute();
    }

    /**
     * 如果不存在則設(shè)置 并返回 true 如果存在則返回 false
     *
     * @param key   緩存的鍵值
     * @param value 緩存的值
     * @return set成功或失敗
     */
    public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
        RBucket<T> bucket = CLIENT.getBucket(key);
        return bucket.setIfAbsent(value, duration);
    }

    /**
     * 如果存在則設(shè)置 并返回 true 如果存在則返回 false
     *
     * @param key   緩存的鍵值
     * @param value 緩存的值
     * @return set成功或失敗
     */
    public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
        RBucket<T> bucket = CLIENT.getBucket(key);
        return bucket.setIfExists(value, duration);
    }

    /**
     * 注冊(cè)對(duì)象監(jiān)聽(tīng)器
     * <p>
     * key 監(jiān)聽(tīng)器需開(kāi)啟 `notify-keyspace-events` 等 redis 相關(guān)配置
     *
     * @param key      緩存的鍵值
     * @param listener 監(jiān)聽(tīng)器配置
     */
    public static <T> void addObjectListener(final String key, final ObjectListener listener) {
        RBucket<T> result = CLIENT.getBucket(key);
        result.addListener(listener);
    }

    /**
     * 設(shè)置有效時(shí)間
     *
     * @param key     Redis鍵
     * @param timeout 超時(shí)時(shí)間
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public static boolean expire(final String key, final long timeout) {
        return expire(key, Duration.ofSeconds(timeout));
    }

    /**
     * 設(shè)置有效時(shí)間
     *
     * @param key      Redis鍵
     * @param duration 超時(shí)時(shí)間
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public static boolean expire(final String key, final Duration duration) {
        RBucket rBucket = CLIENT.getBucket(key);
        return rBucket.expire(duration);
    }

    /**
     * 獲得緩存的基本對(duì)象。
     *
     * @param key 緩存鍵值
     * @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
     */
    public static <T> T getCacheObject(final String key) {
        RBucket<T> rBucket = CLIENT.getBucket(key);
        return rBucket.get();
    }

    /**
     * 獲得key剩余存活時(shí)間
     *
     * @param key 緩存鍵值
     * @return 剩余存活時(shí)間
     */
    public static <T> long getTimeToLive(final String key) {
        RBucket<T> rBucket = CLIENT.getBucket(key);
        return rBucket.remainTimeToLive();
    }

    /**
     * 刪除單個(gè)對(duì)象
     *
     * @param key 緩存的鍵值
     */
    public static boolean deleteObject(final String key) {
        return CLIENT.getBucket(key).delete();
    }

    /**
     * 刪除集合對(duì)象
     *
     * @param collection 多個(gè)對(duì)象
     */
    public static void deleteObject(final Collection collection) {
        RBatch batch = CLIENT.createBatch();
        collection.forEach(t -> {
            batch.getBucket(t.toString()).deleteAsync();
        });
        batch.execute();
    }

    /**
     * 檢查緩存對(duì)象是否存在
     *
     * @param key 緩存的鍵值
     */
    public static boolean isExistsObject(final String key) {
        return CLIENT.getBucket(key).isExists();
    }

    /**
     * 緩存List數(shù)據(jù)
     *
     * @param key      緩存的鍵值
     * @param dataList 待緩存的List數(shù)據(jù)
     * @return 緩存的對(duì)象
     */
    public static <T> boolean setCacheList(final String key, final List<T> dataList) {
        RList<T> rList = CLIENT.getList(key);
        return rList.addAll(dataList);
    }

    /**
     * 追加緩存List數(shù)據(jù)
     *
     * @param key  緩存的鍵值
     * @param data 待緩存的數(shù)據(jù)
     * @return 緩存的對(duì)象
     */
    public static <T> boolean addCacheList(final String key, final T data) {
        RList<T> rList = CLIENT.getList(key);
        return rList.add(data);
    }

    /**
     * 注冊(cè)List監(jiān)聽(tīng)器
     * <p>
     * key 監(jiān)聽(tīng)器需開(kāi)啟 `notify-keyspace-events` 等 redis 相關(guān)配置
     *
     * @param key      緩存的鍵值
     * @param listener 監(jiān)聽(tīng)器配置
     */
    public static <T> void addListListener(final String key, final ObjectListener listener) {
        RList<T> rList = CLIENT.getList(key);
        rList.addListener(listener);
    }

    /**
     * 獲得緩存的list對(duì)象
     *
     * @param key 緩存的鍵值
     * @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
     */
    public static <T> List<T> getCacheList(final String key) {
        RList<T> rList = CLIENT.getList(key);
        return rList.readAll();
    }

    /**
     * 獲得緩存的list對(duì)象(范圍)
     *
     * @param key  緩存的鍵值
     * @param form 起始下標(biāo)
     * @param to   截止下標(biāo)
     * @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
     */
    public static <T> List<T> getCacheListRange(final String key, int form, int to) {
        RList<T> rList = CLIENT.getList(key);
        return rList.range(form, to);
    }

    /**
     * 緩存Set
     *
     * @param key     緩存鍵值
     * @param dataSet 緩存的數(shù)據(jù)
     * @return 緩存數(shù)據(jù)的對(duì)象
     */
    public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
        RSet<T> rSet = CLIENT.getSet(key);
        return rSet.addAll(dataSet);
    }

    /**
     * 追加緩存Set數(shù)據(jù)
     *
     * @param key  緩存的鍵值
     * @param data 待緩存的數(shù)據(jù)
     * @return 緩存的對(duì)象
     */
    public static <T> boolean addCacheSet(final String key, final T data) {
        RSet<T> rSet = CLIENT.getSet(key);
        return rSet.add(data);
    }

    /**
     * 注冊(cè)Set監(jiān)聽(tīng)器
     * <p>
     * key 監(jiān)聽(tīng)器需開(kāi)啟 `notify-keyspace-events` 等 redis 相關(guān)配置
     *
     * @param key      緩存的鍵值
     * @param listener 監(jiān)聽(tīng)器配置
     */
    public static <T> void addSetListener(final String key, final ObjectListener listener) {
        RSet<T> rSet = CLIENT.getSet(key);
        rSet.addListener(listener);
    }

    /**
     * 獲得緩存的set
     *
     * @param key 緩存的key
     * @return set對(duì)象
     */
    public static <T> Set<T> getCacheSet(final String key) {
        RSet<T> rSet = CLIENT.getSet(key);
        return rSet.readAll();
    }

    /**
     * 緩存Map
     *
     * @param key     緩存的鍵值
     * @param dataMap 緩存的數(shù)據(jù)
     */
    public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            RMap<String, T> rMap = CLIENT.getMap(key);
            rMap.putAll(dataMap);
        }
    }

    /**
     * 注冊(cè)Map監(jiān)聽(tīng)器
     * <p>
     * key 監(jiān)聽(tīng)器需開(kāi)啟 `notify-keyspace-events` 等 redis 相關(guān)配置
     *
     * @param key      緩存的鍵值
     * @param listener 監(jiān)聽(tīng)器配置
     */
    public static <T> void addMapListener(final String key, final ObjectListener listener) {
        RMap<String, T> rMap = CLIENT.getMap(key);
        rMap.addListener(listener);
    }

    /**
     * 獲得緩存的Map
     *
     * @param key 緩存的鍵值
     * @return map對(duì)象
     */
    public static <T> Map<String, T> getCacheMap(final String key) {
        RMap<String, T> rMap = CLIENT.getMap(key);
        return rMap.getAll(rMap.keySet());
    }

    /**
     * 獲得緩存Map的key列表
     *
     * @param key 緩存的鍵值
     * @return key列表
     */
    public static <T> Set<String> getCacheMapKeySet(final String key) {
        RMap<String, T> rMap = CLIENT.getMap(key);
        return rMap.keySet();
    }

    /**
     * 往Hash中存入數(shù)據(jù)
     *
     * @param key   Redis鍵
     * @param hKey  Hash鍵
     * @param value 值
     */
    public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        RMap<String, T> rMap = CLIENT.getMap(key);
        rMap.put(hKey, value);
    }

    /**
     * 獲取Hash中的數(shù)據(jù)
     *
     * @param key  Redis鍵
     * @param hKey Hash鍵
     * @return Hash中的對(duì)象
     */
    public static <T> T getCacheMapValue(final String key, final String hKey) {
        RMap<String, T> rMap = CLIENT.getMap(key);
        return rMap.get(hKey);
    }

    /**
     * 刪除Hash中的數(shù)據(jù)
     *
     * @param key  Redis鍵
     * @param hKey Hash鍵
     * @return Hash中的對(duì)象
     */
    public static <T> T delCacheMapValue(final String key, final String hKey) {
        RMap<String, T> rMap = CLIENT.getMap(key);
        return rMap.remove(hKey);
    }

    /**
     * 刪除Hash中的數(shù)據(jù)
     *
     * @param key   Redis鍵
     * @param hKeys Hash鍵
     */
    public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
        RBatch batch = CLIENT.createBatch();
        RMapAsync<String, T> rMap = batch.getMap(key);
        for (String hKey : hKeys) {
            rMap.removeAsync(hKey);
        }
        batch.execute();
    }

    /**
     * 獲取多個(gè)Hash中的數(shù)據(jù)
     *
     * @param key   Redis鍵
     * @param hKeys Hash鍵集合
     * @return Hash對(duì)象集合
     */
    public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
        RMap<K, V> rMap = CLIENT.getMap(key);
        return rMap.getAll(hKeys);
    }

    /**
     * 設(shè)置原子值
     *
     * @param key   Redis鍵
     * @param value 值
     */
    public static void setAtomicValue(String key, long value) {
        RAtomicLong atomic = CLIENT.getAtomicLong(key);
        atomic.set(value);
    }

    /**
     * 獲取原子值
     *
     * @param key Redis鍵
     * @return 當(dāng)前值
     */
    public static long getAtomicValue(String key) {
        RAtomicLong atomic = CLIENT.getAtomicLong(key);
        return atomic.get();
    }

    /**
     * 遞增原子值
     *
     * @param key Redis鍵
     * @return 當(dāng)前值
     */
    public static long incrAtomicValue(String key) {
        RAtomicLong atomic = CLIENT.getAtomicLong(key);
        return atomic.incrementAndGet();
    }

    /**
     * 遞減原子值
     *
     * @param key Redis鍵
     * @return 當(dāng)前值
     */
    public static long decrAtomicValue(String key) {
        RAtomicLong atomic = CLIENT.getAtomicLong(key);
        return atomic.decrementAndGet();
    }

    /**
     * 獲得緩存的基本對(duì)象列表
     *
     * @param pattern 字符串前綴
     * @return 對(duì)象列表
     */
    public static Collection<String> keys(final String pattern) {
        Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
        return stream.collect(Collectors.toList());
    }

    /**
     * 刪除緩存的基本對(duì)象列表
     *
     * @param pattern 字符串前綴
     */
    public static void deleteKeys(final String pattern) {
        CLIENT.getKeys().deleteByPattern(pattern);
    }

    /**
     * 檢查redis中是否存在key
     *
     * @param key 鍵
     */
    public static Boolean hasKey(String key) {
        RKeys rKeys = CLIENT.getKeys();
        return rKeys.countExists(key) > 0;
    }
}

獲取i18n資源文件

提供一下第 3 步需要 獲取i18n資源文件 類,可以做國(guó)際化進(jìn)行處理,如果項(xiàng)目沒(méi)有國(guó)際化,這個(gè)可以省略

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MessageUtils {

    private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);

    /**
     * 根據(jù)消息鍵和參數(shù) 獲取消息 委托給spring messageSource
     *
     * @param code 消息鍵
     * @param args 參數(shù)
     * @return 獲取國(guó)際化翻譯值
     */
    public static String message(String code, Object... args) {
        try {
            return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
        } catch (NoSuchMessageException e) {
            return code;
        }
    }
}

自定義異常

這個(gè)我們?cè)僮远x一個(gè)業(yè)務(wù)異常類,用于拋出異常 ,如果自己項(xiàng)目之前有定義,也可以使用自己的異常類

ServiceException

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class ServiceException extends RuntimeException {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 錯(cuò)誤碼
     */
    private Integer code;

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

    /**
     * 錯(cuò)誤明細(xì),內(nèi)部調(diào)試錯(cuò)誤
     */
    private String detailMessage;

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

    public ServiceException(String message, Integer code) {
        this.message = message;
        this.code = code;
    }

    public String getDetailMessage() {
        return detailMessage;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public Integer getCode() {
        return code;
    }

    public ServiceException setMessage(String message) {
        this.message = message;
        return this;
    }

    public ServiceException setDetailMessage(String detailMessage) {
        this.detailMessage = detailMessage;
        return this;
    }
}

客戶端工具類

如果對(duì) ip 進(jìn)行限流,在注解處理中會(huì)用到參數(shù),ip ,url 等信息

ServletUtils

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ServletUtils extends JakartaServletUtil {
     /**
     * 獲取request
     */
    public static HttpServletRequest getRequest() {
        try {
            return getRequestAttributes().getRequest();
        } catch (Exception e) {
            return null;
        }
    }
    
     public static String getClientIP() {
        return getClientIP(getRequest());
    }
}

3,處理限流注解

處理限流注解:RateLimiterAspect

對(duì)注解處理的核心代碼就在這里,

@Slf4j
@Aspect
public class RateLimiterAspect {

    /**
     * 定義spel表達(dá)式解析器
     */
    private final ExpressionParser parser = new SpelExpressionParser();
    /**
     * 定義spel解析模版
     */
    private final ParserContext parserContext = new TemplateParserContext();
    /**
     * 定義spel上下文對(duì)象進(jìn)行解析
     */
    private final EvaluationContext context = new StandardEvaluationContext();
    /**
     * 方法參數(shù)解析器
     */
    private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();


    /**
     * GLOBAL_REDIS_KEY 和  RATE_LIMIT_KEY  最好還是定義在項(xiàng)目的一個(gè)統(tǒng)一的常量文件中,這里為了解剖出來(lái)的文件少一點(diǎn)
     *
     * */

    /**
     * 全局 redis key (業(yè)務(wù)無(wú)關(guān)的key)
     */
    private final String GLOBAL_REDIS_KEY = "global:";

    /**
     * 限流 redis key
     */
    private final String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
        // 獲取注解傳的 時(shí)間 次數(shù)
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        // 處理 key
        String combineKey = getCombineKey(rateLimiter, point);
        try {
            RateType rateType = RateType.OVERALL;
            if (rateLimiter.limitType() == LimitType.CLUSTER) {
                rateType = RateType.PER_CLIENT;
            }
            long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
            if (number == -1) {
                String message = rateLimiter.message();
                if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
                    message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
                }
                throw new ServiceException(message);
            }
            log.info("限制令牌 => {}, 剩余令牌 => {}, 緩存key => '{}'", count, number, combineKey);
        } catch (Exception e) {
            if (e instanceof ServiceException) {
                throw e;
            } else {
                throw new RuntimeException("服務(wù)器限流異常,請(qǐng)稍候再試");
            }
        }
    }


    /**
     * 返回帶有特定前綴的 key 
     * @param rateLimiter 限流注解
     * @param point 切入點(diǎn)
     * @return key
     */
    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        String key = rateLimiter.key();
        // 獲取方法(通過(guò)方法簽名來(lái)獲取)
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        // 判斷是否是spel格式
        if (StringUtils.containsAny(key, "#")) {
            // 獲取參數(shù)值
            Object[] args = point.getArgs();
            // 獲取方法上參數(shù)的名稱
            String[] parameterNames = pnd.getParameterNames(method);
            if (ArrayUtil.isEmpty(parameterNames)) {
                throw new ServiceException("限流key解析異常!請(qǐng)聯(lián)系管理員!");
            }
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            // 解析返回給key
            try {
                Expression expression;
                if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
                    && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
                    expression = parser.parseExpression(key, parserContext);
                } else {
                    expression = parser.parseExpression(key);
                }
                key = expression.getValue(context, String.class) + ":";
            } catch (Exception e) {
                throw new ServiceException("限流key解析異常!請(qǐng)聯(lián)系管理員!");
            }
        }
        // 限流前綴key
        StringBuilder stringBuffer = new StringBuilder(RATE_LIMIT_KEY);
        stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
        // 判斷限流類型
        if (rateLimiter.limitType() == LimitType.IP) {
            // 獲取請(qǐng)求ip
            stringBuffer.append(ServletUtils.getClientIP()).append(":");
        } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
            // 獲取客戶端實(shí)例id
            stringBuffer.append(RedisUtils.getClient().getId()).append(":");
        }
        return stringBuffer.append(key).toString();
    }
}

到這里注解就定義好了,接下來(lái)就可以進(jìn)行測(cè)試和使用!!!

測(cè)試限流

定義一個(gè) Controller 來(lái)測(cè)試限流,這里返回的 R ,可以根據(jù)自己項(xiàng)目統(tǒng)一定義的返回,或者使用 void

RedisRateLimiterController

@Slf4j
@RestController
@RequestMapping("/demo/rateLimiter")
public class RedisRateLimiterController {

    /**
     * 測(cè)試全局限流
     * 全局影響
     */
    @RateLimiter(count = 2, time = 10)
    @GetMapping("/test")
    public R<String> test(String value) {
        return R.ok("操作成功", value);
    }

    /**
     * 測(cè)試請(qǐng)求IP限流
     * 同一IP請(qǐng)求受影響
     */
    @RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
    @GetMapping("/testip")
    public R<String> testip(String value) {
        return R.ok("操作成功", value);
    }

    /**
     * 測(cè)試集群實(shí)例限流
     * 啟動(dòng)兩個(gè)后端服務(wù)互不影響
     */
    @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
    @GetMapping("/testcluster")
    public R<String> testcluster(String value) {
        return R.ok("操作成功", value);
    }

    /**
     * 測(cè)試請(qǐng)求IP限流(key基于參數(shù)獲取)
     * 同一IP請(qǐng)求受影響
     *
     * 簡(jiǎn)單變量獲取 #變量 復(fù)雜表達(dá)式 #{#變量 != 1 ? 1 : 0}
     */
    @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
    @GetMapping("/testObj")
    public R<String> testObj(String value) {
        return R.ok("操作成功", value);
    }

}

如果代碼寫(xiě)的有問(wèn)題,歡迎大家評(píng)論交流,進(jìn)行指點(diǎn)!!!

也希望大家點(diǎn)個(gè)關(guān)注哦~~~~~~~~

總結(jié)

以上是生活随笔為你收集整理的redis + AOP + 自定义注解实现接口限流的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。