SpringBoot + SpringCloud Gateway + Sentinel + Redis:API 网关层的接口限流、黑名单拦截与用户认证

今天结合实战经验,教大家用 SpringBoot、SpringCloud Gateway、Sentinel 和 Redis 搭建一套集接口限流、黑名单拦截、用户认证于一体的高性能 API 网关,文中会把这些年踩过的坑、总结的最佳实践全部分享出来。

在从单体架构往微服务迁移的过程中,我见过太多团队因为忽视网关层的建设踩了大坑:某电商平台大促时没做限流,直接导致全链路服务崩溃;某金融系统因裸漏服务接口,遭遇恶意攻击造成数据泄露;某 SaaS 平台把认证逻辑散在各个服务里,最终引发权限漏洞......

这三个核心痛点,对应着 API 网关的三大核心职责:

  • 接口限流:对流量"削峰填谷",避免后端服务被突发流量冲垮
  • 黑名单拦截:在流量入口处阻断恶意请求,减少无效资源消耗
  • 用户认证:统一鉴权逻辑,杜绝各服务重复开发认证模块的冗余问题

为什么选这套技术组合?实战经验告诉我,技术选型要兼顾功能、性能和可维护性,这套组合刚好满足:

  • SpringCloud Gateway:基于 Netty 的非阻塞架构,性能远优于传统 Zuul,还支持动态路由和灵活的过滤器机制
  • Sentinel:阿里开源的轻量级流量控制组件,对比 Hystrix 更易上手,支持多维度限流且规则可动态配置
  • Redis:高性能的分布式缓存,完美解决限流计数、黑名单共享、令牌存储等分布式场景问题
  • SpringBoot:快速整合各类组件,大幅减少模板代码,提升开发效率

#mermaid-svg-kqDfpdxuuepz9Eab{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kqDfpdxuuepz9Eab .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kqDfpdxuuepz9Eab .error-icon{fill:#552222;}#mermaid-svg-kqDfpdxuuepz9Eab .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kqDfpdxuuepz9Eab .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kqDfpdxuuepz9Eab .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kqDfpdxuuepz9Eab .marker.cross{stroke:#333333;}#mermaid-svg-kqDfpdxuuepz9Eab svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kqDfpdxuuepz9Eab p{margin:0;}#mermaid-svg-kqDfpdxuuepz9Eab .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kqDfpdxuuepz9Eab .cluster-label text{fill:#333;}#mermaid-svg-kqDfpdxuuepz9Eab .cluster-label span{color:#333;}#mermaid-svg-kqDfpdxuuepz9Eab .cluster-label span p{background-color:transparent;}#mermaid-svg-kqDfpdxuuepz9Eab .label text,#mermaid-svg-kqDfpdxuuepz9Eab span{fill:#333;color:#333;}#mermaid-svg-kqDfpdxuuepz9Eab .node rect,#mermaid-svg-kqDfpdxuuepz9Eab .node circle,#mermaid-svg-kqDfpdxuuepz9Eab .node ellipse,#mermaid-svg-kqDfpdxuuepz9Eab .node polygon,#mermaid-svg-kqDfpdxuuepz9Eab .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kqDfpdxuuepz9Eab .rough-node .label text,#mermaid-svg-kqDfpdxuuepz9Eab .node .label text,#mermaid-svg-kqDfpdxuuepz9Eab .image-shape .label,#mermaid-svg-kqDfpdxuuepz9Eab .icon-shape .label{text-anchor:middle;}#mermaid-svg-kqDfpdxuuepz9Eab .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kqDfpdxuuepz9Eab .rough-node .label,#mermaid-svg-kqDfpdxuuepz9Eab .node .label,#mermaid-svg-kqDfpdxuuepz9Eab .image-shape .label,#mermaid-svg-kqDfpdxuuepz9Eab .icon-shape .label{text-align:center;}#mermaid-svg-kqDfpdxuuepz9Eab .node.clickable{cursor:pointer;}#mermaid-svg-kqDfpdxuuepz9Eab .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kqDfpdxuuepz9Eab .arrowheadPath{fill:#333333;}#mermaid-svg-kqDfpdxuuepz9Eab .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kqDfpdxuuepz9Eab .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kqDfpdxuuepz9Eab .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kqDfpdxuuepz9Eab .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kqDfpdxuuepz9Eab .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kqDfpdxuuepz9Eab .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kqDfpdxuuepz9Eab .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kqDfpdxuuepz9Eab .cluster text{fill:#333;}#mermaid-svg-kqDfpdxuuepz9Eab .cluster span{color:#333;}#mermaid-svg-kqDfpdxuuepz9Eab div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kqDfpdxuuepz9Eab .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kqDfpdxuuepz9Eab rect.text{fill:none;stroke-width:0;}#mermaid-svg-kqDfpdxuuepz9Eab .icon-shape,#mermaid-svg-kqDfpdxuuepz9Eab .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kqDfpdxuuepz9Eab .icon-shape p,#mermaid-svg-kqDfpdxuuepz9Eab .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kqDfpdxuuepz9Eab .icon-shape .label rect,#mermaid-svg-kqDfpdxuuepz9Eab .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kqDfpdxuuepz9Eab .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kqDfpdxuuepz9Eab .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kqDfpdxuuepz9Eab :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端
API网关层

SpringCloud Gateway + Sentinel
微服务集群
Redis

缓存/黑名单/限流

请求从进入网关到转发至微服务的完整链路,采用了责任链模式的分层过滤设计------每个过滤器只负责单一职责,后续维护和扩展都更轻松。


一、核心依赖引入

首先要引入核心依赖,这里给大家整理了完整的依赖清单,同时标注了版本兼容要点:

xml 复制代码
<!-- SpringBoot WebFlux 核心依赖(Gateway基于WebFlux) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- SpringCloud Gateway 核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- Sentinel 整合 SpringCloud Gateway 依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<!-- 响应式Redis依赖(适配Gateway非阻塞架构) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

<!-- JWT 令牌相关依赖 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

❗版本兼容提示:SpringCloud Gateway 3.1.x 必须搭配 Sentinel 2.2.x 及以上版本,否则会出现过滤器适配问题。我曾在项目中因版本不匹配导致限流规则完全不生效,排查了整整一天,这是血的教训!


二、核心配置编写

接下来是网关的核心配置,包括路由规则、Sentinel 限流兜底、Redis 连接等:

yaml 复制代码
spring:
  application:
    name: api-gateway # 网关应用名称
  cloud:
    gateway:
      routes:
        # 用户服务路由规则
        - id: user-service
          uri: lb://user-service # 负载均衡指向用户服务
          predicates:
            - Path=/api/v1/users/** # 匹配路径
          filters:
            - name: AuthenticationFilter # 认证过滤器(优先执行)
            - name: BlacklistFilter # 黑名单过滤器
            - name: SentinelGatewayFilter # Sentinel限流过滤器
            - RewritePath=/api/v1/users/(?<segment>.*),/users/${segment} # 路径重写
        # 订单服务路由规则
        - id: order-service
          uri: lb://order-service # 负载均衡指向订单服务
          predicates:
            - Path=/api/v1/orders/** # 匹配路径
          filters:
            - name: AuthenticationFilter # 认证过滤器
            - name: BlacklistFilter # 黑名单过滤器
            - name: SentinelGatewayFilter # Sentinel限流过滤器
            - RewritePath=/api/v1/orders/(?<segment>.*),/orders/${segment} # 路径重写
    sentinel:
      transport:
        dashboard: localhost:8080 # Sentinel控制台地址
      scg:
        fallback:
          mode: response # 限流兜底模式:直接返回响应
          response-status: 429 # 限流返回状态码
          response-body: '{"code":429,"msg":"请求过于频繁,请稍后再试"}' # 限流提示语
  # Redis 配置
  redis:
    host: localhost # Redis地址
    port: 6379 # Redis端口
    password: # Redis密码(生产环境务必配置)
    lettuce:
      pool:
        max-active: 20 # 最大活跃连接数
        max-idle: 10 # 最大空闲连接数
        min-idle: 5 # 最小空闲连接数

# 自定义业务配置
api:
  jwt:
    secret: your-secret-key # JWT密钥(生产环境用环境变量注入)
    expiration: 86400000 # JWT过期时间(24小时)
  rate-limit:
    default:
      qps: 100 # 默认QPS限流阈值
  blacklist:
    prefix: "blacklist:" # 黑名单Redis Key前缀

三、统一认证过滤器(JWT实现)

认证是网关的基础能力,这里基于 JWT 实现统一认证,并且通过白名单放行登录、注册等无需认证的接口:

java 复制代码
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(AuthenticationFilter.class);
    
    // 认证白名单:无需token即可访问的接口
    private static final List<String> WHITE_LIST = Arrays.asList(
        "/api/v1/users/login",
        "/api/v1/users/register",
        "/actuator/health"
    );

    @Autowired
    private ReactiveRedisTemplate<String, String> redisTemplate; // 响应式Redis模板
    
    @Value("${api.jwt.secret}")
    private String secretKey; // JWT签名密钥

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 白名单接口直接放行
        if (WHITE_LIST.stream().anyMatch(path::startsWith)) {
            return chain.filter(exchange);
        }

        // 从请求头获取token(格式:Bearer xxx)
        String token = request.getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return unauthorized(exchange, "未提供有效令牌");
        }

        // 截取真正的token串(去掉Bearer 前缀)
        token = token.substring(7);

        try {
            // 解析JWT令牌,验证签名和有效期
            Claims claims = Jwts.parserBuilder()
                .setSigningKey(secretKey.getBytes()) // 设置签名密钥
                .build()
                .parseClaimsJws(token)
                .getBody();

            // 获取JWT唯一标识,检查是否在黑名单(已注销的token)
            String jti = claims.getId();
            Mono<Boolean> isBlacklisted = redisTemplate.hasKey("jwt:blacklist:" + jti);

            // 异步处理Redis查询结果
            return isBlacklisted.flatMap(blacklisted -> {
                if (blacklisted) {
                    return unauthorized(exchange, "令牌已失效");
                }

                // 将用户信息放入请求头,传递给后端服务
                ServerHttpRequest mutatedRequest = request.mutate()
                    .header("X-User-Id", claims.getSubject()) // 用户ID
                    .header("X-User-Role", claims.get("role", String.class)) // 用户角色
                    .build();

                // 继续执行过滤器链
                return chain.filter(exchange.mutate().request(mutatedRequest).build());
            });

        } catch (ExpiredJwtException e) {
            // token过期
            return unauthorized(exchange, "令牌已过期");
        } catch (Exception e) {
            // 其他token验证失败场景
            log.warn("令牌验证失败: {}", e.getMessage());
            return unauthorized(exchange, "无效的令牌");
        }
    }

    /**
     * 返回401未授权响应
     */
    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED); // 设置401状态码
        response.getHeaders().add("Content-Type", "application/json"); // 设置响应格式

        // 构造响应体
        String body = String.format("{\"code\":401,\"msg\":\"%s\"}", message);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 设置过滤器执行顺序(负数优先执行)
     */
    @Override
    public int getOrder() {
        return -100; // 认证过滤器优先级最高,先执行
    }
}

总结:认证过滤器的 order 值一定要设为负数,确保在路由和其他过滤器之前执行。另外白名单一定要维护好,避免把健康检查、登录注册等接口误拦截。


四、黑名单拦截过滤器(IP+用户ID双维度)

黑名单需要同时支持 IP 和用户 ID 两个维度,并且要能正确获取代理场景下的真实客户端 IP:

java 复制代码
@Component
public class BlacklistFilter implements GlobalFilter, Ordered {

    @Autowired
    private ReactiveRedisTemplate<String, String> redisTemplate; // 响应式Redis模板
    
    @Value("${api.blacklist.prefix}")
    private String blacklistPrefix; // 黑名单Redis Key前缀

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 1. 检查IP黑名单
        String ip = getClientIp(request); // 获取真实客户端IP
        Mono<Boolean> isIpBlacklisted = redisTemplate.hasKey(blacklistPrefix + "ip:" + ip);

        // 2. 检查用户ID黑名单(已认证用户才有X-User-Id头)
        String userId = request.getHeaders().getFirst("X-User-Id");
        Mono<Boolean> isUserBlacklisted = Mono.just(false); // 默认未拉黑
        if (userId != null) {
            isUserBlacklisted = redisTemplate.hasKey(blacklistPrefix + "user:" + userId);
        }

        // 先检查IP黑名单,再检查用户ID黑名单
        return isIpBlacklisted.flatMap(ipBlack -> {
            if (ipBlack) {
                return forbidden(exchange, "IP已被限制访问");
            }
            return isUserBlacklisted.flatMap(userBlack -> {
                if (userBlack) {
                    return forbidden(exchange, "账号已被限制访问");
                }
                return chain.filter(exchange); // 未拉黑则放行
            });
        });
    }

    /**
     * 获取真实客户端IP(兼容代理/反向代理场景)
     */
    private String getClientIp(ServerHttpRequest request) {
        // 优先从X-Forwarded-For头获取(反向代理如Nginx会设置)
        String ip = request.getHeaders().getFirst("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");
        }
        // 最后从请求连接获取IP
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        // 多级代理时,X-Forwarded-For会有多个IP,取第一个
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }

    /**
     * 返回403禁止访问响应
     */
    private Mono<Void> forbidden(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FORBIDDEN); // 设置403状态码
        response.getHeaders().add("Content-Type", "application/json"); // 设置响应格式

        // 构造响应体
        String body = String.format("{\"code\":403,\"msg\":\"%s\"}", message);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 设置过滤器执行顺序(在认证之后,限流之前)
     */
    @Override
    public int getOrder() {
        return -90;
    }
}

技巧:获取客户端真实 IP 时一定要考虑代理场景,生产环境几乎都会部署 Nginx 等反向代理,必须正确解析 X-Forwarded-For 等头信息。另外黑名单建议设置过期时间,避免永久封禁。


五、Sentinel 网关限流配置

Sentinel 的网关限流需要自定义规则加载,同时支持动态更新,这里给出基础规则配置和 Redis 持久化方案:

java 复制代码
@Configuration
public class SentinelConfig {

    @Value("${api.rate-limit.default.qps}")
    private int defaultQps; // 默认QPS限流阈值

    /**
     * 初始化Sentinel网关限流规则
     */
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();

        // 1. 用户服务限流规则(按路由ID限流)
        GatewayFlowRule userServiceRule = new GatewayFlowRule("user-service")
            .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID) // 按路由ID限流
            .setCount(100) // QPS阈值
            .setGrade(RuleConstant.FLOW_GRADE_QPS) // 按QPS限流
            .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 默认限流策略(快速失败)
        rules.add(userServiceRule);

        // 2. 订单服务限流规则(按路由ID限流)
        GatewayFlowRule orderServiceRule = new GatewayFlowRule("order-service")
            .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
            .setCount(50) // 订单服务QPS阈值
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(orderServiceRule);

        // 3. 登录接口限流规则(按请求路径限流)
        GatewayFlowRule loginRule = new GatewayFlowRule("/api/v1/users/login")
            .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_REQUEST_PATH) // 按路径限流
            .setCount(20) // 登录接口QPS阈值
            .setGrade(RuleConstant.FLOW_GRADE_QPS);
        rules.add(loginRule);

        // 加载限流规则
        GatewayRuleManager.loadRules(rules);

        // 设置限流兜底处理逻辑
        SentinelGatewayFilterFactory.setBlockHandler(new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
                Map<String, Object> result = new HashMap<>();
                result.put("code", 429); // 限流状态码
                result.put("msg", "请求过于频繁,请稍后再试"); // 限流提示
                result.put("timestamp", System.currentTimeMillis()); // 时间戳

                // 构造限流响应
                return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(result));
            }
        });
    }
}

/**
 * Redis实现Sentinel规则持久化,支持动态更新
 */
@Component
public class RedisSentinelRuleManager {

    private static final Logger log = LoggerFactory.getLogger(RedisSentinelRuleManager.class);
    
    @Autowired
    private RedissonClient redissonClient; // Redisson客户端(响应式Redis客户端)
    private static final String SENTINEL_RULE_KEY = "sentinel:gateway:rules"; // 规则存储Key

    /**
     * 初始化:从Redis加载规则,并监听规则变化
     */
    @PostConstruct
    public void init() {
        // 获取Redis中的规则列表
        RList<GatewayFlowRule> ruleList = redissonClient.getList(SENTINEL_RULE_KEY);
        if (!ruleList.isEmpty()) {
            // 加载已有规则
            GatewayRuleManager.loadRules(new HashSet<>(ruleList));
        }

        // 监听Redis列表变化,动态更新规则
        ruleList.addListener((ListListener<GatewayFlowRule>) event -> {
            if (event.getEventType() == ListEventType.ADD ||
                event.getEventType() == ListEventType.UPDATE ||
                event.getEventType() == ListEventType.REMOVE) {
                // 重新加载规则
                GatewayRuleManager.loadRules(new HashSet<>(ruleList));
                log.info("Sentinel规则已更新,当前规则数: {}", ruleList.size());
            }
        });
    }

    /**
     * 更新限流规则(供管理后台调用)
     */
    public void updateRules(List<GatewayFlowRule> newRules) {
        RList<GatewayFlowRule> ruleList = redissonClient.getList(SENTINEL_RULE_KEY);
        ruleList.clear(); // 清空旧规则
        ruleList.addAll(newRules); // 添加新规则
    }
}

性能优化点:Sentinel 默认的限流统计基于滑动窗口,高并发场景下可调整窗口大小(默认 1 秒)。另外规则更新建议通过管理后台操作,不要直接修改 Redis,避免规则混乱。


六、生产环境部署与问题排查

单网关实例存在单点故障风险,生产环境必须集群部署:

  • 多实例部署:在不同服务器部署多个网关实例,通过 Nginx 或云厂商负载均衡器分发流量
  • 规则一致性:确保 Sentinel 规则通过 Redis 等共享存储,保证集群规则一致性
  • 服务发现:网关实例间不直接通信,通过 Nacos/Eureka 等注册中心感知后端服务变化

常见问题与解决方案

1. 问题:黑名单过滤器偶尔在认证过滤器之前执行,导致获取不到用户 ID

原因:SpringCloud Gateway 过滤器 order 值冲突,多个过滤器 order 相同时执行顺序不确定

解决方案 :明确设置 order 值,保证 认证过滤器(-100) < 黑名单过滤器(-90) < 限流过滤器(-80),严格控制执行顺序

2. 问题:配置了限流规则但不起作用,压测时 QPS 远超设置值

排查方向

  • 检查 Gateway 和 Sentinel 版本是否兼容
  • 检查限流规则的 resourceMode 是否配置正确
  • 是否漏加 SentinelGatewayFilter 过滤器
  • 确认 Sentinel 控制台是否正常连接
3. 问题:高并发下网关频繁报 Redis 连接超时

原因:使用 ReactiveRedisTemplate 时未正确处理 Mono/Flux 的订阅,导致连接未释放

解决方案:确保所有 Redis 操作通过 flatMap 等操作符串联,避免嵌套订阅

java 复制代码
// 错误写法(嵌套订阅导致连接泄漏)
Mono<Boolean> result = redisTemplate.hasKey(key);
result.subscribe(blacklisted -> {
    // 处理逻辑
});

// 正确写法(通过flatMap串联异步操作)
return redisTemplate.hasKey(key).flatMap(blacklisted -> {
    // 处理逻辑
    return chain.filter(exchange);
});

七、总结与建议

这套基于 SpringCloud Gateway、Sentinel 和 Redis 的网关解决方案,在我们的生产环境扛

系统整体架构图

下面这张图展示了完整的系统架构,包括客户端、网关层、后端服务以及各个组件之间的交互关系:
#mermaid-svg-hyz7sIDD6TwhI7lu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hyz7sIDD6TwhI7lu .error-icon{fill:#552222;}#mermaid-svg-hyz7sIDD6TwhI7lu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hyz7sIDD6TwhI7lu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hyz7sIDD6TwhI7lu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hyz7sIDD6TwhI7lu .marker.cross{stroke:#333333;}#mermaid-svg-hyz7sIDD6TwhI7lu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hyz7sIDD6TwhI7lu p{margin:0;}#mermaid-svg-hyz7sIDD6TwhI7lu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu .cluster-label text{fill:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu .cluster-label span{color:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu .cluster-label span p{background-color:transparent;}#mermaid-svg-hyz7sIDD6TwhI7lu .label text,#mermaid-svg-hyz7sIDD6TwhI7lu span{fill:#333;color:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu .node rect,#mermaid-svg-hyz7sIDD6TwhI7lu .node circle,#mermaid-svg-hyz7sIDD6TwhI7lu .node ellipse,#mermaid-svg-hyz7sIDD6TwhI7lu .node polygon,#mermaid-svg-hyz7sIDD6TwhI7lu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hyz7sIDD6TwhI7lu .rough-node .label text,#mermaid-svg-hyz7sIDD6TwhI7lu .node .label text,#mermaid-svg-hyz7sIDD6TwhI7lu .image-shape .label,#mermaid-svg-hyz7sIDD6TwhI7lu .icon-shape .label{text-anchor:middle;}#mermaid-svg-hyz7sIDD6TwhI7lu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hyz7sIDD6TwhI7lu .rough-node .label,#mermaid-svg-hyz7sIDD6TwhI7lu .node .label,#mermaid-svg-hyz7sIDD6TwhI7lu .image-shape .label,#mermaid-svg-hyz7sIDD6TwhI7lu .icon-shape .label{text-align:center;}#mermaid-svg-hyz7sIDD6TwhI7lu .node.clickable{cursor:pointer;}#mermaid-svg-hyz7sIDD6TwhI7lu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hyz7sIDD6TwhI7lu .arrowheadPath{fill:#333333;}#mermaid-svg-hyz7sIDD6TwhI7lu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hyz7sIDD6TwhI7lu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hyz7sIDD6TwhI7lu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hyz7sIDD6TwhI7lu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hyz7sIDD6TwhI7lu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hyz7sIDD6TwhI7lu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hyz7sIDD6TwhI7lu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hyz7sIDD6TwhI7lu .cluster text{fill:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu .cluster span{color:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hyz7sIDD6TwhI7lu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hyz7sIDD6TwhI7lu rect.text{fill:none;stroke-width:0;}#mermaid-svg-hyz7sIDD6TwhI7lu .icon-shape,#mermaid-svg-hyz7sIDD6TwhI7lu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hyz7sIDD6TwhI7lu .icon-shape p,#mermaid-svg-hyz7sIDD6TwhI7lu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hyz7sIDD6TwhI7lu .icon-shape .label rect,#mermaid-svg-hyz7sIDD6TwhI7lu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hyz7sIDD6TwhI7lu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hyz7sIDD6TwhI7lu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hyz7sIDD6TwhI7lu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 监控告警
后端微服务
共享存储层
API网关集群
负载均衡层
客户端层
Web客户端
移动端App
第三方服务
Nginx/云LB

负载均衡
网关实例1
网关实例2
网关实例3
Redis集群

黑名单/限流计数/令牌
Nacos/Eureka

服务注册中心
用户服务
订单服务
商品服务
支付服务
Prometheus
Grafana
Sentinel控制台

架构说明

  1. 客户端层:各类客户端通过统一入口访问系统
  2. 负载均衡层:使用 Nginx 或云厂商负载均衡器分发流量到网关集群
  3. API网关集群:多实例部署,避免单点故障,通过 Redis 共享状态
  4. 共享存储层
    • Redis:存储黑名单、限流计数、JWT令牌等共享数据
    • Nacos/Eureka:服务注册与发现,网关动态感知后端服务变化
  5. 后端微服务:业务服务集群,网关根据路由规则转发请求
  6. 监控告警:Prometheus 收集指标,Grafana 可视化,Sentinel 控制台管理限流规则

请求处理流程图

下面这张流程图详细展示了单个请求在网关中的完整处理流程:
#mermaid-svg-GzusgWYtt9kZ1kk2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GzusgWYtt9kZ1kk2 .error-icon{fill:#552222;}#mermaid-svg-GzusgWYtt9kZ1kk2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GzusgWYtt9kZ1kk2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .marker.cross{stroke:#333333;}#mermaid-svg-GzusgWYtt9kZ1kk2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GzusgWYtt9kZ1kk2 p{margin:0;}#mermaid-svg-GzusgWYtt9kZ1kk2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .cluster-label text{fill:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .cluster-label span{color:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .cluster-label span p{background-color:transparent;}#mermaid-svg-GzusgWYtt9kZ1kk2 .label text,#mermaid-svg-GzusgWYtt9kZ1kk2 span{fill:#333;color:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .node rect,#mermaid-svg-GzusgWYtt9kZ1kk2 .node circle,#mermaid-svg-GzusgWYtt9kZ1kk2 .node ellipse,#mermaid-svg-GzusgWYtt9kZ1kk2 .node polygon,#mermaid-svg-GzusgWYtt9kZ1kk2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .rough-node .label text,#mermaid-svg-GzusgWYtt9kZ1kk2 .node .label text,#mermaid-svg-GzusgWYtt9kZ1kk2 .image-shape .label,#mermaid-svg-GzusgWYtt9kZ1kk2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-GzusgWYtt9kZ1kk2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .rough-node .label,#mermaid-svg-GzusgWYtt9kZ1kk2 .node .label,#mermaid-svg-GzusgWYtt9kZ1kk2 .image-shape .label,#mermaid-svg-GzusgWYtt9kZ1kk2 .icon-shape .label{text-align:center;}#mermaid-svg-GzusgWYtt9kZ1kk2 .node.clickable{cursor:pointer;}#mermaid-svg-GzusgWYtt9kZ1kk2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .arrowheadPath{fill:#333333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GzusgWYtt9kZ1kk2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GzusgWYtt9kZ1kk2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GzusgWYtt9kZ1kk2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GzusgWYtt9kZ1kk2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .cluster text{fill:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 .cluster span{color:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GzusgWYtt9kZ1kk2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GzusgWYtt9kZ1kk2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-GzusgWYtt9kZ1kk2 .icon-shape,#mermaid-svg-GzusgWYtt9kZ1kk2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GzusgWYtt9kZ1kk2 .icon-shape p,#mermaid-svg-GzusgWYtt9kZ1kk2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GzusgWYtt9kZ1kk2 .icon-shape .label rect,#mermaid-svg-GzusgWYtt9kZ1kk2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GzusgWYtt9kZ1kk2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GzusgWYtt9kZ1kk2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GzusgWYtt9kZ1kk2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 白名单接口
需认证接口
无效
有效


IP/用户ID在黑名单
不在黑名单
超过QPS限制
未超限
匹配路由规则
无匹配路由
客户端请求
认证检查
跳过认证
验证JWT令牌
黑名单检查
JWT是否有效?
返回401未授权
令牌是否在黑名单?
添加用户信息到请求头
返回403禁止访问
Sentinel限流检查
返回429请求过多
路由匹配
路径重写
转发到后端服务
返回404未找到
获取后端响应
返回响应给客户端
结束

流程说明

  1. 认证检查:首先检查请求路径是否在白名单中,如果是则跳过认证
  2. JWT验证:对于需要认证的接口,验证 JWT 令牌的有效性和是否在黑名单
  3. 黑名单检查:检查客户端 IP 和用户 ID 是否在黑名单中
  4. 限流检查:通过 Sentinel 检查当前 QPS 是否超过限制
  5. 路由匹配:根据配置的路由规则匹配目标服务
  6. 路径重写:根据配置重写请求路径
  7. 请求转发:将请求转发到对应的后端服务
  8. 响应返回:将后端服务的响应返回给客户端

过滤器执行顺序图

为了更清晰地展示各个过滤器的执行顺序和职责,下面用序列图展示:
后端服务 路由过滤器 Sentinel限流过滤器 黑名单过滤器 认证过滤器 SpringCloud Gateway 客户端 后端服务 路由过滤器 Sentinel限流过滤器 黑名单过滤器 认证过滤器 SpringCloud Gateway 客户端 #mermaid-svg-a91hR4oyVgKXzl20{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a91hR4oyVgKXzl20 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a91hR4oyVgKXzl20 .error-icon{fill:#552222;}#mermaid-svg-a91hR4oyVgKXzl20 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a91hR4oyVgKXzl20 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a91hR4oyVgKXzl20 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a91hR4oyVgKXzl20 .marker.cross{stroke:#333333;}#mermaid-svg-a91hR4oyVgKXzl20 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a91hR4oyVgKXzl20 p{margin:0;}#mermaid-svg-a91hR4oyVgKXzl20 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-a91hR4oyVgKXzl20 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-a91hR4oyVgKXzl20 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-a91hR4oyVgKXzl20 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-a91hR4oyVgKXzl20 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-a91hR4oyVgKXzl20 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-a91hR4oyVgKXzl20 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-a91hR4oyVgKXzl20 .sequenceNumber{fill:white;}#mermaid-svg-a91hR4oyVgKXzl20 #sequencenumber{fill:#333;}#mermaid-svg-a91hR4oyVgKXzl20 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-a91hR4oyVgKXzl20 .messageText{fill:#333;stroke:none;}#mermaid-svg-a91hR4oyVgKXzl20 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-a91hR4oyVgKXzl20 .labelText,#mermaid-svg-a91hR4oyVgKXzl20 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-a91hR4oyVgKXzl20 .loopText,#mermaid-svg-a91hR4oyVgKXzl20 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-a91hR4oyVgKXzl20 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-a91hR4oyVgKXzl20 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-a91hR4oyVgKXzl20 .noteText,#mermaid-svg-a91hR4oyVgKXzl20 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-a91hR4oyVgKXzl20 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-a91hR4oyVgKXzl20 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-a91hR4oyVgKXzl20 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-a91hR4oyVgKXzl20 .actorPopupMenu{position:absolute;}#mermaid-svg-a91hR4oyVgKXzl20 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-a91hR4oyVgKXzl20 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-a91hR4oyVgKXzl20 .actor-man circle,#mermaid-svg-a91hR4oyVgKXzl20 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-a91hR4oyVgKXzl20 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt令牌无效/过期令牌有效 alt白名单接口需认证接口 alt在黑名单中不在黑名单 alt超过限制未超限 发送HTTP请求执行认证过滤器 (order=-100)直接放行验证JWT令牌返回401未授权添加用户信息到请求头认证通过执行黑名单过滤器 (order=-90)检查IP/用户ID黑名单返回403禁止访问检查通过执行限流过滤器 (order=-80)检查QPS限制返回429请求过多限流通过执行路由过滤器匹配路由规则路径重写转发请求到后端服务返回响应返回响应返回最终响应

关键点

  • 执行顺序:认证过滤器(-100) → 黑名单过滤器(-90) → 限流过滤器(-80) → 路由过滤器
  • 责任链模式 :每个过滤器只负责单一职责,通过 Ordered 接口控制执行顺序
  • 快速失败:任一过滤器失败即直接返回响应,不会继续后续处理
  • 异步非阻塞:所有过滤器基于 WebFlux 响应式编程,避免阻塞线程

住了日均千万级请求的考验,核心优势在于:

  1. 非阻塞架构:基于 Netty 的异步非阻塞模型,保证高性能,适配高并发场景
  2. 责任链模式:过滤器设计职责清晰、易扩展,维护成本低
  3. 分布式存储:Redis 保证集群一致性,避免单点问题
  4. 动态规则配置:无需重启网关即可调整限流策略,运维友好

实战建议

最后给同行们几个实战建议:

  • 监控告警:网关是系统的"咽喉",一定要做好监控告警(重点监控响应时间、错误率、限流次数)
  • 保持轻量:避免在网关中编写复杂业务逻辑,保持网关轻量级,专注于流量管控和安全防护
  • 定期压测:定期做压测验证限流效果,不要等流量峰值来临才发现问题
  • 安全配置:核心配置(如 JWT 密钥、Redis 密码)务必通过环境变量注入,避免硬编码
  • 版本管理:严格管理依赖版本,特别是 SpringCloud Gateway 和 Sentinel 的兼容性
  • 日志规范:统一网关日志格式,便于问题排查和链路追踪

扩展思考

随着业务发展,还可以考虑以下扩展方向:

  1. 灰度发布:在网关层实现基于用户 ID、IP 或百分比的灰度流量分发
  2. API 文档集成:集成 Swagger/OpenAPI,提供统一的 API 文档入口
  3. 请求/响应日志:记录关键请求信息,便于审计和问题排查
  4. 熔断降级:结合 Sentinel 实现服务熔断和降级策略
  5. 性能监控:集成 Prometheus + Grafana,实现可视化监控

希望这篇实战指南能帮助大家快速搭建稳定、高性能的 API 网关,避免踩坑,提升系统整体稳定性!

相关推荐
小悟空1 小时前
[AI 生成] Nginx 502 Bad Gateway 排查手册(Python 后端篇)
python·nginx·gateway
爱吃羊的老虎14 小时前
【JAVA】python转java:Spring Boot 入门
java·spring boot·python
_qingche15 小时前
H2 数据库到 MySQL 数据迁移
java·数据库·spring boot·mysql·spring·重构·kotlin
码语智行18 小时前
系统启动时初始化数据功能分析
java·spring boot
invicinble18 小时前
推荐一下,遇到的几本比较好的书
spring boot
憧憬成为java架构高手的小白19 小时前
git工作流程简化版
java·spring boot·git
YDS82920 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— 动态决策策略的接口对接
java·spring boot·ai·agent·spring ai·deepseek
淘源码A21 小时前
专科医院云HIS系统源码:技术栈包括SpringBoot、Angular、MySQL等
spring boot·后端·源码·云his·医院信息系统·医院his系统
小马爱打代码21 小时前
基于 SpringBoot 的微服务文件上传下载组件设计与实现
spring boot·后端