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

SpringBoot整合Redis實(shí)現(xiàn)分布式限流功能

 更新時(shí)間:2026年07月03日 08:43:45   作者:張老師技術(shù)棧  
線上服務(wù)最怕被惡意刷接口或者突發(fā)流量把系統(tǒng)打垮,單機(jī)限流用 Guava RateLimiter 就行,但微服務(wù)多實(shí)例部署時(shí)必須用分布式限流——所有實(shí)例共享同一個(gè)限流狀態(tài),因此本文給大家介紹了SpringBoot整合Redis實(shí)現(xiàn)分布式限流功能,需要的朋友可以參考下

引言

線上服務(wù)最怕被惡意刷接口或者突發(fā)流量把系統(tǒng)打垮。單機(jī)限流用 Guava RateLimiter 就行,但微服務(wù)多實(shí)例部署時(shí)必須用分布式限流——所有實(shí)例共享同一個(gè)限流狀態(tài)。

一、限流的常見算法

算法原理特點(diǎn)
固定窗口1 分鐘內(nèi)最多 N 個(gè)請(qǐng)求簡(jiǎn)單但有突刺問題
滑動(dòng)窗口把窗口切小段,更平滑推薦,較常用
漏桶請(qǐng)求進(jìn)桶,勻速流出平滑流量,但突發(fā)處理慢
令牌桶勻速放令牌,拿到的才通過允許一定突發(fā),最均衡

Redis 實(shí)現(xiàn)限流的本質(zhì): 用 Redis 計(jì)數(shù)器/有序集合記錄請(qǐng)求次數(shù),加上過期時(shí)間自動(dòng)清理。

二、固定窗口限流

最簡(jiǎn)單的方案:一個(gè) key 記錄 1 分鐘內(nèi)的請(qǐng)求數(shù)。

@Component
public class FixedWindowRateLimiter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 固定窗口限流
     * @param key   限流 key(如 "rate:user:1001")
     * @param max    窗口內(nèi)最大請(qǐng)求數(shù)
     * @param window 窗口大?。耄?
     * @return true 通過,false 被限
     */
    public boolean tryAcquire(String key, long max, long window) {
        // INCR 原子自增
        Long count = redisTemplate.opsForValue().increment(key);
        if (count == 1) {
            // 第一次訪問,設(shè)置過期時(shí)間
            redisTemplate.expire(key, window, TimeUnit.SECONDS);
        }
        return count <= max;
    }
}
@RestController
public class TestController {

    @Autowired
    private FixedWindowRateLimiter rateLimiter;

    @GetMapping("/api/test")
    public ResultVO<?> test() {
        // 限制每個(gè)用戶每秒最多 5 次
        if (!rateLimiter.tryAcquire("rate:user:1001", 5, 1)) {
            return ResultVO.error(429, "請(qǐng)求太頻繁,請(qǐng)稍后");
        }
        return ResultVO.success("成功");
    }
}

問題: 窗口邊界會(huì)有突刺——比如第 59 秒和第 61 秒各發(fā) 5 個(gè)請(qǐng)求,理論上通過 10 個(gè),但看起來像 1 秒內(nèi) 10 個(gè)。

三、滑動(dòng)窗口限流(推薦)

用 Redis ZSet(有序集合)記錄每個(gè)請(qǐng)求的時(shí)間戳,滑動(dòng)窗口更精確。

@Component
public class SlidingWindowRateLimiter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 滑動(dòng)窗口限流
     * @param key      限流 key
     * @param max      窗口內(nèi)最大請(qǐng)求數(shù)
     * @param window   窗口大?。ê撩耄?
     * @return true 通過,false 被限
     */
    public boolean tryAcquire(String key, long max, long windowMs) {
        long now = System.currentTimeMillis();
        long windowStart = now - windowMs;

        // 使用 Lua 腳本保證原子性
        String luaScript =
            "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1]) " +    // 移除窗口外的記錄
            "local count = redis.call('ZCARD', KEYS[1]) " +             // 統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù)
            "if count < tonumber(ARGV[2]) then " +
            "  redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3]) " +        // 加入當(dāng)前請(qǐng)求
            "  redis.call('EXPIRE', KEYS[1], ARGV[4]) " +               // 設(shè)置過期(秒)
            "  return 1 " +
            "else " +
            "  return 0 " +
            "end";

        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(luaScript, Long.class),
            Collections.singletonList(key),
            String.valueOf(windowStart),
            String.valueOf(max),
            String.valueOf(now),
            String.valueOf(windowMs / 1000 + 1)
        );

        return Long.valueOf(1).equals(result);
    }
}

四、注解式限流

把限流邏輯封裝成注解,用在需要的接口上,代碼更干凈。

1. 定義注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {

    /** 限流 key(支持 SpEL) */
    String key() default "";

    /** 限流維度:用戶、IP、接口 */
    String type() default "user";  // user、ip、api

    /** 最大請(qǐng)求數(shù) */
    long max() default 10;

    /** 窗口大小(秒) */
    long window() default 1;
}

2. 實(shí)現(xiàn) AOP 切面

@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private SlidingWindowRateLimiter rateLimiter;

    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
        // 構(gòu)建 key
        String key = buildKey(rateLimit, pjp);

        // 執(zhí)行限流
        boolean acquired = rateLimiter.tryAcquire(
                key, rateLimit.max(), rateLimit.window() * 1000);

        if (!acquired) {
            throw new RuntimeException("請(qǐng)求太頻繁,請(qǐng)稍后重試");
        }

        return pjp.proceed();
    }

    private String buildKey(RateLimit rateLimit, ProceedingJoinPoint pjp) {
        String prefix = "rate:";

        switch (rateLimit.type()) {
            case "ip":
                // 從請(qǐng)求中獲取 IP(需要注入 RequestContextHolder)
                HttpServletRequest request = ((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();
                String ip = request.getRemoteAddr();
                return prefix + "ip:" + ip;

            case "api":
                // 按接口限流
                String methodName = pjp.getSignature().toShortString();
                return prefix + "api:" + methodName;

            default:
                // 按用戶限流(需要從 Token 獲取用戶 ID)
                String userId = StpUtil.getLoginIdAsString();
                return prefix + "user:" + userId;
        }
    }
}

// 全局異常處理
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResultVO<?> handle(RuntimeException e) {
        if (e.getMessage().contains("請(qǐng)求太頻繁")) {
            return ResultVO.error(429, "請(qǐng)求太頻繁,請(qǐng)稍后重試");
        }
        return ResultVO.error(500, e.getMessage());
    }
}

3. 使用

@RestController
@RequestMapping("/api")
public class OrderController {

    @PostMapping("/order")
    @RateLimit(key = "createOrder", type = "user", max = 5, window = 10)
    public ResultVO<?> createOrder(@RequestBody OrderDTO dto) {
        // 每個(gè)用戶 10 秒內(nèi)最多 5 次下單
        return ResultVO.success("下單成功");
    }

    @GetMapping("/product/list")
    @RateLimit(type = "api", max = 100, window = 1)
    public ResultVO<?> list() {
        // 接口級(jí)別限流:每秒最多 100 次
        return ResultVO.success("商品列表");
    }
}

五、秒殺系統(tǒng)的限流

在秒殺系統(tǒng)中,限流是保護(hù)系統(tǒng)的第一道防線:

@RestController
@RequestMapping("/api/seckill")
public class SeckillController {

    @Autowired
    private SeckillService seckillService;

    @PostMapping("/do/{productId}")
    @RateLimit(type = "user", max = 1, window = 10)    // 每個(gè)用戶10秒只能秒殺1次
    public ResultVO<?> doSeckill(@PathVariable Long productId) {
        long userId = StpUtil.getLoginIdAsLong();
        return seckillService.doSeckill(productId, userId, "用戶");
    }
}

秒殺系統(tǒng)的多層防護(hù):

用戶請(qǐng)求
  ↓
① 限流(Redis 滑動(dòng)窗口)→ 被限返回"操作頻繁"
  ↓
② 參數(shù)校驗(yàn) → 不合法直接返回
  ↓
③ 時(shí)間校驗(yàn) → 沒開始/已結(jié)束直接返回
  ↓
④ 重復(fù)提交校驗(yàn)(SETNX)→ 已提交直接返回
  ↓
⑤ Redis 預(yù)減庫(kù)存 → 庫(kù)存不足直接返回
  ↓
⑥ 數(shù)據(jù)庫(kù)扣庫(kù)存(樂觀鎖)→ 失敗回滾
  ↓
⑦ 生成訂單

限流在最外層,把大部分無效請(qǐng)求擋在門外。

六、IP 限流

@GetMapping("/api/public")
@RateLimit(type = "ip", max = 30, window = 60)
public ResultVO<?> publicApi() {
    // 同一 IP 每分鐘最多 30 次
    return ResultVO.success("公開接口");
}

獲取真實(shí) IP(經(jīng)過 Nginx 代理時(shí)):

public static String getRealIp(HttpServletRequest request) {
    String ip = request.getHeader("X-Forwarded-For");
    if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
        ip = request.getHeader("X-Real-IP");
    }
    if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
        ip = request.getRemoteAddr();
    }
    // 多個(gè)代理時(shí)取第一個(gè)
    if (ip != null && ip.contains(",")) {
        ip = ip.split(",")[0].trim();
    }
    return ip;
}

七、限流配置管理

把限流參數(shù)放在配置中心或 yml 中,不用改代碼:

rate-limit:
  defaults:
    max: 10
    window: 1
  rules:
    - key: "createOrder"
      max: 5
      window: 10
    - key: "seckill"
      max: 1
      window: 10
    - key: "publicApi"
      max: 30
      window: 60
@ConfigurationProperties(prefix = "rate-limit")
@Data
public class RateLimitProperties {
    private Rule defaults = new Rule();
    private List<Rule> rules = new ArrayList<>();

    @Data
    public static class Rule {
        private String key;
        private long max;
        private long window;
    }
}

八、限流效果驗(yàn)證

# 快速請(qǐng)求測(cè)試限流效果
for i in {1..20}; do
  curl -X POST http://localhost:9090/api/order
  echo ""
done

# 正常返回:{"code":200,"message":"下單成功"}
# 被限返回:{"code":429,"message":"請(qǐng)求太頻繁,請(qǐng)稍后重試"}

九、限流 vs 熔斷 vs 降級(jí)

限流 Ratelimit熔斷 CircuitBreaker降級(jí) Degrade
作用控制請(qǐng)求速率防止故障擴(kuò)散舍棄非核心功能
時(shí)機(jī)請(qǐng)求進(jìn)來時(shí)錯(cuò)誤率超標(biāo)時(shí)資源不足時(shí)
工具Redis + LuaSentinel、Resilience4jSentinel、Hystrix

建議: 限流是必須的,熔斷和降級(jí)在大流量的核心系統(tǒng)上建議加上。

以上就是SpringBoot整合Redis實(shí)現(xiàn)分布式限流功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Redis分布式限流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Mybatis實(shí)現(xiàn)批量刪除和多條件查詢方式

    Mybatis實(shí)現(xiàn)批量刪除和多條件查詢方式

    本文介紹了批量刪除功能的實(shí)現(xiàn)步驟,包括后臺(tái)代碼的修改,具體涉及到mapper層、Service層、Controller接口等層面的修改,同時(shí)也介紹了多條件查詢功能的實(shí)現(xiàn),具體包括創(chuàng)建封裝查詢條件的類,mapper層、Service層、Controller接口的修改等
    2026-05-05
  • Java8如何優(yōu)雅的記錄代碼運(yùn)行時(shí)間

    Java8如何優(yōu)雅的記錄代碼運(yùn)行時(shí)間

    這篇文章主要為大家詳細(xì)介紹了 Java 8 中幾種記錄代碼運(yùn)行時(shí)間的優(yōu)雅方式,并附上實(shí)用工具類與建議,希望可以幫助大家提高大家的代碼可讀性與復(fù)用性
    2025-04-04
  • Java IO流對(duì)文件File操作

    Java IO流對(duì)文件File操作

    這篇文章主要介紹了Java IO流對(duì)文件File操作,java封裝的一個(gè)操作文件及文件夾(目錄)的對(duì)象??梢圆僮鞔疟P上的任何一個(gè)文件和文件夾
    2022-12-12
  • Java高級(jí)用法中的JNA類型映射注意細(xì)節(jié)及使用問題

    Java高級(jí)用法中的JNA類型映射注意細(xì)節(jié)及使用問題

    本文介紹了在使用JNA方法映射中應(yīng)該注意的一些細(xì)節(jié)和具體的使用問題,對(duì)java??JNA類型映射注意細(xì)節(jié)感興趣的朋友一起看看吧
    2022-04-04
  • Java數(shù)組動(dòng)態(tài)擴(kuò)容的實(shí)現(xiàn)示例

    Java數(shù)組動(dòng)態(tài)擴(kuò)容的實(shí)現(xiàn)示例

    本文主要介紹了Java數(shù)組動(dòng)態(tài)擴(kuò)容的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-12-12
  • 基于Java ActiveMQ的實(shí)例講解

    基于Java ActiveMQ的實(shí)例講解

    下面小編就為大家?guī)硪黄贘ava ActiveMQ的實(shí)例講解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • Java之idea @NotNull @Nullable 注解使用

    Java之idea @NotNull @Nullable 注解使用

    這篇文章主要介紹了Java之idea @NotNull @Nullable 注解使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • SpringBoot AOP如何配置全局事務(wù)

    SpringBoot AOP如何配置全局事務(wù)

    這篇文章主要介紹了SpringBoot AOP如何配置全局事務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java實(shí)現(xiàn)雙色球抽獎(jiǎng)隨機(jī)算法示例

    Java實(shí)現(xiàn)雙色球抽獎(jiǎng)隨機(jī)算法示例

    本篇文章主要介紹了Java實(shí)現(xiàn)雙色球抽獎(jiǎng)隨機(jī)算法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • jdk中動(dòng)態(tài)代理異常處理分析:UndeclaredThrowableException

    jdk中動(dòng)態(tài)代理異常處理分析:UndeclaredThrowableException

    最近在工作中遇到了報(bào)UndeclaredThrowableException的錯(cuò)誤,通過查找相關(guān)的資料,終于解決了,所以這篇文章主要給大家介紹了關(guān)于jdk中動(dòng)態(tài)代理異常處理分析:UndeclaredThrowableException的相關(guān)資料,需要的朋友可以參考下
    2018-04-04

最新評(píng)論

龙口市| 九龙县| 阜新| 大石桥市| 成安县| 六枝特区| 康保县| 古浪县| 于田县| 鹿邑县| 平潭县| 丹阳市| 南宫市| 潞城市| 荥阳市| 谢通门县| 怀宁县| 镇康县| 长子县| 历史| 来凤县| 平罗县| 博客| 句容市| 繁峙县| 滁州市| 民和| 南通市| 佛冈县| 仪陇县| 隆德县| 彭州市| 萨嘎县| 拉萨市| 礼泉县| 陵水| 通渭县| 屏东县| 横峰县| 星子县| 大悟县|