Appearance
多实例间共享路由
概述
在微服务架构中,为了保证高可用性和负载均衡,我们通常会部署多个 Spring Cloud Gateway 实例。但是,如何在这些实例间共享路由配置成为了一个关键问题。Spring Cloud Gateway 提供了两种RouteDefinitionRepository实现来解决这个问题。
业务场景
想象一个电商平台,有用户服务、订单服务、商品服务等多个微服务,前端通过 API 网关访问这些服务。为了保证系统的高可用性,我们部署了 3 个 Gateway 实例:
在这种架构下,每个 Gateway 实例都需要有相同的路由配置,否则用户可能会遇到"有时能访问,有时不能访问"的问题。
路由定义仓库实现
1. InMemoryRouteDefinitionRepository(内存路由仓库)
这是 Spring Cloud Gateway 的默认实现,路由配置仅存储在单个 Gateway 实例的内存中。
WARNING
InMemoryRouteDefinitionRepository不适合用于多 Gateway 实例的集群部署,因为路由配置无法在实例间共享。
问题示例
假设我们有两个 Gateway 实例,使用内存路由仓库:
kotlin
@Configuration
class RouteConfig {
@Bean
fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
return builder.routes()
.route("user-service") { r ->
r.path("/api/users/**")
.uri("lb://user-service")
}
.route("order-service") { r ->
r.path("/api/orders/**")
.uri("lb://order-service")
}
.build()
}
}kotlin
@Configuration
class RouteConfig {
@Bean
fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
return builder.routes()
.route("user-service") { r ->
r.path("/api/users/**")
.uri("lb://user-service")
}
// 假设这里忘记配置order-service路由
.build()
}
}在这种情况下,用户访问订单服务时:
- 请求被负载均衡到实例 1 → ✅ 成功访问
- 请求被负载均衡到实例 2 → ❌ 404 错误
2. RedisRouteDefinitionRepository(Redis 路由仓库)
为了解决多实例间路由共享的问题,Spring Cloud Gateway 提供了基于 Redis 的路由定义仓库。
配置 Redis 路由仓库
1. 添加依赖
首先需要添加 Redis 响应式依赖:
kotlin
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
implementation("org.springframework.cloud:spring-cloud-starter-gateway")
// 其他依赖...
}xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 其他依赖... -->
</dependencies>2. 启用 Redis 路由仓库
在application.yml中配置:
yaml
spring:
cloud:
gateway:
# 启用Redis路由定义仓库
redis-route-definition-repository:
enabled: true
# Redis连接配置
redis:
host: localhost
port: 6379
password: your-password
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 03. 自定义 Redis 路由仓库配置
kotlin
@Configuration
@EnableConfigurationProperties(GatewayProperties::class)
class RedisRouteConfig {
/**
* 配置Redis路由定义仓库
* 使用Redis作为路由配置的持久化存储
*/
@Bean
@ConditionalOnProperty(
name = ["spring.cloud.gateway.redis-route-definition-repository.enabled"],
havingValue = "true"
)
fun redisRouteDefinitionRepository(
reactiveRedisTemplate: ReactiveRedisTemplate<String, String>
): RouteDefinitionRepository {
return RedisRouteDefinitionRepository(reactiveRedisTemplate)
}
/**
* 配置Redis连接工厂
*/
@Bean
fun lettuceConnectionFactory(): LettuceConnectionFactory {
val config = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(3))
.shutdownTimeout(Duration.ofSeconds(3))
.build()
return LettuceConnectionFactory(
RedisStandaloneConfiguration("localhost", 6379),
config
)
}
}动态路由管理
使用 Redis 路由仓库后,我们可以实现动态路由管理:
1. 路由管理服务
kotlin
@Service
class RouteManagementService(
private val routeDefinitionRepository: RouteDefinitionRepository,
private val applicationEventPublisher: ApplicationEventPublisher
) {
/**
* 添加新路由
*/
fun addRoute(routeDefinition: RouteDefinition): Mono<Void> {
return routeDefinitionRepository.save(
Mono.just(routeDefinition)
).then(
// 发布路由刷新事件
Mono.fromRunnable {
applicationEventPublisher.publishEvent(
RefreshRoutesEvent(this)
)
}
)
}
/**
* 删除路由
*/
fun deleteRoute(routeId: String): Mono<Void> {
return routeDefinitionRepository.delete(
Mono.just(routeId)
).then(
Mono.fromRunnable {
applicationEventPublisher.publishEvent(
RefreshRoutesEvent(this)
)
}
)
}
/**
* 获取所有路由
*/
fun getAllRoutes(): Flux<RouteDefinition> {
return routeDefinitionRepository.routeDefinitions
}
}2. 路由管理控制器
kotlin
@RestController
@RequestMapping("/admin/routes")
class RouteController(
private val routeManagementService: RouteManagementService
) {
/**
* 获取所有路由配置
*/
@GetMapping
fun getAllRoutes(): Flux<RouteDefinition> {
return routeManagementService.getAllRoutes()
}
/**
* 添加新路由
*/
@PostMapping
fun addRoute(@RequestBody routeDefinition: RouteDefinition): Mono<ResponseEntity<String>> {
return routeManagementService.addRoute(routeDefinition)
.map { ResponseEntity.ok("路由添加成功") }
.onErrorReturn(ResponseEntity.badRequest().body("路由添加失败"))
}
/**
* 删除指定路由
*/
@DeleteMapping("/{routeId}")
fun deleteRoute(@PathVariable routeId: String): Mono<ResponseEntity<String>> {
return routeManagementService.deleteRoute(routeId)
.map { ResponseEntity.ok("路由删除成功") }
.onErrorReturn(ResponseEntity.badRequest().body("路由删除失败"))
}
}实际使用示例
场景:动态添加新服务路由
假设我们的电商平台要上线一个新的推荐服务,需要动态添加路由配置:
kotlin
@Component
class ServiceDeploymentHandler(
private val routeManagementService: RouteManagementService
) {
/**
* 服务上线时自动添加路由
*/
@EventListener
fun handleServiceRegistered(event: ServiceRegisteredEvent) {
if (event.serviceName == "recommendation-service") {
val routeDefinition = RouteDefinition().apply {
id = "recommendation-service"
uri = URI.create("lb://recommendation-service")
predicates = listOf(
PredicateDefinition().apply {
name = "Path"
args = mapOf("pattern" to "/api/recommendations/**")
}
)
filters = listOf(
FilterDefinition().apply {
name = "StripPrefix"
args = mapOf("parts" to "2")
}
)
}
routeManagementService.addRoute(routeDefinition)
.subscribe(
{ logger.info("推荐服务路由添加成功") },
{ error -> logger.error("推荐服务路由添加失败", error) }
)
}
}
}路由同步机制
当使用 Redis 路由仓库时,路由同步的流程如下:
配置最佳实践
1. Redis 高可用配置
yaml
spring:
redis:
# 使用Redis Sentinel或Cluster保证高可用
sentinel:
master: mymaster
nodes:
- 192.168.1.10:26379
- 192.168.1.11:26379
- 192.168.1.12:26379
lettuce:
pool:
max-active: 50 # 根据并发量调整
max-idle: 20
min-idle: 5
max-wait: 3000ms2. 路由配置验证
kotlin
@Component
class RouteValidator {
/**
* 验证路由配置的有效性
*/
fun validateRoute(routeDefinition: RouteDefinition): List<String> {
val errors = mutableListOf<String>()
// 检查路由ID
if (routeDefinition.id.isBlank()) {
errors.add("路由ID不能为空")
}
// 检查URI格式
try {
URI.create(routeDefinition.uri.toString())
} catch (e: Exception) {
errors.add("URI格式不正确: ${routeDefinition.uri}")
}
// 检查谓词配置
if (routeDefinition.predicates.isEmpty()) {
errors.add("至少需要配置一个路由谓词")
}
return errors
}
}监控和诊断
1. 路由状态监控
kotlin
@Component
class RouteHealthIndicator(
private val routeDefinitionRepository: RouteDefinitionRepository
) : HealthIndicator {
override fun health(): Health {
return try {
val routeCount = routeDefinitionRepository.routeDefinitions.count().block()
Health.up()
.withDetail("route_count", routeCount)
.withDetail("repository_type", "Redis")
.build()
} catch (e: Exception) {
Health.down()
.withDetail("error", e.message)
.build()
}
}
}2. 路由同步日志
kotlin
@EventListener
class RouteEventListener {
private val logger = LoggerFactory.getLogger(RouteEventListener::class.java)
@EventListener
fun handleRefreshRoutes(event: RefreshRoutesEvent) {
logger.info("路由刷新事件触发,来源: ${event.source}")
}
}注意事项
使用 Redis 路由仓库时的重要考虑事项:
- Redis 连接稳定性:确保 Redis 集群的高可用性,避免因 Redis 故障导致路由配置丢失
- 网络延迟:Redis 读写操作会引入网络延迟,需要合理配置超时时间
- 数据一致性:在高并发场景下,需要考虑路由配置的最终一致性
- 安全性:配置 Redis 认证和网络安全策略
建议在生产环境中:
- 使用 Redis Sentinel 或 Cluster 模式
- 配置适当的连接池大小
- 监控 Redis 性能指标
- 定期备份路由配置
总结
Spring Cloud Gateway 的 Redis 路由仓库解决了多实例部署中路由配置共享的关键问题。通过将路由配置存储在 Redis 中,实现了:
- ✅ 多实例间路由配置一致性
- ✅ 动态路由管理能力
- ✅ 路由配置持久化存储
- ✅ 高可用性部署支持
这使得我们可以构建更加灵活和可靠的微服务网关架构,满足现代分布式系统的需求。