API网关
基础概念
什么是API网关?
答案: API网关是微服务架构中的统一入口,负责请求路由、协议转换、安全认证、流量控制等功能。
核心功能:
- 路由转发:将请求路由到对应的微服务
- 负载均衡:分发请求到多个服务实例
- 认证鉴权:统一的身份验证和权限控制
- 限流熔断:保护后端服务
- 协议转换:HTTP、gRPC、WebSocket等协议转换
- 日志监控:统一的日志记录和监控
- 缓存:减少后端服务压力
为什么需要API网关?
答案:
没有网关的问题:
- 客户端需要维护多个服务地址
- 认证鉴权逻辑分散在各个服务
- 跨域、限流等通用逻辑重复实现
- 协议不统一
使用网关的优势:
- 统一入口,简化客户端调用
- 集中处理横切关注点
- 服务解耦,后端服务对客户端透明
- 便于监控和管理
架构对比:
# 没有网关
客户端 → 服务A
客户端 → 服务B
客户端 → 服务C
# 使用网关
客户端 → API网关 → 服务A
↓
服务B
↓
服务C常见的API网关?
答案:
| 网关 | 特点 | 适用场景 |
|---|---|---|
| Spring Cloud Gateway | 基于Spring 5、WebFlux,异步非阻塞 | Spring Cloud生态 |
| Zuul | Netflix开源,同步阻塞 | 传统Spring Cloud |
| Kong | 基于Nginx+Lua,高性能 | 大流量场景 |
| Nginx | 高性能,配置简单 | 简单路由场景 |
| Traefik | 云原生,支持多种协议 | Kubernetes环境 |
Spring Cloud Gateway
Gateway的核心概念?
答案:
三大核心:
1. Route(路由)
- 网关的基本构建块
- 包含ID、目标URI、断言集合、过滤器集合
2. Predicate(断言)
- 匹配HTTP请求的条件
- 如路径、方法、请求头等
3. Filter(过滤器)
- 修改请求和响应
- 分为Pre和Post两种
工作流程:
客户端请求
↓
Gateway Handler Mapping(匹配路由)
↓
Predicate(断言判断)
↓
Gateway Web Handler
↓
Pre Filter(前置过滤)
↓
代理请求到目标服务
↓
Post Filter(后置过滤)
↓
返回响应Gateway的路由配置?
答案:
方式1:配置文件
yaml
spring:
cloud:
gateway:
routes:
# 路由1:用户服务
- id: user-service
uri: lb://user-service # lb表示负载均衡
predicates:
- Path=/user/**
- Method=GET,POST
- Header=X-Request-Id, \d+
filters:
- StripPrefix=1
- AddRequestHeader=X-Request-Source, Gateway
# 路由2:订单服务
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
- After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
filters:
- RewritePath=/order/(?<segment>.*), /${segment}
# 路由3:静态资源
- id: static-resource
uri: http://cdn.example.com
predicates:
- Path=/static/**方式2:Java配置
java
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r
.path("/user/**")
.and()
.method(HttpMethod.GET, HttpMethod.POST)
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Request-Source", "Gateway")
)
.uri("lb://user-service")
)
.route("order-service", r -> r
.path("/order/**")
.filters(f -> f
.rewritePath("/order/(?<segment>.*)", "/${segment}")
)
.uri("lb://order-service")
)
.build();
}
}Gateway的断言工厂?
答案:
内置断言工厂:
1. 路径断言
yaml
predicates:
- Path=/user/{id} # 路径匹配
- Path=/api/** # 通配符2. 方法断言
yaml
predicates:
- Method=GET,POST3. 请求头断言
yaml
predicates:
- Header=X-Request-Id, \d+ # 正则匹配
- Header=Authorization4. 请求参数断言
yaml
predicates:
- Query=token # 必须有token参数
- Query=version, 1.* # version参数匹配1.*5. Cookie断言
yaml
predicates:
- Cookie=sessionId, [a-z0-9]+6. 时间断言
yaml
predicates:
- After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
- Before=2024-12-31T23:59:59+08:00[Asia/Shanghai]
- Between=2024-01-01T00:00:00+08:00[Asia/Shanghai],2024-12-31T23:59:59+08:00[Asia/Shanghai]7. 权重断言
yaml
# 80%流量到v1,20%流量到v2
- id: service-v1
uri: lb://service-v1
predicates:
- Weight=group1, 8
- id: service-v2
uri: lb://service-v2
predicates:
- Weight=group1, 2自定义断言:
java
@Component
public class CustomRoutePredicateFactory
extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
public CustomRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("userId");
return config.getUserIds().contains(userId);
};
}
public static class Config {
private List<String> userIds;
// getter/setter
}
}Gateway的过滤器?
答案:
内置过滤器:
1. 请求头过滤器
yaml
filters:
- AddRequestHeader=X-Request-Id, 123
- RemoveRequestHeader=Cookie
- SetRequestHeader=X-Request-Source, Gateway2. 响应头过滤器
yaml
filters:
- AddResponseHeader=X-Response-Time, 100ms
- RemoveResponseHeader=Set-Cookie
- SetResponseHeader=X-Response-Source, Gateway3. 路径过滤器
yaml
filters:
- StripPrefix=1 # 去掉路径第一层
- PrefixPath=/api # 添加路径前缀
- RewritePath=/old/(?<segment>.*), /new/${segment}4. 重定向过滤器
yaml
filters:
- RedirectTo=302, https://www.example.com5. 重试过滤器
yaml
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 26. 限流过滤器
yaml
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 令牌桶填充速率
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
key-resolver: "#{@userKeyResolver}"java
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("userId")
);
}7. 熔断过滤器
yaml
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallbackjava
@RestController
public class FallbackController {
@GetMapping("/fallback")
public Result fallback() {
return Result.error("服务暂时不可用");
}
}认证鉴权
如何实现统一认证?
答案:
方案1:JWT认证
java
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtil jwtUtil;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 白名单
if (isWhitelist(request.getPath().value())) {
return chain.filter(exchange);
}
// 获取token
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return unauthorized(exchange);
}
token = token.substring(7);
// 验证token
try {
Claims claims = jwtUtil.parseToken(token);
String userId = claims.getSubject();
// 将用户信息传递给下游服务
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (Exception e) {
return unauthorized(exchange);
}
}
private Mono<Void> unauthorized(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -100;
}
}方案2:OAuth2认证
yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/authjava
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/public/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}如何实现权限控制?
答案:
基于角色的权限控制(RBAC):
java
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {
@Autowired
private PermissionService permissionService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
// 检查权限
if (!permissionService.hasPermission(userId, path, method)) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -90;
}
}java
@Service
public class PermissionService {
@Autowired
private RedisTemplate<String, Set<String>> redisTemplate;
public boolean hasPermission(String userId, String path, String method) {
// 从Redis获取用户权限
Set<String> permissions = redisTemplate.opsForValue().get("user:permission:" + userId);
if (permissions == null) {
return false;
}
// 检查是否有权限
String permission = method + ":" + path;
return permissions.contains(permission) || permissions.contains("*:*");
}
}限流
如何实现网关限流?
答案:
方案1:基于Redis的令牌桶限流
yaml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒生成10个令牌
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量20
redis-rate-limiter.requestedTokens: 1 # 每次请求消耗1个令牌
key-resolver: "#{@ipKeyResolver}"java
@Configuration
public class RateLimiterConfig {
// 基于IP限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
// 基于用户限流
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("userId")
);
}
// 基于接口限流
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().value()
);
}
}方案2:自定义限流过滤器
java
@Component
public class CustomRateLimiterFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String key = getKey(exchange);
// 使用Lua脚本实现令牌桶
String luaScript =
"local key = KEYS[1]\n" +
"local capacity = tonumber(ARGV[1])\n" +
"local rate = tonumber(ARGV[2])\n" +
"local now = tonumber(ARGV[3])\n" +
"...\n"; // 完整的Lua脚本
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(key),
"20", "10", String.valueOf(System.currentTimeMillis())
);
if (result == null || result == 0) {
// 限流
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
private String getKey(ServerWebExchange exchange) {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
String path = exchange.getRequest().getPath().value();
return "rate_limiter:" + ip + ":" + path;
}
@Override
public int getOrder() {
return -80;
}
}缓存
如何实现网关缓存?
答案:
java
@Component
public class CacheFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 只缓存GET请求
if (!HttpMethod.GET.equals(request.getMethod())) {
return chain.filter(exchange);
}
String cacheKey = getCacheKey(request);
// 查询缓存
String cachedResponse = redisTemplate.opsForValue().get(cacheKey);
if (cachedResponse != null) {
// 返回缓存数据
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer buffer = response.bufferFactory().wrap(cachedResponse.getBytes());
return response.writeWith(Mono.just(buffer));
}
// 缓存响应
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
// 缓存响应
String responseBody = new String(content, StandardCharsets.UTF_8);
redisTemplate.opsForValue().set(cacheKey, responseBody, 60, TimeUnit.SECONDS);
return dataBufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
private String getCacheKey(ServerHttpRequest request) {
return "cache:" + request.getPath().value() + ":" + request.getQueryParams().toString();
}
@Override
public int getOrder() {
return -70;
}
}日志监控
如何实现请求日志记录?
答案:
java
@Component
@Slf4j
public class LogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestId = UUID.randomUUID().toString();
// 记录请求日志
log.info("请求开始 - requestId: {}, method: {}, path: {}, params: {}",
requestId,
request.getMethod(),
request.getPath(),
request.getQueryParams()
);
long startTime = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
long duration = System.currentTimeMillis() - startTime;
// 记录响应日志
log.info("请求结束 - requestId: {}, status: {}, duration: {}ms",
requestId,
response.getStatusCode(),
duration
);
}));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}练习题
- 如何实现网关的高可用?
- 如何处理网关的跨域问题?
- 如何实现动态路由配置?
- 网关如何实现灰度发布?
- 如何优化网关的性能?