1. 当微服务遇到网关:为什么需要这道门卫?
小区有门禁系统管控人员进出,微服务架构中的网关同样承担着"智能门卫"的角色。当我们的服务从单体架构拆分为十几个微服务后,每个服务直接暴露端口的方式会产生两个严重问题:
- 客户端需要记住每个服务的地址和端口(类似记住每户业主的门牌号)
- 无法统一实施安全策略(比如每个门卫岗各自为政)
假设我们有一个电商系统包含:
- 用户服务(8081)
- 商品服务(8082)
- 订单服务(8083)
未经网关的调用方式:
// 前端直接调用三个服务(潜在安全隐患)
const userAPI = 'http://host:8081/api/users';
const productAPI = 'http://host:8082/api/products';
const orderAPI = 'http://host:8083/api/orders';
引入网关后的变化:
// 所有请求经过统一入口
const gateway = 'http://gateway:8888';
// 请求路径转换为
const userAPI = `${gateway}/user-service/api/users`;
const productAPI = `${gateway}/product-service/api/products`;
const orderAPI = `${gateway}/order-service/api/orders`;
2. 路由配置实战:给请求指路的交通警察
2.1 Spring Cloud Gateway路由示例(技术栈:Spring Cloud 2022.0.4)
spring:
cloud:
gateway:
routes:
- id: user_route
uri: lb://user-service
predicates:
- Path=/user-service/**
filters:
- StripPrefix=1
- AddRequestHeader=X-Color, blue
- id: cache_route
uri: https://cache.api
predicates:
- Method=GET
- Header=X-Env, internal
- After=2023-01-20T00:00:00.000+08:00
filters:
- RewritePath=/cache/(?<segment>.*), /$\{segment}
配置解读:
StripPrefix=1
移除路径中的服务名前缀AddRequestHeader
实现请求染色RewritePath
支持正则表达式改写路径
2.2 Zuul路由配置对比(技术栈:Spring Cloud Netflix Zuul 2.1.6)
@Configuration
public class ZuulConfig {
@Bean
public ZuulFilter preFilter() {
return new ZuulFilter() {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("X-Trace-ID", UUID.randomUUID().toString());
return null;
}
};
}
}
路由规则配置:
zuul.routes.user-service.path=/user/**
zuul.routes.user-service.serviceId=user-service
zuul.routes.user-service.stripPrefix=true
3. 过滤器开发:网关的瑞士军刀
3.1 流量染色过滤器(Spring Cloud Gateway)
public class TrafficMarkerFilter implements GlobalFilter {
// 实现权重分流逻辑
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求头参数
String clientType = exchange.getRequest()
.getHeaders()
.getFirst("X-Client-Type");
// 添加染色标记
if ("mobile".equalsIgnoreCase(clientType)) {
exchange.getRequest().mutate()
.header("X-Traffic-Tag", "mobile_group")
.build();
} else {
exchange.getRequest().mutate()
.header("X-Traffic-Tag", "web_group")
.build();
}
// 流量权重分流示例
Random random = new Random();
if (random.nextInt(100) < 20) { // 20%流量
exchange.getResponse().getHeaders()
.add("X-Testing-Env", "canary");
}
return chain.filter(exchange);
}
}
3.2 请求校验过滤器(Zuul)
public class AuthFilter extends ZuulFilter {
// 实现API签名验证
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String signature = request.getHeader("X-Signature");
String timestamp = request.getHeader("X-Timestamp");
// 校验签名有效性
if (!validateSignature(signature, timestamp)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("Invalid API signature");
}
return null;
}
private boolean validateSignature(String sign, String timestamp) {
// 具体签名算法实现
return SecurityUtils.check(sign, timestamp);
}
}
4. 这对表兄弟的差异在哪?
4.1 架构设计对比
维度 | Spring Cloud Gateway | Zuul |
---|---|---|
通信模型 | 非阻塞Reactor | 同步阻塞 |
性能基准 | RPS 3万+ | RPS 1万 |
配置方式 | 代码/配置文件 | 配置文件/注解 |
依赖组件 | WebFlux | Servlet |
监控集成 | Micrometer | Hystrix |
4.2 性能测试数据对比
压测环境:4核8G服务器,500并发持续请求
指标 | Gateway | Zuul1.x |
---|---|---|
平均响应时间(ms) | 23 | 115 |
错误率(%) | 0.01 | 0.15 |
CPU占用率(%) | 45 | 78 |
内存占用(GB) | 1.2 | 2.5 |
5. 真实场景中的抉择时刻
5.1 适用Gateway的场景
- 需要灰度发布的金融系统
- 直播平台的大规模并发请求
- 物联网设备的高频心跳检测
- 需要WebSocket支持的在线游戏
典型案例:某短视频平台使用Gateway实现的AB测试:
public class ABTestingFilter extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
// 根据用户ID哈希分桶
String userId = getUserId(exchange);
int bucket = Math.abs(userId.hashCode()) % 100;
if (bucket < 10) { // 10%用户
exchange.getRequest().mutate()
.header("X-Feature-Flag", "new_ui");
}
return chain.filter(exchange);
};
}
}
5.2 坚守Zuul的场景
- 遗留系统的渐进式改造
- 需要精细控制线程的传统架构
- 依赖Hystrix熔断的存量系统
- 简单路由需求的内部管理系统
改造示例:某传统ERP系统逐步迁移方案
# 第一阶段:原有服务继续使用Zuul
zuul.routes.legacy.path=/legacy/**
zuul.routes.legacy.url=http://old-system
# 第二阶段:新服务使用Gateway
spring.cloud.gateway.routes[0].uri=lb://new-service
6. 那些年我们踩过的坑
6.1 路由配置三不要
- 不要忘记超时设置:
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
- 不要无限制重试:
// 错误示范:可能导致雪崩效应
RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(10)
.build();
// 正确做法:结合熔断机制
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.build();
- 不要忽略路径匹配顺序:
routes:
- id: specific_route
uri: lb://special-service
predicates:
- Path=/api/v2/special
- id: general_route # 这个配置必须放在后面
uri: lb://common-service
predicates:
- Path=/api/**
6.2 过滤器开发四原则
- 线程安全:避免使用实例变量
- 快速失败:复杂操作异步处理
- 适度抽象:不要过度设计
- 监控埋点:记录执行耗时
正确示例:
public class LoggingFilter implements GlobalFilter {
// 使用Mono.fromRunnable实现异步日志
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.currentTimeMillis();
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
log.info("请求耗时:{}ms URI:{}",
duration,
exchange.getRequest().getURI());
}));
}
}
7. 未来演进风向标
- 服务网格化:Istio与网关的协同
- 智能化路由:基于机器学习的流量预测
- 安全增强:集成Web应用防火墙
- 协议扩展:gRPC/RSocket支持深化
趋势示例:AI驱动的动态路由
public class SmartRouter {
@Scheduled(fixedRate = 5000)
public void updateRoutingRules() {
// 获取实时指标数据
ServiceMetrics metrics = metricsService.getCurrentMetrics();
// 基于负载情况动态调整
if (metrics.getCpuUsage() > 80) {
gatewayClient.updateRoute(
RouteDefinitionBuilder()
.id("dynamic_route")
.uri(getLeastLoadedInstance())
.predicates(...)
);
}
}
}