Appearance
路由元数据配置
概述
在微服务架构中,网关作为统一的流量入口,经常需要为不同的路由配置额外的参数来支持不同的业务需求。Spring Cloud Gateway 提供了元数据(metadata)机制,允许我们为每个路由配置自定义的参数,这些参数可以在后续的过滤器或业务逻辑中使用。
路由元数据配置是 Spring Cloud Gateway 的一个重要特性,它允许我们在不修改核心路由逻辑的情况下,为每个路由添加额外的配置信息。
业务场景
在实际的微服务架构中,路由元数据配置通常用于以下场景:
- 服务限流配置:为不同的路由配置不同的限流参数
- 监控标签:为路由添加监控相关的标签信息
- 业务标识:为路由添加业务相关的标识符
- 缓存策略:为不同的路由配置不同的缓存策略
- 日志级别:为不同的路由配置不同的日志记录级别
配置方式
1. YAML 配置方式
在 application.yml 文件中配置路由元数据:
yaml
spring:
cloud:
gateway:
routes:
- id: route_with_metadata # 路由唯一标识
uri: https://example.org # 目标服务地址
metadata:
# 字符串类型的元数据
optionName: "OptionValue"
# 复合对象类型的元数据
compositeObject:
name: "value"
description: "示例配置"
# 数字类型的元数据
iAmNumber: 1
# 业务相关的元数据配置
business:
service: "user-service"
version: "v1.0"
rateLimit: 100
cacheTimeout: 3002. Java 配置方式
使用 Kotlin 进行编程式配置:
kotlin
@Configuration
class GatewayConfig {
@Bean
fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
return builder.routes()
.route("user-service-route") { r ->
r.path("/api/users/**")
.uri("lb://user-service")
// 配置路由元数据
.metadata("service", "user-service")
.metadata("version", "v1.0")
.metadata("rateLimit", 100)
.metadata("businessConfig", mapOf(
"priority" to "high",
"timeout" to 5000,
"retryCount" to 3
))
}
.route("order-service-route") { r ->
r.path("/api/orders/**")
.uri("lb://order-service")
// 为订单服务配置不同的元数据
.metadata("service", "order-service")
.metadata("version", "v2.0")
.metadata("rateLimit", 200)
.metadata("businessConfig", mapOf(
"priority" to "critical",
"timeout" to 3000,
"retryCount" to 5
))
}
.build()
}
}java
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service-route", r -> r.path("/api/users/**")
.uri("lb://user-service")
// 配置路由元数据
.metadata("service", "user-service")
.metadata("version", "v1.0")
.metadata("rateLimit", 100)
.metadata("businessConfig", Map.of(
"priority", "high",
"timeout", 5000,
"retryCount", 3
)))
.build();
}
}获取和使用元数据
1. 在过滤器中获取元数据
创建自定义过滤器来使用路由元数据:
kotlin
@Component
class MetadataAwareGlobalFilter : GlobalFilter, Ordered {
private val logger = LoggerFactory.getLogger(MetadataAwareGlobalFilter::class.java)
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
// 获取当前路由信息
val route = exchange.getAttribute<Route>(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)
route?.let { r ->
// 获取所有元数据
val allMetadata = r.metadata
logger.info("路由 ${r.id} 的所有元数据: $allMetadata")
// 获取特定的元数据
val serviceName = r.getMetadata("service") as? String
val rateLimit = r.getMetadata("rateLimit") as? Int
val businessConfig = r.getMetadata("businessConfig") as? Map<String, Any>
// 基于元数据进行业务逻辑处理
serviceName?.let { service ->
logger.info("当前请求的目标服务: $service")
// 可以在这里添加服务特定的处理逻辑
}
rateLimit?.let { limit ->
logger.info("当前路由的限流配置: $limit")
// 可以在这里实现基于元数据的限流逻辑
}
businessConfig?.let { config ->
val priority = config["priority"] as? String
val timeout = config["timeout"] as? Int
logger.info("业务配置 - 优先级: $priority, 超时时间: $timeout")
// 基于优先级设置请求超时时间
if (priority == "critical" && timeout != null) {
exchange.attributes[ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR] =
ClientResponse.create(HttpStatus.OK)
.header("X-Custom-Timeout", timeout.toString())
.build()
}
}
}
return chain.filter(exchange)
}
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE + 1
}2. 在自定义断言中使用元数据
创建基于元数据的自定义路由断言:
kotlin
@Component
class MetadataRoutePredicateFactory :
AbstractRoutePredicateFactory<MetadataRoutePredicateFactory.Config>(Config::class.java) {
override fun apply(config: Config): Predicate<ServerWebExchange> {
return Predicate { exchange ->
val route = exchange.getAttribute<Route>(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)
route?.let { r ->
val metadataValue = r.getMetadata(config.key) as? String
metadataValue == config.expectedValue
} ?: false
}
}
data class Config(
var key: String = "",
var expectedValue: String = ""
)
}实际业务案例
案例:基于元数据的动态限流
假设我们有一个电商系统,需要为不同的服务配置不同的限流策略:
kotlin
@Component
class DynamicRateLimitFilter : GlobalFilter, Ordered {
private val rateLimiters = mutableMapOf<String, RedisRateLimiter>()
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
val route = exchange.getAttribute<Route>(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)
return route?.let { r ->
// 从元数据中获取限流配置
val rateLimit = r.getMetadata("rateLimit") as? Int ?: 10
val burstCapacity = r.getMetadata("burstCapacity") as? Int ?: rateLimit * 2
val routeId = r.id
// 获取或创建对应的限流器
val rateLimiter = rateLimiters.getOrPut(routeId) {
RedisRateLimiter(rateLimit, burstCapacity)
}
// 应用限流
rateLimiter.isAllowed("route", routeId)
.flatMap { response ->
if (response.isAllowed) {
// 添加限流相关的响应头
exchange.response.headers.add("X-RateLimit-Remaining",
response.tokensRemaining.toString())
chain.filter(exchange)
} else {
// 限流触发,返回 429 状态码
exchange.response.statusCode = HttpStatus.TOO_MANY_REQUESTS
exchange.response.headers.add("X-RateLimit-Retry-After", "1")
exchange.response.setComplete()
}
}
} ?: chain.filter(exchange)
}
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE + 2
}案例:基于元数据的监控标签
为不同的路由添加监控指标:
kotlin
@Component
class MetricsCollectorFilter : GlobalFilter, Ordered {
private val meterRegistry: MeterRegistry
constructor(meterRegistry: MeterRegistry) {
this.meterRegistry = meterRegistry
}
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
val startTime = System.currentTimeMillis()
val route = exchange.getAttribute<Route>(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)
return chain.filter(exchange)
.doFinally {
val duration = System.currentTimeMillis() - startTime
route?.let { r ->
// 从元数据中获取监控标签
val serviceName = r.getMetadata("service") as? String ?: "unknown"
val version = r.getMetadata("version") as? String ?: "unknown"
val businessType = r.getMetadata("businessType") as? String ?: "unknown"
// 记录请求指标
Timer.Sample.start(meterRegistry)
.stop(Timer.builder("gateway.request.duration")
.tag("service", serviceName)
.tag("version", version)
.tag("business_type", businessType)
.tag("route_id", r.id)
.register(meterRegistry))
// 记录请求计数
Counter.builder("gateway.request.count")
.tag("service", serviceName)
.tag("version", version)
.tag("business_type", businessType)
.register(meterRegistry)
.increment()
}
}
}
override fun getOrder(): Int = Ordered.LOWEST_PRECEDENCE
}配置示例
完整的生产环境配置示例
yaml
spring:
cloud:
gateway:
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
metadata:
service: "user-service"
version: "v1.0"
businessType: "core"
rateLimit: 100
burstCapacity: 200
timeout: 5000
retryCount: 3
priority: "high"
monitoring:
enabled: true
alertThreshold: 500
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
metadata:
service: "order-service"
version: "v2.0"
businessType: "transaction"
rateLimit: 200
burstCapacity: 400
timeout: 3000
retryCount: 5
priority: "critical"
caching:
enabled: true
ttl: 300
monitoring:
enabled: true
alertThreshold: 300
# 产品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
metadata:
service: "product-service"
version: "v1.5"
businessType: "catalog"
rateLimit: 500
burstCapacity: 1000
timeout: 2000
retryCount: 2
priority: "normal"
caching:
enabled: true
ttl: 600最佳实践
以下是使用路由元数据配置的最佳实践建议:
1. 元数据命名规范
kotlin
// 推荐的元数据命名方式
data class RouteMetadataKeys(
// 基础信息
val SERVICE = "service"
val VERSION = "version"
val BUSINESS_TYPE = "businessType"
// 性能配置
val RATE_LIMIT = "rateLimit"
val BURST_CAPACITY = "burstCapacity"
val TIMEOUT = "timeout"
val RETRY_COUNT = "retryCount"
// 业务配置
val PRIORITY = "priority"
val CACHING = "caching"
val MONITORING = "monitoring"
)2. 元数据验证
kotlin
@Component
class MetadataValidator {
fun validateRouteMetadata(route: Route): List<String> {
val errors = mutableListOf<String>()
val metadata = route.metadata
// 验证必需的元数据
if (!metadata.containsKey("service")) {
errors.add("路由 ${route.id} 缺少必需的 'service' 元数据")
}
// 验证数值类型的元数据
val rateLimit = metadata["rateLimit"]
if (rateLimit != null && rateLimit !is Int) {
errors.add("路由 ${route.id} 的 'rateLimit' 必须是整数类型")
}
// 验证枚举类型的元数据
val priority = metadata["priority"] as? String
if (priority != null && priority !in listOf("low", "normal", "high", "critical")) {
errors.add("路由 ${route.id} 的 'priority' 值无效: $priority")
}
return errors
}
}3. 元数据动态更新
kotlin
@RestController
@RequestMapping("/admin/routes")
class RouteMetadataController {
private val routeDefinitionRepository: RouteDefinitionRepository
private val applicationEventPublisher: ApplicationEventPublisher
constructor(
routeDefinitionRepository: RouteDefinitionRepository,
applicationEventPublisher: ApplicationEventPublisher
) {
this.routeDefinitionRepository = routeDefinitionRepository
this.applicationEventPublisher = applicationEventPublisher
}
@PutMapping("/{routeId}/metadata")
fun updateRouteMetadata(
@PathVariable routeId: String,
@RequestBody metadata: Map<String, Any>
): ResponseEntity<String> {
return try {
// 获取现有路由定义
val routeDefinition = routeDefinitionRepository.getRoutes()
.filter { it.id == routeId }
.blockFirst()
routeDefinition?.let { rd ->
// 更新元数据
rd.metadata.putAll(metadata)
// 保存更新后的路由定义
routeDefinitionRepository.save(Mono.just(rd)).block()
// 发布路由刷新事件
applicationEventPublisher.publishEvent(RefreshRoutesEvent(this))
ResponseEntity.ok("路由 $routeId 的元数据已更新")
} ?: ResponseEntity.notFound().build()
} catch (e: Exception) {
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("更新路由元数据失败: ${e.message}")
}
}
}注意事项
使用路由元数据时需要注意以下几点:
- 性能影响:过多的元数据会影响路由匹配的性能,建议只添加必要的元数据
- 类型安全:元数据值是
Object类型,使用时需要进行类型转换和验证 - 内存占用:元数据会常驻内存,大量的元数据可能会增加内存使用
- 更新机制:元数据的动态更新需要触发路由刷新事件
不要在元数据中存储敏感信息,如密码、密钥等,这些信息应该通过配置中心或环境变量管理。
总结
Spring Cloud Gateway 的路由元数据配置为微服务网关提供了强大的可扩展性,通过合理使用元数据配置,我们可以:
- 实现动态的路由策略
- 支持细粒度的服务治理
- 提供灵活的监控和运维能力
- 简化复杂业务场景的处理
元数据配置使得网关不仅仅是一个简单的路由转发器,而是成为了微服务架构中重要的治理组件。 🎉