【架构实战】网关架构设计:微服务的统一入口

一、没有网关的日子我们是怎么过的

2018年,我们的微服务直接暴露给前端。前端要记10个不同的域名和端口。

更痛苦的是,每个服务各自实现鉴权、限流、日志,代码重复度超过60%。

有一次安全审计,发现3个服务没有做鉴权,2个服务没有做限流。

后来我们上了网关,统一入口、统一鉴权、统一限流,世界瞬间清净了。


二、网关核心功能

2.1 功能清单

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    网关核心功能                                    │
│                                                                  │
│  1. 路由转发                                                   │
│     - 根据路径/域名/Header转发到后端服务                        │
│                                                                  │
│  2. 鉴权认证                                                   │
│     - JWT验证、OAuth2认证                                       │
│     - 统一登录、单点登录                                        │
│                                                                  │
│  3. 限流熔断                                                   │
│     - 全局限流、按用户限流                                      │
│     - 服务熔断、降级                                            │
│                                                                  │
│  4. 协议转换                                                   │
│     - HTTP → gRPC                                              │
│     - HTTP → WebSocket                                         │
│                                                                  │
│  5. 日志监控                                                   │
│     - 请求日志、响应日志                                        │
│     - 链路追踪                                                  │
│                                                                  │
│  6. 灰度发布                                                   │
│     - 按比例/按用户灰度路由                                     │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

三、Spring Cloud Gateway实现

3.1 路由配置

yaml 复制代码
# application.yml
spring:
  cloud:
    gateway:
      routes:
        # 订单服务
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200
                key-resolver: "#{@userKeyResolver}"
        
        # 商品服务
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: productCircuitBreaker
                fallbackUri: forward:/fallback/product
        
        # 支付服务(灰度)
        - id: payment-service-v2
          uri: lb://payment-service-v2
          predicates:
            - Path=/api/payments/**
            - Header=X-Gray, v2
          filters:
            - StripPrefix=1

3.2 鉴权过滤器

java 复制代码
/**
 * JWT鉴权过滤器
 */
@Component
@Slf4j
public class JwtAuthFilter implements GlobalFilter, Ordered {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    /** 白名单路径 */
    private static final Set<String> WHITE_LIST = Set.of(
        "/api/auth/login",
        "/api/auth/register",
        "/api/auth/refresh",
        "/api/public/**"
    );
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        
        // 白名单放行
        if (isWhiteListed(path)) {
            return chain.filter(exchange);
        }
        
        // 获取Token
        String token = extractToken(exchange.getRequest());
        
        if (token == null) {
            return unauthorized(exchange, "缺少认证Token");
        }
        
        try {
            // 验证Token
            JwtClaims claims = tokenProvider.validateToken(token);
            
            // 将用户信息传递给下游服务
            ServerHttpRequest request = exchange.getRequest().mutate()
                .header("X-User-Id", claims.getUserId())
                .header("X-User-Role", claims.getRole())
                .header("X-Trace-Id", generateTraceId())
                .build();
            
            return chain.filter(exchange.mutate().request(request).build());
            
        } catch (JwtTokenExpiredException e) {
            return unauthorized(exchange, "Token已过期");
        } catch (JwtTokenInvalidException e) {
            return unauthorized(exchange, "Token无效");
        }
    }
    
    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        String body = JSON.toJSONString(Result.fail(401, message));
        DataBuffer buffer = exchange.getResponse().bufferFactory()
            .wrap(body.getBytes(StandardCharsets.UTF_8));
        
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }
    
    @Override
    public int getOrder() {
        return -100;  // 高优先级
    }
}

3.3 限流过滤器

java 复制代码
/**
 * 自定义限流Key解析器
 */
@Component
public class UserKeyResolver implements KeyResolver {
    
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        String path = exchange.getRequest().getPath().value();
        
        if (userId != null) {
            return Mono.just("rate_limit:" + userId + ":" + path);
        }
        
        // 未登录用户按IP限流
        String ip = exchange.getRequest().getRemoteAddress()
            .getAddress().getHostAddress();
        return Mono.just("rate_limit:ip:" + ip + ":" + path);
    }
}

3.4 灰度路由

java 复制代码
/**
 * 灰度路由过滤器
 */
@Component
@Slf4j
public class GrayRouteFilter implements GlobalFilter, Ordered {
    
    @Autowired
    private GrayConfigService grayConfigService;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        String serviceId = getServiceId(exchange);
        
        // 检查灰度规则
        GrayRule rule = grayConfigService.getGrayRule(serviceId);
        
        if (rule != null && rule.isEnabled()) {
            boolean isGrayUser = isGrayUser(userId, rule);
            
            if (isGrayUser) {
                // 灰度用户路由到V2版本
                String newUri = rewriteUri(exchange.getRequest().getURI(), 
                    rule.getGrayVersion());
                
                ServerHttpRequest request = exchange.getRequest().mutate()
                    .uri(URI.create(newUri))
                    .header("X-Gray", "true")
                    .build();
                
                log.info("灰度路由: userId={}, service={}, version={}", 
                    userId, serviceId, rule.getGrayVersion());
                
                return chain.filter(exchange.mutate().request(request).build());
            }
        }
        
        return chain.filter(exchange);
    }
    
    private boolean isGrayUser(String userId, GrayRule rule) {
        // 按用户ID范围灰度
        if (rule.getUserIdRange() != null) {
            return rule.getUserIdRange().contains(Long.parseLong(userId));
        }
        
        // 按百分比灰度
        if (rule.getPercentage() > 0) {
            int hash = Math.abs(userId.hashCode());
            return hash % 100 < rule.getPercentage();
        }
        
        return false;
    }
    
    @Override
    public int getOrder() {
        return 0;
    }
}

四、踩坑实录

坑1:网关成为单点

网关挂了,所有服务都不可用。

解决:网关多实例部署 + 健康检查 + 自动扩容。

坑2:网关性能瓶颈

所有请求经过网关,QPS高时网关响应慢。

解决:网关只做轻量操作(路由、鉴权),重逻辑放在业务服务。

坑3:网关超时设置不合理

网关超时30秒,但有些导出接口需要60秒。

解决:不同路由设置不同超时,长连接接口特殊处理。

坑4:跨域配置遗漏

前端请求被CORS策略拦截。

解决:在网关统一配置CORS。

坑5:请求体大小限制

文件上传请求被网关拒绝,因为超过了默认请求体大小限制。

解决 :调整spring.codec.max-in-memory-size配置。


五、总结

网关设计要点:

功能 方案
路由 Spring Cloud Gateway
鉴权 JWT + GlobalFilter
限流 Redis + RequestRateLimiter
熔断 Resilience4J
灰度 自定义路由规则
监控 Actuator + Prometheus

最佳实践:

  1. 网关多实例部署
  2. 只做轻量操作
  3. 统一鉴权和限流
  4. 合理的超时配置
  5. 完善的监控告警

血的教训:

网关是微服务的大门。门没守好,再多的内部安全措施也白搭。

思考题: 你的系统用了什么网关方案?有没有踩过坑?


个人观点,仅供参考

相关推荐
杉氧3 小时前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
杉氧3 小时前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
Lion094 小时前
ReAct 循环:Agent 的思考引擎 — Think → Act → Observe
架构
阿里云云原生4 小时前
Higress v2.2.3 发布:正式入驻 CNCF Sandbox,AI Gateway 与 Ingress 迁移能力双向加固
云原生
得物技术6 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
自珍JAVA7 小时前
Superpowers AI编码秩序
架构
古茗前端团队8 小时前
急招!前端|测试|后端|产品(名额多,速来)
前端·后端·架构
木雷坞10 小时前
我再也不敢随手 `docker compose down -v` 了
架构
没落英雄10 小时前
从零开始搭建一个 AI Agent —— LangChain + TypeScript 实战手记
前端·人工智能·架构
doiito10 小时前
【Agent Harness】Gliding Horse 设计细节 -- 不跟风开发自己的AI Agent
架构·rust·agent