Skip to content

监控和管理

概述

Spring Cloud Gateway 的 /gateway Actuator 端点是一个强大的监控和管理工具,它允许我们监控和交互网关应用程序。在微服务架构中,网关作为流量的入口和出口,其健康状态和路由配置的实时监控对于系统稳定性至关重要。

为了能够远程访问这些端点,必须在应用程序属性中启用并通过 HTTP 或 JMX 暴露端点。

实际应用场景

在生产环境中,Gateway Actuator API 主要解决以下业务问题:

  1. 运维监控:实时查看网关路由状态和过滤器配置
  2. 故障排查:当某个服务出现问题时,快速检查路由配置是否正确
  3. 动态管理:在不重启服务的情况下,动态添加、删除或修改路由
  4. 性能优化:通过查看过滤器链的执行顺序,优化请求处理流程

配置启用

基本配置

properties
# 启用 gateway 端点(默认为 true)
management.endpoint.gateway.enabled=true
# 通过 HTTP 暴露 gateway 端点
management.endpoints.web.exposure.include=gateway
kotlin
@Configuration
@EnableConfigurationProperties(GatewayProperties::class)
class GatewayConfig {

    /**
     * 配置管理端点的安全性
     */
    @Bean
    fun managementSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        return http
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests { auth ->
                auth.requestMatchers("/actuator/health").permitAll()
                    .requestMatchers("/actuator/gateway/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
            }
            .httpBasic(Customizer.withDefaults())
            .build()
    }
}
java
@Configuration
@EnableConfigurationProperties(GatewayProperties.class)
public class GatewayConfig {

    /**
     * 配置管理端点的安全性
     */
    @Bean
    public SecurityFilterChain managementSecurityFilterChain(HttpSecurity http) throws Exception {
        return http
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests(auth ->
                auth.requestMatchers("/actuator/health").permitAll()
                    .requestMatchers("/actuator/gateway/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .build();
    }
}

可用端点概览

当访问 /actuator/gateway 时,会返回所有可用的子端点及其支持的 HTTP 方法:

json
[
  {
    "href": "/actuator/gateway/",
    "methods": ["GET"]
  },
  {
    "href": "/actuator/gateway/routes",
    "methods": ["POST", "GET"]
  },
  {
    "href": "/actuator/gateway/globalfilters",
    "methods": ["GET"]
  },
  {
    "href": "/actuator/gateway/routefilters",
    "methods": ["GET"]
  },
  {
    "href": "/actuator/gateway/refresh",
    "methods": ["POST"]
  }
]

详细功能解析

1. 详细路由格式 (Verbose Actuator Format)

Spring Cloud Gateway 提供了更详细的路由信息格式,包含每个路由的谓词过滤器配置信息

json
[
  {
    "predicate": "(Hosts: [**.addrequestheader.org] && Paths: [/headers], match trailing slash: true)",
    "route_id": "add_request_header_test",
    "filters": [
      "[[AddResponseHeader X-Response-Default-Foo = 'Default-Bar'], order = 1]",
      "[[AddRequestHeader X-Request-Foo = 'Bar'], order = 1]",
      "[[PrefixPath prefix = '/httpbin'], order = 2]"
    ],
    "uri": "lb://testservice",
    "order": 0
  }
]

控制详细格式

properties
# 禁用详细格式(默认启用)
spring.cloud.gateway.actuator.verbose.enabled=false

详细格式在调试和故障排查时非常有用,建议在开发和测试环境中保持启用。

2. 路由过滤器管理

全局过滤器查询

通过 GET /actuator/gateway/globalfilters 查看应用于所有路由的全局过滤器:

响应示例:

json
{
  "org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@77856cc5": 10100,
  "org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@4f6fd101": 10000,
  "org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@32d22650": -1,
  "org.springframework.cloud.gateway.filter.ForwardRoutingFilter@106459d9": 2147483647,
  "org.springframework.cloud.gateway.filter.NettyRoutingFilter@1fbd5e0": 2147483647
}

数字表示过滤器在过滤器链中的执行顺序,数字越小优先级越高。

路由过滤器查询

通过 GET /actuator/gateway/routefilters 查看可用的 GatewayFilter 工厂:

json
{
  "[AddRequestHeaderGatewayFilterFactory@570ed9c configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]": null,
  "[SecureHeadersGatewayFilterFactory@fceab5d configClass = Object]": null,
  "[SaveSessionGatewayFilterFactory@4449b273 configClass = Object]": null
}

3. 路由缓存刷新

在生产环境中,有时需要动态刷新路由配置而不重启服务:

全量刷新

bash
curl -X POST http://localhost:8080/actuator/gateway/refresh

基于元数据的选择性刷新

bash
# 只刷新指定分组的路由
curl -X POST "http://localhost:8080/actuator/gateway/refresh?metadata=group:group-1"
kotlin
@Service
class RouteRefreshService(
    private val routeDefinitionRepository: RouteDefinitionRepository,
    private val applicationEventPublisher: ApplicationEventPublisher
) {

    /**
     * 基于元数据刷新特定路由
     * 适用场景:微服务分组部署,只刷新特定业务模块的路由
     */
    fun refreshRoutesByMetadata(metadataKey: String, metadataValue: String) {
        val routesToRefresh = routeDefinitionRepository.routeDefinitions
            .filter { route ->
                route.metadata[metadataKey] == metadataValue
            }
            .collectList()
            .block()

        routesToRefresh?.forEach { route ->
            // 重新加载路由定义
            applicationEventPublisher.publishEvent(RefreshRoutesEvent(this))
            logger.info("刷新路由: ${route.id}, 元数据: $metadataKey=$metadataValue")
        }
    }

    companion object {
        private val logger = LoggerFactory.getLogger(RouteRefreshService::class.java)
    }
}
java
@Service
public class RouteRefreshService {

    private final RouteDefinitionRepository routeDefinitionRepository;
    private final ApplicationEventPublisher applicationEventPublisher;
    private static final Logger logger = LoggerFactory.getLogger(RouteRefreshService.class);

    public RouteRefreshService(RouteDefinitionRepository routeDefinitionRepository,
                              ApplicationEventPublisher applicationEventPublisher) {
        this.routeDefinitionRepository = routeDefinitionRepository;
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 基于元数据刷新特定路由
     * 适用场景:微服务分组部署,只刷新特定业务模块的路由
     */
    public void refreshRoutesByMetadata(String metadataKey, String metadataValue) {
        List<RouteDefinition> routesToRefresh = routeDefinitionRepository.getRouteDefinitions()
            .filter(route -> metadataValue.equals(route.getMetadata().get(metadataKey)))
            .collectList()
            .block();

        if (routesToRefresh != null) {
            routesToRefresh.forEach(route -> {
                // 重新加载路由定义
                applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
                logger.info("刷新路由: {}, 元数据: {}={}", route.getId(), metadataKey, metadataValue);
            });
        }
    }
}

4. 路由信息查询

查询所有路由

GET /actuator/gateway/routes 返回网关中定义的所有路由:

json
[
  {
    "route_id": "user_service_route",
    "route_object": {
      "predicate": "org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory$$Lambda$432/1736826640@1e9d7e7d",
      "filters": [
        "OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory$$Lambda$436/674480275@6631ef72, order=0}"
      ]
    },
    "order": 0
  }
]

查询特定路由

GET /actuator/gateway/routes/{id} 获取特定路由的详细信息:

json
{
  "id": "user_service_route",
  "predicates": [
    {
      "name": "Path",
      "args": { "_genkey_0": "/api/users/**" }
    }
  ],
  "filters": [
    {
      "name": "StripPrefix",
      "args": { "_genkey_0": "2" }
    }
  ],
  "uri": "lb://user-service",
  "order": 0
}

5. 动态路由管理

创建路由

bash
curl -X POST http://localhost:8080/actuator/gateway/routes/new_route \
  -H "Content-Type: application/json" \
  -d '{
    "predicates": [{
      "name": "Path",
      "args": {"_genkey_0": "/api/orders/**"}
    }],
    "filters": [{
      "name": "StripPrefix",
      "args": {"_genkey_0": "2"}
    }],
    "uri": "lb://order-service",
    "order": 100
  }'

删除路由

bash
curl -X DELETE http://localhost:8080/actuator/gateway/routes/route_to_delete
kotlin
@RestController
@RequestMapping("/admin/routes")
class DynamicRouteController(
    private val routeDefinitionRepository: RouteDefinitionRepository,
    private val applicationEventPublisher: ApplicationEventPublisher
) {

    /**
     * 动态添加路由 - 适用于新服务上线场景
     */
    @PostMapping("/{serviceId}")
    fun addServiceRoute(
        @PathVariable serviceId: String,
        @RequestBody request: CreateRouteRequest
    ): ResponseEntity<String> {

        val routeDefinition = RouteDefinition().apply {
            id = "${serviceId}_route"
            uri = URI.create("lb://$serviceId")
            order = request.order ?: 100

            // 添加路径谓词
            predicates = listOf(
                PredicateDefinition().apply {
                    name = "Path"
                    addArg("_genkey_0", "/api/$serviceId/**")
                }
            )

            // 添加默认过滤器
            filters = listOf(
                FilterDefinition().apply {
                    name = "StripPrefix"
                    addArg("_genkey_0", "2")
                },
                FilterDefinition().apply {
                    name = "AddRequestHeader"
                    addArg("_genkey_0", "X-Service-Name")
                    addArg("_genkey_1", serviceId)
                }
            )

            // 添加元数据
            metadata["service"] = serviceId
            metadata["created_by"] = "admin"
            metadata["created_at"] = Instant.now().toString()
        }

        return try {
            routeDefinitionRepository.save(Mono.just(routeDefinition)).block()
            // 触发路由刷新事件
            applicationEventPublisher.publishEvent(RefreshRoutesEvent(this))

            ResponseEntity.ok("路由 ${routeDefinition.id} 创建成功")
        } catch (e: Exception) {
            ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("创建路由失败: ${e.message}")
        }
    }

    /**
     * 批量创建路由 - 适用于微服务集群初始化
     */
    @PostMapping("/batch")
    fun createMultipleRoutes(@RequestBody services: List<String>): ResponseEntity<Map<String, String>> {
        val results = mutableMapOf<String, String>()

        services.forEach { serviceId ->
            try {
                val routeDefinition = createDefaultRoute(serviceId)
                routeDefinitionRepository.save(Mono.just(routeDefinition)).block()
                results[serviceId] = "成功"
            } catch (e: Exception) {
                results[serviceId] = "失败: ${e.message}"
            }
        }

        // 统一刷新路由
        applicationEventPublisher.publishEvent(RefreshRoutesEvent(this))

        return ResponseEntity.ok(results)
    }

    private fun createDefaultRoute(serviceId: String): RouteDefinition {
        return RouteDefinition().apply {
            id = "${serviceId}_default_route"
            uri = URI.create("lb://$serviceId")
            order = 100

            predicates = listOf(
                PredicateDefinition().apply {
                    name = "Path"
                    addArg("_genkey_0", "/api/$serviceId/**")
                }
            )

            filters = listOf(
                FilterDefinition().apply {
                    name = "StripPrefix"
                    addArg("_genkey_0", "2")
                }
            )

            metadata["service"] = serviceId
            metadata["type"] = "default"
        }
    }

    data class CreateRouteRequest(
        val path: String? = null,
        val order: Int? = null,
        val filters: List<String>? = null
    )
}
java
@RestController
@RequestMapping("/admin/routes")
public class DynamicRouteController {

    private final RouteDefinitionRepository routeDefinitionRepository;
    private final ApplicationEventPublisher applicationEventPublisher;

    public DynamicRouteController(RouteDefinitionRepository routeDefinitionRepository,
                                 ApplicationEventPublisher applicationEventPublisher) {
        this.routeDefinitionRepository = routeDefinitionRepository;
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 动态添加路由 - 适用于新服务上线场景
     */
    @PostMapping("/{serviceId}")
    public ResponseEntity<String> addServiceRoute(@PathVariable String serviceId,
                                                  @RequestBody CreateRouteRequest request) {

        RouteDefinition routeDefinition = new RouteDefinition();
        routeDefinition.setId(serviceId + "_route");
        routeDefinition.setUri(URI.create("lb://" + serviceId));
        routeDefinition.setOrder(request.getOrder() != null ? request.getOrder() : 100);

        // 添加路径谓词
        PredicateDefinition pathPredicate = new PredicateDefinition();
        pathPredicate.setName("Path");
        pathPredicate.addArg("_genkey_0", "/api/" + serviceId + "/**");
        routeDefinition.setPredicates(List.of(pathPredicate));

        // 添加默认过滤器
        FilterDefinition stripPrefixFilter = new FilterDefinition();
        stripPrefixFilter.setName("StripPrefix");
        stripPrefixFilter.addArg("_genkey_0", "2");

        FilterDefinition headerFilter = new FilterDefinition();
        headerFilter.setName("AddRequestHeader");
        headerFilter.addArg("_genkey_0", "X-Service-Name");
        headerFilter.addArg("_genkey_1", serviceId);

        routeDefinition.setFilters(List.of(stripPrefixFilter, headerFilter));

        // 添加元数据
        routeDefinition.setMetadata(Map.of(
            "service", serviceId,
            "created_by", "admin",
            "created_at", Instant.now().toString()
        ));

        try {
            routeDefinitionRepository.save(Mono.just(routeDefinition)).block();
            // 触发路由刷新事件
            applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));

            return ResponseEntity.ok("路由 " + routeDefinition.getId() + " 创建成功");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("创建路由失败: " + e.getMessage());
        }
    }

    public static class CreateRouteRequest {
        private String path;
        private Integer order;
        private List<String> filters;

        // getters and setters
        public String getPath() { return path; }
        public void setPath(String path) { this.path = path; }
        public Integer getOrder() { return order; }
        public void setOrder(Integer order) { this.order = order; }
        public List<String> getFilters() { return filters; }
        public void setFilters(List<String> filters) { this.filters = filters; }
    }
}

实际业务场景示例

场景一:新服务上线

当新的微服务需要接入网关时,可以使用 Actuator API 动态添加路由:

场景二:故障排查

当某个服务出现问题时,运维人员可以通过 Actuator API 快速排查:

  1. 检查路由配置GET /actuator/gateway/routes/problematic-service
  2. 查看过滤器状态GET /actuator/gateway/globalfilters
  3. 验证路由谓词:分析返回的 predicate 配置

场景三:灰度发布

在灰度发布场景中,可以动态调整路由权重:

kotlin
@RestController
@RequestMapping("/admin/gray-release")
class GrayReleaseController(
    private val routeDefinitionRepository: RouteDefinitionRepository,
    private val applicationEventPublisher: ApplicationEventPublisher
) {

    /**
     * 创建灰度路由 - 将部分流量导向新版本
     */
    @PostMapping("/{serviceId}/enable")
    fun enableGrayRelease(
        @PathVariable serviceId: String,
        @RequestParam(defaultValue = "10") weight: Int
    ): ResponseEntity<String> {

        // 创建灰度路由(权重路由)
        val grayRoute = RouteDefinition().apply {
            id = "${serviceId}_gray_route"
            uri = URI.create("lb://${serviceId}-gray")
            order = 1 // 优先级高于正常路由

            predicates = listOf(
                PredicateDefinition().apply {
                    name = "Path"
                    addArg("_genkey_0", "/api/$serviceId/**")
                },
                PredicateDefinition().apply {
                    name = "Weight"
                    addArg("_genkey_0", serviceId)
                    addArg("_genkey_1", weight.toString())
                }
            )

            filters = listOf(
                FilterDefinition().apply {
                    name = "AddRequestHeader"
                    addArg("_genkey_0", "X-Gray-Release")
                    addArg("_genkey_1", "true")
                }
            )

            metadata["type"] = "gray-release"
            metadata["weight"] = weight.toString()
        }

        // 更新正常路由的权重
        val normalWeight = 100 - weight
        updateNormalRouteWeight(serviceId, normalWeight)

        routeDefinitionRepository.save(Mono.just(grayRoute)).block()
        applicationEventPublisher.publishEvent(RefreshRoutesEvent(this))

        return ResponseEntity.ok("灰度发布已启用,灰度流量占比: $weight%")
    }

    private fun updateNormalRouteWeight(serviceId: String, weight: Int) {
        // 实现正常路由权重更新逻辑
        // ...
    }
}
java
@RestController
@RequestMapping("/admin/gray-release")
public class GrayReleaseController {

    private final RouteDefinitionRepository routeDefinitionRepository;
    private final ApplicationEventPublisher applicationEventPublisher;

    public GrayReleaseController(RouteDefinitionRepository routeDefinitionRepository,
                                ApplicationEventPublisher applicationEventPublisher) {
        this.routeDefinitionRepository = routeDefinitionRepository;
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 创建灰度路由 - 将部分流量导向新版本
     */
    @PostMapping("/{serviceId}/enable")
    public ResponseEntity<String> enableGrayRelease(@PathVariable String serviceId,
                                                    @RequestParam(defaultValue = "10") int weight) {

        // 创建灰度路由(权重路由)
        RouteDefinition grayRoute = new RouteDefinition();
        grayRoute.setId(serviceId + "_gray_route");
        grayRoute.setUri(URI.create("lb://" + serviceId + "-gray"));
        grayRoute.setOrder(1); // 优先级高于正常路由

        // 设置谓词
        PredicateDefinition pathPredicate = new PredicateDefinition();
        pathPredicate.setName("Path");
        pathPredicate.addArg("_genkey_0", "/api/" + serviceId + "/**");

        PredicateDefinition weightPredicate = new PredicateDefinition();
        weightPredicate.setName("Weight");
        weightPredicate.addArg("_genkey_0", serviceId);
        weightPredicate.addArg("_genkey_1", String.valueOf(weight));

        grayRoute.setPredicates(List.of(pathPredicate, weightPredicate));

        // 设置过滤器
        FilterDefinition headerFilter = new FilterDefinition();
        headerFilter.setName("AddRequestHeader");
        headerFilter.addArg("_genkey_0", "X-Gray-Release");
        headerFilter.addArg("_genkey_1", "true");

        grayRoute.setFilters(List.of(headerFilter));

        // 设置元数据
        grayRoute.setMetadata(Map.of(
            "type", "gray-release",
            "weight", String.valueOf(weight)
        ));

        // 更新正常路由的权重
        int normalWeight = 100 - weight;
        updateNormalRouteWeight(serviceId, normalWeight);

        routeDefinitionRepository.save(Mono.just(grayRoute)).block();
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));

        return ResponseEntity.ok("灰度发布已启用,灰度流量占比: " + weight + "%");
    }

    private void updateNormalRouteWeight(String serviceId, int weight) {
        // 实现正常路由权重更新逻辑
        // ...
    }
}

端点安全配置

在生产环境中,这些管理端点需要适当的安全保护:

kotlin
@Configuration
@EnableWebSecurity
class ActuatorSecurityConfig {

    @Bean
    fun actuatorSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        return http
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests { auth ->
                auth
                    // 健康检查端点允许匿名访问
                    .requestMatchers("/actuator/health").permitAll()
                    .requestMatchers("/actuator/info").permitAll()
                    // 网关管理端点需要管理员权限
                    .requestMatchers("/actuator/gateway/**").hasRole("GATEWAY_ADMIN")
                    // 其他端点需要运维权限
                    .requestMatchers("/actuator/**").hasRole("OPS")
                    .anyRequest().authenticated()
            }
            .httpBasic(Customizer.withDefaults())
            .sessionManagement { session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            }
            .build()
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()

    @Bean
    fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService {
        val admin = User.builder()
            .username("gateway-admin")
            .password(passwordEncoder.encode("secure-password"))
            .roles("GATEWAY_ADMIN", "OPS")
            .build()

        val ops = User.builder()
            .username("ops-user")
            .password(passwordEncoder.encode("ops-password"))
            .roles("OPS")
            .build()

        return InMemoryUserDetailsManager(admin, ops)
    }
}
java
@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {

    @Bean
    public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
        return http
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests(auth ->
                auth
                    // 健康检查端点允许匿名访问
                    .requestMatchers("/actuator/health").permitAll()
                    .requestMatchers("/actuator/info").permitAll()
                    // 网关管理端点需要管理员权限
                    .requestMatchers("/actuator/gateway/**").hasRole("GATEWAY_ADMIN")
                    // 其他端点需要运维权限
                    .requestMatchers("/actuator/**").hasRole("OPS")
                    .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails admin = User.builder()
            .username("gateway-admin")
            .password(passwordEncoder.encode("secure-password"))
            .roles("GATEWAY_ADMIN", "OPS")
            .build();

        UserDetails ops = User.builder()
            .username("ops-user")
            .password(passwordEncoder.encode("ops-password"))
            .roles("OPS")
            .build();

        return new InMemoryUserDetailsManager(admin, ops);
    }
}

监控和告警

结合监控系统,可以对网关状态进行实时监控:

kotlin
@Service
class GatewayMonitoringService(
    private val webClient: WebClient,
    private val meterRegistry: MeterRegistry
) {

    private val logger = LoggerFactory.getLogger(GatewayMonitoringService::class.java)

    /**
     * 定期检查网关健康状态
     */
    @Scheduled(fixedRate = 30000) // 每30秒检查一次
    fun checkGatewayHealth() {
        try {
            val routes = getGatewayRoutes()
            val routeCount = routes.size

            // 记录路由数量指标
            meterRegistry.gauge("gateway.routes.count", routeCount)

            // 检查路由健康状态
            routes.forEach { route ->
                checkRouteHealth(route)
            }

            logger.debug("网关健康检查完成,当前路由数量: $routeCount")

        } catch (e: Exception) {
            logger.error("网关健康检查失败", e)
            // 发送告警
            sendAlert("网关健康检查失败: ${e.message}")
        }
    }

    private fun getGatewayRoutes(): List<RouteInfo> {
        return webClient.get()
            .uri("/actuator/gateway/routes")
            .retrieve()
            .bodyToMono(object : ParameterizedTypeReference<List<RouteInfo>>() {})
            .block() ?: emptyList()
    }

    private fun checkRouteHealth(route: RouteInfo) {
        // 检查特定路由的健康状态
        // 可以通过调用目标服务的健康检查端点
        // ...
    }

    private fun sendAlert(message: String) {
        // 实现告警逻辑(如发送到Slack、钉钉等)
        logger.warn("发送告警: $message")
    }

    data class RouteInfo(
        val route_id: String,
        val uri: String,
        val order: Int
    )
}
java
@Service
public class GatewayMonitoringService {

    private final WebClient webClient;
    private final MeterRegistry meterRegistry;
    private static final Logger logger = LoggerFactory.getLogger(GatewayMonitoringService.class);

    public GatewayMonitoringService(WebClient webClient, MeterRegistry meterRegistry) {
        this.webClient = webClient;
        this.meterRegistry = meterRegistry;
    }

    /**
     * 定期检查网关健康状态
     */
    @Scheduled(fixedRate = 30000) // 每30秒检查一次
    public void checkGatewayHealth() {
        try {
            List<RouteInfo> routes = getGatewayRoutes();
            int routeCount = routes.size();

            // 记录路由数量指标
            meterRegistry.gauge("gateway.routes.count", routeCount);

            // 检查路由健康状态
            routes.forEach(this::checkRouteHealth);

            logger.debug("网关健康检查完成,当前路由数量: {}", routeCount);

        } catch (Exception e) {
            logger.error("网关健康检查失败", e);
            // 发送告警
            sendAlert("网关健康检查失败: " + e.getMessage());
        }
    }

    private List<RouteInfo> getGatewayRoutes() {
        return webClient.get()
            .uri("/actuator/gateway/routes")
            .retrieve()
            .bodyToMono(new ParameterizedTypeReference<List<RouteInfo>>() {})
            .block();
    }

    private void checkRouteHealth(RouteInfo route) {
        // 检查特定路由的健康状态
        // 可以通过调用目标服务的健康检查端点
        // ...
    }

    private void sendAlert(String message) {
        // 实现告警逻辑(如发送到Slack、钉钉等)
        logger.warn("发送告警: {}", message);
    }

    public static class RouteInfo {
        private String route_id;
        private String uri;
        private int order;

        // getters and setters
        public String getRoute_id() { return route_id; }
        public void setRoute_id(String route_id) { this.route_id = route_id; }
        public String getUri() { return uri; }
        public void setUri(String uri) { this.uri = uri; }
        public int getOrder() { return order; }
        public void setOrder(int order) { this.order = order; }
    }
}

端点总览

端点HTTP 方法描述应用场景
globalfiltersGET显示应用于所有路由的全局过滤器列表调试过滤器执行顺序
routefiltersGET显示应用于特定路由的 GatewayFilter 工厂列表了解可用的过滤器类型
refreshPOST清除路由缓存配置更新后刷新路由
routesGET显示网关中定义的路由列表查看当前所有路由配置
routesPOST批量创建多个路由定义批量部署新服务
routes/{id}GET显示特定路由的信息故障排查特定路由
routes/{id}POST向网关添加新路由动态添加新服务路由
routes/{id}DELETE从网关删除现有路由下线服务时清理路由

最佳实践

TIP

运维建议

  1. 在生产环境中,建议为 Actuator 端点配置独立的端口和安全认证
  2. 定期监控路由数量和配置变化,及时发现异常
  3. 在自动化部署流程中集成路由管理 API,实现服务的自动接入和下线

WARNING

安全注意事项

  1. 网关管理端点具有很高的权限,必须严格控制访问权限
  2. 避免在公网暴露管理端点,建议通过 VPN 或内网访问
  3. 定期审计管理端点的访问日志

CAUTION

操作风险

  1. 删除路由操作不可逆,删除前务必确认影响范围
  2. 批量操作时,如果某个路由配置错误,可能影响整个操作的成功
  3. 刷新路由缓存时,可能短暂影响正在处理的请求

通过 Spring Cloud Gateway 的 Actuator API,我们可以实现网关的可观测性和动态管理,这对于构建稳定、可靠的微服务架构至关重要。合理使用这些 API 可以大大提升系统的运维效率和故障处理能力。