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

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

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. 完善的监控告警

血的教训:

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

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


个人观点,仅供参考

相关推荐
真实的菜1 小时前
微服务架构痛点
java·微服务·架构
MicrosoftReactor2 小时前
技术速递|以 Token 经济学驱动的架构:混合模型、AI Runway、AKS Kata MicroVM 与 MCP
人工智能·ai·架构·copilot·mcp
MemoriKu2 小时前
Flutter 相册 APP 视频模态稳定化实战:从视频抽帧、Embedding 元数据到 Android 真机启动修复
android·开发语言·前端·flutter·架构·音视频·embedding
百珏2 小时前
流量没暴涨,网关却挂了:Spring Cloud Gateway 从 500 QPS 优化到 4200 QPS
后端·spring cloud·架构
ai产品老杨2 小时前
【架构深评】基于 Docker 与 边缘计算,如何打通 GB28181/RTSP 与 X86/ARM 异构算力的企业级 AI 视频流网关?(附源码交付)
人工智能·docker·架构
咖啡八杯2 小时前
GoF设计模式——桥接模式
面试·架构
一切皆是因缘际会2 小时前
量化阈值拆解|2026端侧AI复盘
人工智能·架构·系统架构
阿狸猿2 小时前
论企业应用系统的分层架构风格
java·开发语言·架构
xixingzhe22 小时前
网上商城大促技术难点
架构