跳转到内容
123xiao | 无名键客

《Spring Boot 3 中构建高并发 Java Web 接口的实战:限流、幂等与接口性能优化》

字数: 0 阅读时长: 1 分钟

Spring Boot 3 中构建高并发 Java Web 接口的实战:限流、幂等与接口性能优化

做 Java Web 接口开发时,很多人一开始关注的是“功能能不能跑通”,但线上流量一上来,问题就变成了另外一类:

  • 同一个请求被重复提交,订单生成两次
  • 某个热点接口被突发流量打穿,数据库连接池耗尽
  • 单机压测挺好,多实例部署后限流完全失效
  • 接口 RT 看起来不高,但吞吐量就是上不去

这些问题,本质上都和高并发下的接口治理有关。

这篇文章我想从一个更偏工程落地的角度,带你在 Spring Boot 3 里把三件事串起来:

  1. 限流:保护系统不被突发流量击穿
  2. 幂等:防止重复请求造成数据错乱
  3. 性能优化:让接口在相同资源下扛住更多请求

我会给出一套可运行的示例,尽量避免“概念讲一堆,代码落不了地”的问题。


背景与问题

先看一个典型业务链路:用户调用下单接口。

在低并发下,代码可能只是这样:

  1. 校验参数
  2. 查库存
  3. 扣库存
  4. 写订单
  5. 返回结果

但高并发时,问题会迅速暴露:

  • 突发请求:秒杀、活动场景下,单接口 QPS 飙升
  • 重复提交:用户连点、网络重试、网关超时重放
  • 资源争抢:线程池、数据库连接池、Redis 连接池被打满
  • 慢查询拖垮整体吞吐:某个 SQL 慢一点,整体链路排队

如果接口没有保护层,业务代码写得再“优雅”也顶不住。

一个更稳妥的接口处理链

我的经验是,不要把高并发问题只看成“优化 SQL”或者“加机器”。更稳妥的做法是把请求分层处理:

flowchart LR
    A[客户端请求] --> B[网关/认证]
    B --> C[接口限流]
    C --> D[幂等校验]
    D --> E[业务处理]
    E --> F[缓存/数据库]
    F --> G[响应返回]

这个顺序很关键:

  • 先限流,挡住不该进来的流量
  • 再幂等,避免重复写
  • 最后再谈性能优化,让有效请求处理得更快

核心原理

这一节不空谈定义,只讲和实战最相关的部分。

1. 限流:控制进入系统的请求速率

限流的目标不是“让所有请求都成功”,而是让系统在高压下仍然可控

常见限流维度:

  • 按接口:/api/order/create
  • 按用户:某个用户每秒最多 N 次
  • 按 IP:防恶意请求
  • 按系统总量:整个服务总 QPS 上限

常见算法:

  • 固定窗口:实现简单,但边界突刺明显
  • 滑动窗口:更平滑
  • 令牌桶:允许一定突发流量,适合接口场景
  • 漏桶:强制匀速流出,更适合削峰

在 Web 接口里,令牌桶通常是一个兼顾效果和实现复杂度的方案。

2. 幂等:同一个请求,多次提交,结果一致

幂等最容易被误解。它不是“接口不能重复调用”,而是:

同一个业务请求重复执行,不会造成重复副作用。

例如创建订单接口:

  • 第一次请求:成功创建订单
  • 第二次相同请求:不再重复创建,而是返回第一次结果或提示已处理

常见实现方式:

  • 前端防重复点击:有用,但不可靠
  • 数据库唯一索引:最后一道防线
  • Redis 幂等键:高并发下常用
  • 业务请求号 / Idempotency-Key:推荐做法

我比较推荐的组合是:

  • 请求头传 Idempotency-Key
  • Redis SETNX 做快速抢占
  • 数据库唯一约束兜底

3. 性能优化:不是单点提速,而是减少系统总消耗

接口性能优化,不只是“把某段代码写快一点”,而是从系统视角减少整体资源消耗。

核心抓手通常有:

  • 少一次数据库访问
  • 避免不必要对象创建
  • 缩短事务范围
  • 热点数据缓存
  • 线程池隔离
  • 避免阻塞调用堆积

如果说限流是“别让洪水冲进来”,性能优化就是“把进来的水更快排走”。


方案对比与取舍分析

在架构设计上,这三类问题有多种做法。选型时要看你的流量规模、部署方式和维护成本。

限流方案对比

方案优点缺点适用场景
本地内存限流实现简单,性能高多实例不一致单机、低复杂场景
Redis 分布式限流多实例统一,部署常见依赖 Redis,可用性要保障多实例生产环境
网关层限流统一治理,接入方便粒度受限,不够贴近业务通用 API 保护
应用层 AOP 限流业务灵活,可按方法控制侵入应用层需要细粒度策略

我的建议是:

  • 网关限流 + 应用层补充限流
  • 单机 demo 可以先用本地
  • 生产环境尽量用 Redis 分布式限流

幂等方案对比

方案优点缺点适用场景
前端按钮置灰成本低不可靠体验优化
数据库唯一索引强一致只能兜底,提示不友好核心写操作
Redis 幂等键性能高需要 TTL 和状态设计高并发接口
防重表可审计成本高金融、支付类

最稳的思路通常不是“二选一”,而是:

  • Redis 防重负责快速拦截
  • 数据库唯一索引负责最终一致性兜底

容量估算:别等压测时才发现配置不够

很多接口上线前没有做容量估算,最后会出现一种尴尬局面:业务逻辑没问题,但连接池和线程池先倒了。

一个简单估算思路:

假设:

  • 峰值 QPS:1000
  • 平均 RT:50ms

粗略并发量可估算为:

并发数 ≈ QPS × RT = 1000 × 0.05 = 50

这意味着你的线程池、连接池、下游资源至少要围绕这个量级来设计。

再考虑突发峰值,比如 3 倍瞬时流量:

保护阈值 ≈ 1000 ~ 1500 QPS
突发缓冲 ≈ 2000 QPS 内短时可接受

所以:

  • 限流阈值不能瞎设成“越大越好”
  • 数据库连接池也不要盲目调很大
  • 线程数更不是越多越强,阻塞型接口线程开太大只会放大上下文切换

实战代码(可运行)

下面我们用一个 Spring Boot 3 示例,演示:

  • 基于 Redis 的接口限流
  • 基于 Idempotency-Key 的幂等控制
  • 一个简化的下单接口

1. 项目依赖

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>high-concurrency-api-demo</artifactId>
    <version>1.0.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>
</project>

2. 配置文件

application.yml

server:
  port: 8080

spring:
  data:
    redis:
      host: localhost
      port: 6379
      timeout: 2s

3. 启动类

DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

4. 统一返回与异常

ApiResponse.java

package com.example.demo.common;

public class ApiResponse<T> {
    private boolean success;
    private String message;
    private T data;

    public ApiResponse() {
    }

    public ApiResponse(boolean success, String message, T data) {
        this.success = success;
        this.message = message;
        this.data = data;
    }

    public static <T> ApiResponse<T> ok(T data) {
        return new ApiResponse<>(true, "OK", data);
    }

    public static <T> ApiResponse<T> fail(String message) {
        return new ApiResponse<>(false, message, null);
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

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

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

BizException.java

package com.example.demo.common;

public class BizException extends RuntimeException {
    public BizException(String message) {
        super(message);
    }
}

GlobalExceptionHandler.java

package com.example.demo.common;

import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BizException.class)
    public ApiResponse<Void> handleBiz(BizException e) {
        return ApiResponse.fail(e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<Void> handleValid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldError() != null
                ? e.getBindingResult().getFieldError().getDefaultMessage()
                : "参数校验失败";
        return ApiResponse.fail(msg);
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleOther(Exception e) {
        return ApiResponse.fail("系统异常:" + e.getMessage());
    }
}

5. 限流注解与切面

这里我们做一个基于 Redis 计数器的简单限流实现。它不是最强版本,但胜在容易理解和落地。

RateLimit.java

package com.example.demo.ratelimit;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
    String key();
    long windowSeconds() default 1;
    long maxRequests() default 5;
}

RateLimitAspect.java

package com.example.demo.ratelimit;

import com.example.demo.common.BizException;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.Duration;
import java.util.Objects;

@Aspect
@Component
public class RateLimitAspect {

    private final StringRedisTemplate stringRedisTemplate;

    public RateLimitAspect(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Before("@annotation(rateLimit)")
    public void doBefore(JoinPoint joinPoint, RateLimit rateLimit) {
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        String ip = getClientIp(request);
        String redisKey = "rate_limit:" + rateLimit.key() + ":" + ip;

        Long count = stringRedisTemplate.opsForValue().increment(redisKey);
        if (count != null && count == 1L) {
            stringRedisTemplate.expire(redisKey, Duration.ofSeconds(rateLimit.windowSeconds()));
        }

        if (count != null && count > rateLimit.maxRequests()) {
            throw new BizException("请求过于频繁,请稍后再试");
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String xff = request.getHeader("X-Forwarded-For");
        if (xff != null && !xff.isBlank()) {
            return xff.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }
}

说明:这里是固定窗口计数器方案,简单易上手。生产环境要更平滑的话,可以升级为 Lua + 滑动窗口 / 令牌桶。

6. 幂等控制

我们使用请求头 Idempotency-Key。如果 Redis 中已经存在相同业务键,就拒绝重复处理。

Idempotent.java

package com.example.demo.idempotent;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
    long ttlSeconds() default 30;
}

IdempotentAspect.java

package com.example.demo.idempotent;

import com.example.demo.common.BizException;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.Duration;
import java.util.Objects;

@Aspect
@Component
public class IdempotentAspect {

    private final StringRedisTemplate stringRedisTemplate;
    private static final ThreadLocal<String> KEY_HOLDER = new ThreadLocal<>();

    public IdempotentAspect(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Before("@annotation(idempotent)")
    public void before(Idempotent idempotent) {
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        String idempotencyKey = request.getHeader("Idempotency-Key");
        if (idempotencyKey == null || idempotencyKey.isBlank()) {
            throw new BizException("缺少 Idempotency-Key 请求头");
        }

        String redisKey = "idempotent:" + request.getRequestURI() + ":" + idempotencyKey;
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(redisKey, "PROCESSING", Duration.ofSeconds(idempotent.ttlSeconds()));

        if (!Boolean.TRUE.equals(success)) {
            throw new BizException("请求重复,请勿重复提交");
        }

        KEY_HOLDER.set(redisKey);
    }

    @AfterThrowing(pointcut = "@annotation(idempotent)", throwing = "e")
    public void afterThrowing(Idempotent idempotent, Exception e) {
        String key = KEY_HOLDER.get();
        if (key != null) {
            stringRedisTemplate.delete(key);
            KEY_HOLDER.remove();
        }
    }
}

这里有一个设计点要说明:

  • 业务执行失败时,删除幂等键,允许客户端重试
  • 业务执行成功时,保留幂等键直到 TTL 过期,避免短时间重复提交

这是很常见的一种策略。

7. DTO 与 Controller

CreateOrderRequest.java

package com.example.demo.order;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

public class CreateOrderRequest {

    @NotBlank(message = "用户ID不能为空")
    private String userId;

    @NotBlank(message = "商品ID不能为空")
    private String productId;

    @Min(value = 1, message = "购买数量必须大于0")
    private int amount;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.userId = userId;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }
}

这里我特意提醒一个我自己也踩过的坑:setProductId 里很容易手滑写错字段。上面的代码就有 bug。下面给出正确版本:

CreateOrderRequest.java(修正版)

package com.example.demo.order;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

public class CreateOrderRequest {

    @NotBlank(message = "用户ID不能为空")
    private String userId;

    @NotBlank(message = "商品ID不能为空")
    private String productId;

    @Min(value = 1, message = "购买数量必须大于0")
    private int amount;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }
}

OrderController.java

package com.example.demo.order;

import com.example.demo.common.ApiResponse;
import com.example.demo.idempotent.Idempotent;
import com.example.demo.ratelimit.RateLimit;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

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

    @PostMapping
    @RateLimit(key = "create_order", windowSeconds = 1, maxRequests = 3)
    @Idempotent(ttlSeconds = 10)
    public ApiResponse<Map<String, Object>> createOrder(@Valid @RequestBody CreateOrderRequest request) 
            throws InterruptedException {

        // 模拟业务处理耗时
        Thread.sleep(100);

        String orderId = UUID.randomUUID().toString();

        Map<String, Object> result = new HashMap<>();
        result.put("orderId", orderId);
        result.put("userId", request.getUserId());
        result.put("productId", request.getProductId());
        result.put("amount", request.getAmount());

        return ApiResponse.ok(result);
    }
}

8. AOP 依赖补充

如果你的工程没有自动带上 AOP,还需要增加:

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

9. 调用示例

curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: order-create-1001' \
--data '{
  "userId": "u1001",
  "productId": "p2001",
  "amount": 2
}'

如果短时间内用相同 Idempotency-Key 重复提交,会得到类似响应:

{
  "success": false,
  "message": "请求重复,请勿重复提交",
  "data": null
}

请求执行时序

用一张时序图看得更清楚:

sequenceDiagram
    participant C as Client
    participant A as Spring Boot API
    participant R as Redis
    participant B as Business

    C->>A: POST /api/orders + Idempotency-Key
    A->>R: 限流计数 INCR
    R-->>A: count
    A->>R: SETNX 幂等键
    R-->>A: success/fail
    alt 幂等成功
        A->>B: 执行业务逻辑
        B-->>A: 返回订单结果
        A-->>C: 200 OK
    else 幂等失败
        A-->>C: 重复请求
    end

性能优化:高并发接口真正该优化什么

上面的代码把“防御层”搭起来了,但如果业务逻辑本身很慢,接口照样扛不住。下面是更贴近实战的优化点。

1. 缩短事务范围

很多同学喜欢把整个方法都包进事务里,但事务越大,锁持有时间越长,吞吐越低。

错误思路:

  • 参数校验、远程调用、组装响应都放在同一事务中

更好的做法:

  • 只把真正需要原子性的数据库写操作放进事务

2. 缓存热点读数据

比如商品基础信息、用户等级、活动配置,这些都适合缓存。
目标不是“全部缓存”,而是优先缓存:

  • 访问频繁
  • 更新相对少
  • 查询代价高

3. 避免接口内阻塞等待

例如:

  • 同步调用慢下游
  • 不必要的 Thread.sleep
  • 在请求线程里做复杂报表计算

如果是非核心流程,可以异步化:

  • 发 MQ
  • 异步线程池
  • 事件驱动

但注意:异步化不是万能药,它只是把延迟转移了,前提是你能接受最终一致性。

4. 数据库优化要落到 SQL 层

高并发接口最终常常卡在数据库。

重点检查:

  • 是否走索引
  • 是否出现回表过多
  • 是否有 select *
  • 是否存在大事务
  • 是否把热点更新集中在同一行

5. JSON 序列化与对象创建

这个点常被忽略,但在高 QPS 场景会很明显:

  • 少创建中间对象
  • 响应字段别过于冗余
  • 大对象不要频繁拷贝
  • 注意日志打印不要序列化整个请求体

常见坑与排查

这一部分非常重要,因为很多方案“看起来对”,但线上会翻车。

1. 多实例部署后,本地限流失效

现象

单机测试没问题,部署 3 台后,整体流量超出预期。

原因

限流状态放在本地内存,每台机器各算各的。

解决

改为 Redis 分布式限流,或者在网关层统一限流。


2. 幂等键 TTL 设置不合理

现象

  • TTL 太短:请求还没处理完,幂等键过期了,重复请求又进来
  • TTL 太长:业务失败后用户长时间无法重试

经验值

TTL 至少覆盖:

接口最大处理耗时 + 网络重试窗口 + 一点冗余

比如接口最慢 3 秒,客户端 5 秒内可能自动重试一次,那 TTL 设 10~30 秒会更稳一些。


3. AOP 不生效

现象

注解加了,但限流和幂等逻辑完全没执行。

排查

  • 是否引入 spring-boot-starter-aop
  • 方法是否是 public
  • 是否发生了类内部自调用
  • 切点表达式是否正确

我之前就踩过“同类内部调用导致切面失效”的坑,查半天 Redis 还以为是连接问题,最后发现压根没进切面。


4. X-Forwarded-For 获取 IP 不准确

现象

所有请求都显示成一个代理 IP,导致限流误伤。

原因

服务部署在 Nginx、Ingress 或网关后面时,没有正确透传真实 IP。

解决

  • 网关/Nginx 正确设置 X-Forwarded-For
  • 应用层只信任受控代理传来的头
  • 不要盲信客户端自己传的 IP 头

5. Redis 原子性理解不完整

现象

高并发下偶发限流不准、状态异常。

原因

多条 Redis 命令组合执行,不一定具备完整原子性。

解决

生产环境建议把复杂限流逻辑写成 Lua 脚本,把判断和更新放到一次原子执行里。


安全/性能最佳实践

这一部分给“能直接拿去用”的建议。

1. 限流不要只做一层

推荐分层控制:

  • 网关层:拦截明显异常流量
  • 应用层:按业务接口、用户、资源维度细分
  • 下游保护:线程池隔离、连接池上限
flowchart TD
    A[外部流量] --> B[网关限流]
    B --> C[应用层限流]
    C --> D[幂等校验]
    D --> E[线程池/连接池隔离]
    E --> F[数据库/缓存]

2. 幂等必须有“业务唯一标识”

不要只拿用户 ID 当幂等键,否则同一个用户正常下两单也会被拦。

更合理的是:

  • 订单提交号
  • 支付请求号
  • 客户端生成的请求唯一 ID

3. 数据库唯一索引一定要保留

Redis 很快,但不是最终真相来源。
核心写操作建议保留数据库唯一索引兜底,比如:

CREATE TABLE t_order (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(64) NOT NULL,
    user_id VARCHAR(64) NOT NULL,
    product_id VARCHAR(64) NOT NULL,
    amount INT NOT NULL,
    UNIQUE KEY uk_order_no (order_no)
);

4. 接口超时要明确

高并发时,最怕“慢慢卡死”。

建议明确配置:

  • Web 请求超时
  • Redis 超时
  • 数据库超时
  • 下游 HTTP/RPC 超时

超时不是坏事,无边界等待才是坏事

5. 监控指标要提前埋

至少监控这些指标:

  • QPS
  • RT(P95/P99)
  • 限流次数
  • 幂等拦截次数
  • Redis 延迟
  • 数据库慢查询数量
  • Tomcat 线程池活跃数

没有监控时,排查高并发问题基本靠猜。


进一步优化方向

如果你的业务量继续上升,可以往这些方向演进:

1. Lua 脚本实现分布式令牌桶

比简单计数器更平滑,也更适合秒杀类流量控制。

2. 返回幂等结果而不是直接报错

更高级的实现是:

  • 第一次请求成功后,把结果写入 Redis
  • 重复请求直接返回第一次结果

这样客户端体验更好,特别适合支付、创建类接口。

3. 将削峰交给消息队列

对于强突发场景,接口层只做接收与校验,真正落库异步消费。
但前提是业务能接受:

  • 排队
  • 最终一致
  • 结果延迟可见

总结

在 Spring Boot 3 里做高并发接口,不要把问题拆散看。
限流、幂等、性能优化,本质上是同一套稳定性设计的不同面。

你可以按这个落地顺序来做:

  1. 先加限流:至少保护热点接口
  2. 再补幂等:创建、支付、回调类接口必须做
  3. 再看性能瓶颈:优先优化数据库、缓存和阻塞调用
  4. 最后做分层治理:网关、应用、下游一起兜住

如果你的项目还在“功能优先”的阶段,我建议最少先做两件事:

  • 给关键写接口加 Idempotency-Key
  • 给热点 API 加 Redis 分布式限流

这两件事投入不高,但对线上稳定性的提升非常直接。

最后也给一个边界条件:
如果你的系统还是单机、低 QPS、内部使用工具型接口,就没必要一上来把架构做得特别重。
但只要涉及:

  • 支付
  • 下单
  • 营销活动
  • 回调通知
  • 高频公网 API

那限流和幂等就不是“优化项”,而是必选项


分享到:

上一篇
《Spring Boot 3 实战:基于 JWT 与 Spring Security 6 构建可扩展的 Java Web 登录鉴权体系》
下一篇
《Node.js 中级实战:基于 Worker Threads 与事件循环监控构建高并发任务处理服务》