📖 开场:小区门卫
想象小区的门卫大爷 👴:
没有门卫(混乱):
外人:随便进 🚶
↓
小偷:也能进 🥷
↓
推销员:天天骚扰 📞
↓
快递员:不知道送哪栋 📦
结果:
- 不安全 ❌
- 很混乱 ❌
- 效率低 ❌
有门卫(有序):
外人来访:
↓
门卫:你找谁?
↓
外人:找1栋的张三
↓
门卫:
1. 检查身份证 🪪
2. 登记 📝
3. 打电话确认 ☎️
4. 指路:1栋在左边 👈
5. 放行 ✅
结果:
- 安全 ✅
- 有序 ✅
- 高效 ✅
这就是API网关:系统的守门员!
🤔 为什么需要API网关?
问题1:客户端直接调用微服务(混乱)
没有网关:
客户端 → 订单服务:http://order-service:8080/api/order
客户端 → 用户服务:http://user-service:8081/api/user
客户端 → 支付服务:http://pay-service:8082/api/pay
问题:
1. 客户端需要知道所有服务的地址 ❌
2. 服务地址变了,客户端要改代码 ❌
3. 鉴权逻辑每个服务都要写 ❌
4. 跨域问题 ❌
5. 协议不统一(HTTP/gRPC)❌
问题2:有网关(统一入口)
有网关:
客户端 → 网关:http://gateway:8000/api/*
↓
网关:
1. 鉴权 🔐
2. 限流 🚦
3. 路由 🧭
4. 协议转换 🔄
5. 监控 📊
↓
路由到对应的服务 ✅
优点:
- 统一入口 ✅
- 集中鉴权 ✅
- 简化客户端 ✅
🎯 核心功能
功能1:路由转发 🧭
路由规则:
/api/order/* → 订单服务
/api/user/* → 用户服务
/api/pay/* → 支付服务
例子:
客户端请求:GET http://gateway:8000/api/order/123
↓
网关:匹配路由规则 /api/order/*
↓
转发到:http://order-service:8080/api/order/123
功能2:鉴权 🔐
鉴权流程:
客户端请求:
↓
网关:检查Token
↓
Token有效 → 放行 ✅
Token无效 → 返回401 ❌
功能3:限流 🚦
限流规则:
- 用户级别:每个用户每秒100次
- 接口级别:登录接口每秒1000次
- IP级别:同一IP每秒200次
超过限制:
↓
返回429(Too Many Requests)
功能4:熔断降级 💔
熔断流程:
订单服务:连续10次超时
↓
网关:开启熔断 ⚡
↓
后续请求:直接返回降级响应
↓
30秒后:尝试恢复
🎯 核心设计
设计1:Spring Cloud Gateway实现 ⭐⭐⭐
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
配置路由
spring:
cloud:
gateway:
routes:
# ⭐ 订单服务路由
- id: order-service
uri: lb://order-service # lb表示负载均衡
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1 # 去掉/api前缀
# ⭐ 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
# ⭐ 支付服务路由
- id: pay-service
uri: lb://pay-service
predicates:
- Path=/api/pay/**
filters:
- StripPrefix=1
设计2:鉴权过滤器 🔐
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private JwtService jwtService;
// 白名单(不需要鉴权的接口)
private static final List<String> WHITE_LIST = Arrays.asList(
"/api/user/login",
"/api/user/register"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// ⭐ 1. 白名单,直接放行
if (isWhiteList(path)) {
return chain.filter(exchange);
}
// ⭐ 2. 获取Token
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return unauthorized(exchange, "Token不能为空");
}
token = token.substring(7); // 去掉"Bearer "前缀
// ⭐ 3. 验证Token
try {
Long userId = jwtService.parseToken(token);
// ⭐ 4. 将userId放入请求头,传递给下游服务
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-Id", String.valueOf(userId))
.build();
ServerWebExchange modifiedExchange = exchange.mutate()
.request(modifiedRequest)
.build();
return chain.filter(modifiedExchange);
} catch (Exception e) {
return unauthorized(exchange, "Token无效");
}
}
@Override
public int getOrder() {
return -100; // 优先级最高
}
/**
* 检查是否在白名单
*/
private boolean isWhiteList(String path) {
return WHITE_LIST.stream().anyMatch(path::startsWith);
}
/**
* 返回401未授权
*/
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Result<Void> result = Result.fail(401, message);
DataBuffer buffer = response.bufferFactory()
.wrap(JSON.toJSONBytes(result));
return response.writeWith(Mono.just(buffer));
}
}
设计3:限流过滤器 🚦
Redis限流(令牌桶)
@Component
public class RateLimitFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String RATE_LIMIT_KEY = "rate_limit:";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 获取用户ID(从上游的鉴权过滤器传递过来)
String userId = request.getHeaders().getFirst("X-User-Id");
if (userId == null) {
// 未登录用户,按IP限流
String ip = getClientIp(request);
return rateLimitByIp(exchange, chain, ip);
} else {
// 已登录用户,按用户ID限流
return rateLimitByUser(exchange, chain, userId);
}
}
/**
* ⭐ 按用户ID限流(每秒100次)
*/
private Mono<Void> rateLimitByUser(ServerWebExchange exchange,
GatewayFilterChain chain,
String userId) {
String key = RATE_LIMIT_KEY + "user:" + userId;
return rateLimit(exchange, chain, key, 100);
}
/**
* ⭐ 按IP限流(每秒200次)
*/
private Mono<Void> rateLimitByIp(ServerWebExchange exchange,
GatewayFilterChain chain,
String ip) {
String key = RATE_LIMIT_KEY + "ip:" + ip;
return rateLimit(exchange, chain, key, 200);
}
/**
* ⭐ 限流(Lua脚本)
*/
private Mono<Void> rateLimit(ServerWebExchange exchange,
GatewayFilterChain chain,
String key,
int limit) {
// Lua脚本(令牌桶算法)
String luaScript =
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local current = redis.call('incr', key)\n" +
"if current == 1 then\n" +
" redis.call('expire', key, 1)\n" +
"end\n" +
"if current <= limit then\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
// 执行Lua脚本
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(key),
String.valueOf(limit)
);
if (result != null && result == 1) {
// 未超限,放行
return chain.filter(exchange);
} else {
// 超限,拒绝
return tooManyRequests(exchange);
}
}
/**
* 返回429(Too Many Requests)
*/
private Mono<Void> tooManyRequests(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Result<Void> result = Result.fail(429, "请求太频繁,请稍后再试");
DataBuffer buffer = response.bufferFactory()
.wrap(JSON.toJSONBytes(result));
return response.writeWith(Mono.just(buffer));
}
/**
* 获取客户端IP
*/
private String getClientIp(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("X-Forwarded-For");
if (ip != null && !ip.isEmpty()) {
return ip.split(",")[0];
}
ip = headers.getFirst("X-Real-IP");
if (ip != null && !ip.isEmpty()) {
return ip;
}
return request.getRemoteAddress().getAddress().getHostAddress();
}
@Override
public int getOrder() {
return -90; // 在鉴权过滤器之后
}
}
设计4:熔断降级(Resilience4j)💔
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
配置熔断
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
# ⭐ 配置熔断
- name: CircuitBreaker
args:
name: orderServiceCircuitBreaker
fallbackUri: forward:/fallback/order
降级处理器
@RestController
@RequestMapping("/fallback")
public class FallbackController {
/**
* ⭐ 订单服务降级响应
*/
@RequestMapping("/order")
public Result<Void> orderFallback() {
return Result.fail("订单服务暂时不可用,请稍后再试");
}
/**
* 用户服务降级响应
*/
@RequestMapping("/user")
public Result<Void> userFallback() {
return Result.fail("用户服务暂时不可用,请稍后再试");
}
}
熔断配置
resilience4j:
circuitbreaker:
instances:
orderServiceCircuitBreaker:
# ⭐ 熔断配置
failure-rate-threshold: 50 # 失败率阈值(50%)
minimum-number-of-calls: 10 # 最少调用次数
sliding-window-size: 20 # 滑动窗口大小
wait-duration-in-open-state: 30s # 熔断持续时间
permitted-number-of-calls-in-half-open-state: 5 # 半开状态允许调用次数
设计5:日志和监控 📊
@Component
@Slf4j
public class LogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
long startTime = System.currentTimeMillis();
String requestId = UUID.randomUUID().toString();
// ⭐ 记录请求日志
log.info("⭐ [{}] {} {} 开始",
requestId,
request.getMethod(),
request.getURI().getPath());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
ServerHttpResponse response = exchange.getResponse();
// ⭐ 记录响应日志
log.info("⭐ [{}] {} {} 完成,耗时:{}ms,状态码:{}",
requestId,
request.getMethod(),
request.getURI().getPath(),
duration,
response.getStatusCode());
}));
}
@Override
public int getOrder() {
return -200; // 最先执行
}
}
🎓 面试题速答
Q1: API网关有什么作用?
A: 六大作用:
- 统一入口:客户端只需知道网关地址
- 鉴权:集中鉴权,不需要每个服务都写
- 限流:保护后端服务
- 熔断降级:服务故障时快速失败
- 路由转发:根据路径转发到对应服务
- 协议转换:HTTP → gRPC
Q2: 如何实现鉴权?
A: GlobalFilter + JWT:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取Token
String token = request.getHeaders().getFirst("Authorization");
// 2. 验证Token
Long userId = jwtService.parseToken(token);
// 3. 将userId放入请求头
request.mutate().header("X-User-Id", String.valueOf(userId));
// 4. 放行
return chain.filter(exchange);
}
Q3: 如何实现限流?
A: Redis + Lua脚本:
-- Lua脚本(令牌桶)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('incr', key)
if current == 1 then
redis.call('expire', key, 1)
end
if current <= limit then
return 1 -- 未超限
else
return 0 -- 超限
end
优点:原子操作,高性能
Q4: 熔断降级如何实现?
A: Resilience4j:
resilience4j:
circuitbreaker:
instances:
orderService:
failure-rate-threshold: 50 # 失败率50%
wait-duration-in-open-state: 30s # 熔断30秒
流程:
- 失败率 > 50% → 开启熔断
- 30秒内直接返回降级响应
- 30秒后尝试恢复
Q5: Spring Cloud Gateway vs Zuul?
A: Gateway更好⭐:
| 特性 | Gateway | Zuul 1.x |
|---|---|---|
| 基于 | WebFlux(异步) | Servlet(同步) |
| 性能 | 高 ✅ | 低 ❌ |
| 吞吐量 | 高 ✅ | 低 ❌ |
| 功能 | 丰富 ✅ | 较少 ❌ |
推荐:Spring Cloud Gateway
Q6: 网关如何保证高可用?
A: 集群部署 + 负载均衡:
Nginx
↓
┌─────────┼─────────┐
↓ ↓ ↓
Gateway1 Gateway2 Gateway3
↓ ↓ ↓
后端服务集群
优点:
- 网关宕机自动切换
- 负载均衡
🎬 总结
API网关核心功能
┌────────────────────────────────────┐
│ 1. 路由转发 🧭 │
│ - 统一入口 │
│ - 路径匹配 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 2. 鉴权 🔐 │
│ - JWT验证 │
│ - 白名单 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 3. 限流 🚦 │
│ - Redis + Lua │
│ - 按用户/IP限流 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 4. 熔断降级 💔 │
│ - Resilience4j │
│ - 快速失败 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 5. 日志监控 📊 │
│ - 请求日志 │
│ - 性能监控 │
└────────────────────────────────────┘
🎉 恭喜你!
你已经完全掌握了API网关的设计!🎊
核心要点:
- 路由转发:统一入口,路径匹配
- 鉴权:JWT验证,白名单
- 限流:Redis + Lua,令牌桶
- 熔断降级:Resilience4j,快速失败
- 日志监控:记录请求日志,性能监控
下次面试,这样回答:
"API网关是微服务架构的统一入口,承担路由转发、鉴权、限流、熔断降级等职责。我们项目使用Spring Cloud Gateway实现,基于WebFlux异步非阻塞,性能比Zuul更好。
鉴权通过GlobalFilter实现。获取请求头中的Authorization字段,验证JWT Token,解析出用户ID后放入X-User-Id请求头传递给下游服务。白名单接口如登录注册直接放行。验证失败返回401未授权。
限流使用Redis + Lua脚本实现令牌桶算法。已登录用户按用户ID限流每秒100次,未登录用户按IP限流每秒200次。Lua脚本先incr计数器,第一次设置1秒过期,超过限制返回429 Too Many Requests。Lua脚本保证原子性,避免并发问题。
熔断降级集成Resilience4j。配置失败率阈值50%,滑动窗口20次调用,10次最小调用数。失败率超过50%时开启熔断,30秒内请求直接走降级逻辑返回友好提示。30秒后进入半开状态,允许5次调用测试服务是否恢复。
日志监控方面,LogFilter记录每个请求的开始时间、结束时间、耗时、状态码。生成唯一requestId用于链路追踪。集成Prometheus和Grafana监控网关的QPS、响应时间、错误率等指标。
高可用方面,网关集群部署3个节点,前端Nginx负载均衡。网关本身是无状态的,宕机后自动切换。限流数据存储在Redis,熔断状态存储在本地内存并通过事件广播同步到其他节点。"
面试官:👍 "很好!你对API网关的设计理解很深刻!"
本文完 🎬
上一篇: 215-设计一个评论系统.md
下一篇: 217-设计一个分布式爬虫系统.md
作者注:写完这篇,我觉得自己可以当门卫了!🚪
如果这篇文章对你有帮助,请给我一个Star⭐!