Skip to content

多实例间共享路由

概述

在微服务架构中,为了保证高可用性和负载均衡,我们通常会部署多个 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: 0

3. 自定义 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: 3000ms

2. 路由配置验证

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 路由仓库时的重要考虑事项:

  1. Redis 连接稳定性:确保 Redis 集群的高可用性,避免因 Redis 故障导致路由配置丢失
  2. 网络延迟:Redis 读写操作会引入网络延迟,需要合理配置超时时间
  3. 数据一致性:在高并发场景下,需要考虑路由配置的最终一致性
  4. 安全性:配置 Redis 认证和网络安全策略

建议在生产环境中:

  • 使用 Redis Sentinel 或 Cluster 模式
  • 配置适当的连接池大小
  • 监控 Redis 性能指标
  • 定期备份路由配置

总结

Spring Cloud Gateway 的 Redis 路由仓库解决了多实例部署中路由配置共享的关键问题。通过将路由配置存储在 Redis 中,实现了:

  • ✅ 多实例间路由配置一致性
  • ✅ 动态路由管理能力
  • ✅ 路由配置持久化存储
  • ✅ 高可用性部署支持

这使得我们可以构建更加灵活和可靠的微服务网关架构,满足现代分布式系统的需求。