最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot + Redis 實現(xiàn)API接口限流的幾種方法

 更新時間:2026年01月27日 10:14:22   作者:十月初七丶  
本文主要介紹了SpringBoot + Redis 實現(xiàn)API接口限流的幾種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

了解Redis

Redis(Remote Dictionary Server)是一個開源的高性能鍵值對存儲數(shù)據(jù)庫。它支持多種數(shù)據(jù)結構,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。Redis的特點包括:

  • 內(nèi)存存儲:Redis將數(shù)據(jù)存儲在內(nèi)存中,因此讀寫速度非??欤m用于對性能有較高要求的場景。
  • 持久化:Redis支持持久化將內(nèi)存中的數(shù)據(jù)保存到硬盤上,以便在服務器重啟后能夠恢復數(shù)據(jù)。
  • 數(shù)據(jù)結構多樣:Redis不僅僅支持簡單的鍵值對存儲,還支持豐富的數(shù)據(jù)結構,例如列表、集合、有序集合等,使其具備更多的功能和用途。
  • 高并發(fā):Redis是單線程模型,通過使用異步I/O和非阻塞I/O來支持高并發(fā)。
  • 多語言支持:Redis支持多種編程語言的客戶端,如Java、Python、C#等,便于開發(fā)人員在不同平臺上使用。
  • 發(fā)布/訂閱:Redis支持發(fā)布/訂閱模式,允許客戶端訂閱一個或多個頻道并接收對應頻道的消息。
  • 事務支持:Redis支持事務,可以在一個事務中執(zhí)行多個命令,并保證這些命令的原子性。

由于Redis具有高性能、靈活的數(shù)據(jù)結構和豐富的功能,它被廣泛用于緩存、消息隊列、計數(shù)器、實時排行榜、會話管理等多種應用場景。

需求&為什么需要接口限流

需求:針對相同IP,60s的接口請求次數(shù)不能超過10000次

接口限流是為了保護系統(tǒng)和服務,防止因為過多的請求而導致系統(tǒng)過載、性能下降甚至崩潰。以下是進行接口限流的幾個主要原因:

  • 防止惡意攻擊:接口限流可以防止惡意用戶或者攻擊者通過大量的請求來攻擊系統(tǒng),保護系統(tǒng)的穩(wěn)定性和安全性。
  • 保護系統(tǒng)資源:對于一些計算密集型或者資源消耗較大的接口,限制請求的頻率可以避免服務器資源被過度消耗,保障其他正常請求的處理。
  • 避免雪崩效應:當某個服務不可用或者響應時間過長時,如果沒有限流措施,大量請求可能會涌入后端,導致更多的請求失敗,產(chǎn)生雪崩效應。
  • 提升系統(tǒng)性能:限流可以控制并發(fā)請求數(shù),避免過多的請求導致服務器負載過高,從而提升系統(tǒng)的整體性能和響應速度。
  • 提供公平資源分配:通過限流,可以實現(xiàn)對不同用戶或者不同服務請求的公平分配,避免某些請求占用過多資源而影響其他請求。

綜上所述,進行接口限流是保護系統(tǒng)和提升性能的重要手段,對于高并發(fā)的系統(tǒng)尤為重要。通過合理設置限流策略,可以有效地平衡資源利用和系統(tǒng)穩(wěn)定性,提供更好的用戶體驗。

實現(xiàn)方案

方案一:固定時間段

思路:

當用戶在第一次訪問該接口時,向Redis中設置一個包含了用戶IP和接口方法名的key,value的值初始化為1(表示第一次訪問當前接口),同時設置該key的過期時間(60秒),只要此Redis的key沒有過期,每次訪問都將value的值自增1次,用戶每次訪問接口前,先從Redis中拿到當前接口訪問次數(shù),如果發(fā)現(xiàn)訪問次數(shù)大于規(guī)定的次數(shù)(超過10000次),則向用戶返回接口訪問失敗的標識。

實現(xiàn):

(一)攔截器

1、添加Redis依賴:首先在pom.xml文件中添加Spring Data Redis依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、 配置Redis連接信息:在application.propertiesapplication.yml中配置Redis的連接信息,包括主機、端口、密碼等。

3、創(chuàng)建限流攔截器:在項目中創(chuàng)建一個限流攔截器,用于對用戶IP進行接口限流。攔截器可以實現(xiàn)HandlerInterceptor接口,并重寫preHandle方法進行限流邏輯。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

public class RateLimitInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ipAddress = getIpAddress(request);
        String uri = request.getRequestURI().replace("/","_");
        String key = "apiVisits:" + uri + ":" + ipAddress;

        // 判斷是否已經(jīng)達到限流次數(shù)
        String value = redisTemplate.opsForValue().get(key);
        // key 不存在,則是第一次請求設置過期時間
        if(StringUtils.isBlank(value)){
            redisTemplate.opsForValue().increment(key, 1);
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
            return true;
        }
        if (value != null && Integer.parseInt(value) > 10) {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            return false;
        }

        // 未達到限流次數(shù),自增
        redisTemplate.opsForValue().increment(key, 1);
        return true;
    }

    private String getIpAddress(HttpServletRequest request) {
        // 從請求頭或代理頭中獲取真實IP地址
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }
        return ipAddress;
    }
}

4、注冊攔截器:在配置類中注冊自定義的限流攔截器。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private RateLimitInterceptor rateLimitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/**");
    }
}

(二)AOP

以注解+切面的方式實現(xiàn),將需要進行限流的API加上注解即可

1、創(chuàng)建注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentLimiting {
    
    /**
     * 緩存key
     */
    String key() default "apiVisits:";

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

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

2、創(chuàng)建AOP切面

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class CurrentLimitingAspect {

    private final RedisTemplate redisTemplate;

    /**
     * 帶有注解的方法之前執(zhí)行
     */
    @SuppressWarnings("unchecked")
    @Before("@annotation(currentLimiting)")
    public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable {
        int time = currentLimiting.time();
        int count = currentLimiting.count();
        // 將接口方法和用戶IP構建Redis的key
        String key = getCurrentLimitingKey(currentLimiting.key(), point);
        
        // 判斷是否已經(jīng)達到限流次數(shù)
        String value = redisTemplate.opsForValue().get(key);
        if (value != null && Integer.parseInt(value) > count) {
            log.error("接口限流,key:{},count:{},currentCount:{}", key, count, value);
            throw new RuntimeException("訪問過于頻繁,請稍后再試!");
        }
        // 未達到限流次數(shù),自增
        redisTemplate.opsForValue().increment(key, 1);
        // key 不存在,則是第一次請求設置過期時間
        if(StringUtils.isBlank(value)){
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
    }

    /**
     * 組裝 redis 的 key
     */
    private String getCurrentLimitingKey(String prefixKey,JoinPoint point) {
        StringBuilder sb = new StringBuilder(prefixKey);
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        sb.append( Utils.getIpAddress(request) );
        
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        return sb.append("_").append( targetClass.getName() )
                .append("_").append(method.getName()).toString();
    }
}

缺陷:

當在10:00訪問接口,這個時候向Reids寫入一條數(shù)據(jù)訪問次數(shù)為1,在10:59的時候突然訪問了9999次,然后redis過期,在11:00訪問了9999次,這樣出現(xiàn)的問題就是在10:59到11:00之間訪問了9999+9999次。故以固定時間段的方式進行限流可能會不起作用,會存在Reids過期的臨界點內(nèi)造成大量的用戶訪問。

方案二:滑動窗口

思路:

由于方案一的時間是固定的,我們可以把固定的時間段改成動態(tài)的,也就是在用戶每次訪問接口時,記錄當前用戶訪問的時間點(時間戳),并計算前一分鐘內(nèi)用戶訪問該接口的總次數(shù)。如果總次數(shù)大于限流次數(shù),則不允許用戶訪問該接口。這樣就能保證在任意時刻用戶的訪問次數(shù)不會超過10000次。

實現(xiàn):

1、創(chuàng)建注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentLimiting {
    
    /**
     * 緩存key
     */
    String key() default "apiVisits:";

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

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

2、創(chuàng)建AOP切面

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class CurrentLimitingAspect {

    private final RedisTemplate redisTemplate;

    /**
     * 帶有注解的方法之前執(zhí)行
     */
    @SuppressWarnings("unchecked")
    @Before("@annotation(currentLimiting)")
    public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable {
        int time = currentLimiting.time();
        int count = currentLimiting.count();
        // 將接口方法和用戶IP構建Redis的key
        String key = getCurrentLimitingKey(currentLimiting.key(), point);
        
        // 使用Zset的 score 設置成用戶訪問接口的時間戳
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        // 當前時間戳
        long currentTime = System.currentTimeMillis();
        zSetOperations.add(key, currentTime, currentTime);

        // 設置過期時間防止key不消失
        redisTemplate.expire(key, time, TimeUnit.SECONDS);

        // 移除 time 秒之前的訪問記錄,動態(tài)時間段
        zSetOperations.removeRangeByScore(key, 0, currentTime - time * 1000);
        
        // 獲得當前時間窗口內(nèi)的訪問記錄數(shù)
        Long currentCount = zSetOperations.zCard(key);
        // 限流判斷
        if (currentCount > count) {
            log.error("接口限流,key:{},count:{},currentCount:{}", key, count, currentCount);
            throw new RuntimeException("訪問過于頻繁,請稍后再試!");
        }
    }

    /**
     * 組裝 redis 的 key
     */
    private String getCurrentLimitingKey(String prefixKey,JoinPoint point) {
        StringBuilder sb = new StringBuilder(prefixKey);
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        sb.append( Utils.getIpAddress(request) );
        
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        return sb.append("_").append( targetClass.getName() )
                .append("_").append(method.getName()).toString();
    }
}

到此這篇關于SpringBoot + Redis 實現(xiàn)API接口限流的幾種方法的文章就介紹到這了,更多相關SpringBoot Redis API接口限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • springboot使用hibernate validation對參數(shù)校驗的實現(xiàn)方法

    springboot使用hibernate validation對參數(shù)校驗的實現(xiàn)方法

    這篇文章主要介紹了spring-boot 使用hibernate validation對參數(shù)進行優(yōu)雅的校驗,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • shiro之INI配置詳解

    shiro之INI配置詳解

    這篇文章主要為大家詳細介紹了shiro之INI配置的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • MyBatis中resultMap和resultType的區(qū)別詳解

    MyBatis中resultMap和resultType的區(qū)別詳解

    這篇文章主要介紹了MyBatis中resultMap和resultType的區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • 解決Sentinel鏈路模式規(guī)則無效問題

    解決Sentinel鏈路模式規(guī)則無效問題

    本文介紹了如何在Spring Cloud Alibaba項目中使用Sentinel鏈路流控規(guī)則,并解決規(guī)則不生效的問題,通過關閉Sentinel過濾器,可以避免重復統(tǒng)計請求
    2025-01-01
  • 基于google zxing的Java二維碼生成與解碼

    基于google zxing的Java二維碼生成與解碼

    這篇文章主要為大家詳細介紹了基于google zxing的Java二維碼生成與解碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Java實現(xiàn)的AES256加密解密功能示例

    Java實現(xiàn)的AES256加密解密功能示例

    這篇文章主要介紹了Java實現(xiàn)的AES256加密解密功能,結合完整實例形式分析了Java實現(xiàn)AES256加密解密功能的步驟與相關操作技巧,需要的朋友可以參考下
    2017-02-02
  • StringUtils中的isEmpty、isNotEmpty、isBlank和isNotBlank的區(qū)別詳解

    StringUtils中的isEmpty、isNotEmpty、isBlank和isNotBlank的區(qū)別詳解

    這篇文章主要介紹了StringUtils中的isEmpty、isNotEmpty、isBlank和isNotBlank的區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • java使用@Scheduled注解執(zhí)行定時任務

    java使用@Scheduled注解執(zhí)行定時任務

    這篇文章主要給大家介紹了關于java使用@Scheduled注解執(zhí)行定時任務的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • IDEA全量替換一次性解決舊項目并將所有文件換行符改為LF問題

    IDEA全量替換一次性解決舊項目并將所有文件換行符改為LF問題

    這篇文章主要介紹了IDEA全量替換一次性解決舊項目并將所有文件換行符改為LF問題,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下
    2019-05-05
  • spring注解@Import用法詳解

    spring注解@Import用法詳解

    這篇文章主要介紹了spring注解@Import用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-10-10

最新評論

河北省| 叙永县| 沙洋县| 秭归县| 永清县| 云林县| 德惠市| 乌拉特前旗| 武鸣县| 拜城县| 长顺县| 上犹县| 菏泽市| 原阳县| 连云港市| 江阴市| 长顺县| 华安县| 三明市| 禄劝| 健康| 达州市| 余姚市| 吴旗县| 枣庄市| 瓦房店市| 凤冈县| 英山县| 周宁县| 阳城县| 凤山市| 逊克县| 正蓝旗| 保康县| 边坝县| 保德县| 和田市| 井陉县| 弋阳县| 新源县| 武乡县|