解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题

在 Spring Cloud 应用篇 之 Hystrix Turbine(断路器聚合监控)的基本搭建 这篇文章中,讲解了 Spring Cloud Hystrix Turbine 集群监控服务,但是,如果被监控的服务配置了 context-path 这个属性,那么,你的监控就会出现问题,就如同 Spring Boot Admin 监控出现的问题一样,关于 Spring Boot Admin 的这个问题,可以看一下这篇文章:解决 Spring Cloud 的服务应用配置 context-path 后 Spring Boot Admin 监控不到信息的问题。言归正传,下面我们接着分析服务配置了 context-path 后 Hystrix Turbine 聚合监控不到的问题。

(一)被监控的服务都配置了 context-path 属性

拿之前文章里写的服务 spring-demo-service-feign 做例子

spring-demo-service-feign 的配置文件,添加 context-path 的配置如下:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#  如果项目配置有 server.servlet.context-path 属性,想要被 spring boot admin 监控,就要配置以下属性
  instance:
    metadata-map:
      management:
        context-path: /gateway/actuator
#    解决配置了 context-path 后 spring boot admin 监控第一种方案
#    health-check-url: http://localhost:${server.port}/gateway/actuator/health
#    status-page-url: http://localhost:${server.port}/gateway/actuator/info
#    home-page-url: http://localhost:${server.port}/

#    解决配置了 context-path 后 spring boot admin 监控的第二种方案(优化)
    health-check-url-path: /gateway/actuator/health

server:
  port: 8382
  servlet:
    context-path: /gateway
spring:
  application:
    name: spring-demo-service-feign

feign:
  hystrix:
    enabled: true

# Ribbon 的负载均衡策略
spring-demo-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
      health:
        show-details: ALWAYS
info:
  version: 1.0.0

启动 eureka server、spring-demo-service、spring-demo-service-feign、spring-cloud-hystrix-dashboard-turbine 服务

访问 http://localhost:8581/hystrix,输入 http://localhost:8581/turbine.stream,点击 Monitor Stream,发现界面一直是 Loading...状态,之前说过这个可能是因为没有请求数据,那么我们就访问一下接口

解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题_第1张图片

访问 http://localhost:8382/gateway/hello

解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题_第2张图片

接口已访问,为了证明此时已经有 hystrix 的数据,我们访问一下 http://localhost:8382/gateway/actuator/hystrix.stream

解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题_第3张图片

可见,已经产生数据了,此时刷新 hystrix turbine 的界面,你会发现仍然是 Loading 界面,这就是因为我们服务配置了 context-path 的原因,解决这个问题也很简单,只需要做一些配置即可,具体原理我们等下再说,先给出解决方案

修改 spring-cloud-hystrix-dashboard-turbine 服务的配置如下,重启 spring-cloud-hystrix-dashboard-turbine

server:
  port: 8581

spring:
  application:
    name: spring-cloud-hystrix-dashboard-turbine

turbine:
#  1.被监控的服务应用没有配置 context-path 的情况下
  # 配置 Eureka 中的 serviceId 列表,指定要监控的服务
#  app-config: SPRING-DEMO-SERVICE-FEIGN,SPRING-DEMO-SERVICE-RIBBON
#  aggregator:
#    cluster-config: default
#  # 指定集群名称
#  cluster-name-expression: "'default'"

#  2.被监控的服务应用配置了 context-path 的情况下,此时默认是集群里的应用都配置了 context-path
  # 配置 Eureka 中的 serviceId 列表,指定要监控的服务
  app-config: SPRING-DEMO-SERVICE-FEIGN
  aggregator:
    cluster-config: default
  # 指定集群名称
  cluster-name-expression: "'default'"
  instanceUrlSuffix: gateway/actuator/hystrix.stream

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/


management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
      health:
        show-details: ALWAYS

重新执行访问 http://localhost:8581/hystrix,输入 http://localhost:8581/turbine.stream 的步骤,界面如下,可以看到,已经监控到了 spring-demo-service-feign

解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题_第4张图片

这种情况是被监控的服务都配置了 context-path 属性,如果被监控的服务一部分配置了 context-path,另外一些没有配置呢,也很好办,把配置了 context-path 属性的服务聚合为一个集群,没有配置的服务作为另一个集群,具体如下

(二)被监控的服务部分配置了 context-path

仍然是拿之前的服务作为例子,spring-demo-service-ribbon(没有配置 context-path)、spring-demo-service-feign(配置了 context-path)

修改 spring-cloud-hystrix-dashboard-turbine 的配置文件:

server:
  port: 8581

spring:
  application:
    name: spring-cloud-hystrix-dashboard-turbine

turbine:
#  1.被监控的服务应用没有配置 context-path 的情况下
  # 配置 Eureka 中的 serviceId 列表,指定要监控的服务
#  app-config: SPRING-DEMO-SERVICE-FEIGN,SPRING-DEMO-SERVICE-RIBBON
#  aggregator:
#    cluster-config: default
#  # 指定集群名称
#  cluster-name-expression: "'default'"

#  2.被监控的服务应用配置了 context-path 的情况下,此时默认是集群里的应用都配置了 context-path
  # 配置 Eureka 中的 serviceId 列表,指定要监控的服务
#  app-config: SPRING-DEMO-SERVICE-FEIGN
#  aggregator:
#    cluster-config: default
#  # 指定集群名称
#  cluster-name-expression: "'default'"
#  instanceUrlSuffix: gateway/actuator/hystrix.stream

#  3.被监控的服务应用一部分配置了 context-path,一部分没有配置 context-path
  # 配置 Eureka 中的 serviceId 列表,指定要监控的服务
  app-config: SPRING-DEMO-SERVICE-FEIGN,SPRING-DEMO-SERVICE-RIBBON
  aggregator:
    cluster-config: SPRING-DEMO-SERVICE-FEIGN,SPRING-DEMO-SERVICE-RIBBON
  # 指定集群名称
  cluster-name-expression: metadata['cluster']
  instanceUrlSuffix:
    SPRING-DEMO-SERVICE-FEIGN: gateway/actuator/hystrix.stream

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/


management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
      health:
        show-details: ALWAYS

 

spring-demo-service-ribbon 作为一个集群,spring-demo-service-feign 作为一个集群,然后对 spring-demo-service-feign 集群做特俗配置,只做这些配置还不够,还需要对 spring-demo-service-ribbon 和 spring-demo-service-feign 的配置进行修改,具体修改如下:

spring-demo-service-ribbon 的配置文件:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    metadata-map:
      cluster: SPRING-DEMO-SERVICE-RIBBON
server:
  port: 8381
spring:
  application:
    name: spring-demo-service-ribbon

spring-demo-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
      health:
        show-details: ALWAYS

spring-demo-service-feign 的配置文件:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#  如果项目配置有 server.servlet.context-path 属性,想要被 spring boot admin 监控,就要配置以下属性
  instance:
    metadata-map:
      cluster: SPRING-DEMO-SERVICE-FEIGN
      management:
        context-path: /gateway/actuator
#    解决配置了 context-path 后 spring boot admin 监控第一种方案
#    health-check-url: http://localhost:${server.port}/gateway/actuator/health
#    status-page-url: http://localhost:${server.port}/gateway/actuator/info
#    home-page-url: http://localhost:${server.port}/

#    解决配置了 context-path 后 spring boot admin 监控第二种方案(优化)
    health-check-url-path: /gateway/actuator/health

server:
  port: 8382
  servlet:
    context-path: /gateway
spring:
  application:
    name: spring-demo-service-feign

feign:
  hystrix:
    enabled: true

# Ribbon 的负载均衡策略
spring-demo-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
      health:
        show-details: ALWAYS
info:
  version: 1.0.0

可以看到,都是添加了 eureka.instance.metadata-map.cluster 这个属性,这个属性就是设置服务的集群。

至此,配置已全部完成,依次启动服务,访问 http://localhost:8581/hystrix,此时就不是填写 http://localhost:8581/turbine.stream 了,而是要加上集群名,如:http://localhost:8581/turbine.stream?cluster=SPRING-DEMO-SERVICE-RIBBON,可以看到,已经成功监控

解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题_第5张图片

再来看看对 spring-demo-service-feign 的监控,输入:http://localhost:8581/turbine.stream?cluster=SPRING-DEMO-SERVICE-FEIGN,可以看到也成功监控了

解决 Spring Cloud 的服务应用配置 context-path 后 Spring Cloud Hystrix Turbine 监控不到信息的问题_第6张图片

(三)简单的源码分析

源码在 spring-cloud-netflix-turbine 的 jar 包下,具体在 org.springframework.cloud.netflix.turbine.SpringClusterMonitor 这个类,源码如下:

public class SpringClusterMonitor extends AggregateClusterMonitor {

	// TODO: convert to ConfigurationProperties (how to do per-cluster configuration?

	public SpringClusterMonitor(String name, String clusterName) {
		super(name, new ObservationCriteria.ClusterBasedObservationCriteria(clusterName),
				new PerformanceCriteria.AggClusterPerformanceCriteria(clusterName),
				new MonitorConsole(), InstanceMonitorDispatcher,
				SpringClusterMonitor.ClusterConfigBasedUrlClosure);
	}

	/**
	 * TODO: make this a template of some kind (secure, management port, etc...) Helper
	 * class that decides how to connect to a server based on injected config. Note that
	 * the cluster name must be provided here since one can have different configs for
	 * different clusters
	 */
	public static InstanceUrlClosure ClusterConfigBasedUrlClosure = new InstanceUrlClosure() {

		private final DynamicStringProperty defaultUrlClosureConfig = DynamicPropertyFactory
				.getInstance().getStringProperty("turbine.instanceUrlSuffix",
						"actuator/hystrix.stream");
		private final DynamicBooleanProperty instanceInsertPort = DynamicPropertyFactory
				.getInstance().getBooleanProperty("turbine.instanceInsertPort", true);

		@Override
		public String getUrlPath(Instance host) {
			if (host.getCluster() == null) {
				throw new RuntimeException(
						"Host must have cluster name in order to use ClusterConfigBasedUrlClosure");
			}

			// find url
			String key = "turbine.instanceUrlSuffix." + host.getCluster();
			DynamicStringProperty urlClosureConfig = DynamicPropertyFactory.getInstance()
					.getStringProperty(key, null);
			String url = urlClosureConfig.get();
			if (url == null) {
				url = this.defaultUrlClosureConfig.get();
			}
			if (url == null) {
				throw new RuntimeException("Config property: "
						+ urlClosureConfig.getName() + " or "
						+ this.defaultUrlClosureConfig.getName() + " must be set");
			}

			// find port and scheme
			String port;
			String scheme;
			if (host.getAttributes().containsKey("securePort")) {
				port = host.getAttributes().get("securePort");
				scheme = "https";
			} else {
				port = host.getAttributes().get("port");
				scheme = "http";
			}

			if (host.getAttributes().containsKey("fusedHostPort")) {
				return String.format("%s://%s/%s", scheme, host.getAttributes().get("fusedHostPort"), url);
			}

			// determine if to insert port
			String insertPortKey = "turbine.instanceInsertPort." + host.getCluster();
			DynamicStringProperty insertPortProp = DynamicPropertyFactory.getInstance()
					.getStringProperty(insertPortKey, null);
			boolean insertPort;
			if (insertPortProp.get() == null) {
				insertPort = this.instanceInsertPort.get();
			}
			else {
				insertPort = Boolean.parseBoolean(insertPortProp.get());
			}

			// format url with port
			if (insertPort) {
				if (url.startsWith("/")) {
					url = url.substring(1);
				}
				if (port == null) {
					throw new RuntimeException(
							"Configured to use port, but port or securePort is not in host attributes");
				}

				return String.format("%s://%s:%s/%s", scheme, host.getHostname(), port, url);
			}

			//format url without port
			return scheme + "://" + host.getHostname() + url;
		}
	};

}

可以看到源码里有这个操作:

private final DynamicStringProperty defaultUrlClosureConfig = DynamicPropertyFactory
				.getInstance().getStringProperty("turbine.instanceUrlSuffix",
						"actuator/hystrix.stream");

 就是获取 turbine.instanceUrlSuffix 这个配置,如果没有配置,默认值就是 actuator/hystrix.stream,而当我们给服务配置了 context-path 后,这个 url 肯定取不到信息,所以我们配置了 turbine.instanceUrlSuffix = gateway/actuator/hystrix.stream,这个配置即可解决默认集群中全部配置了 context-path 属性的问题

接着我们看到还有这一步操作:

String key = "turbine.instanceUrlSuffix." + host.getCluster();
			DynamicStringProperty urlClosureConfig = DynamicPropertyFactory.getInstance()
					.getStringProperty(key, null);

这就可以和我们配置的 turbine.instanceUrlSuffix.SPRING-DEMO-SERVICE-FEIGN = gateway/actuator/hystrix.stream 对应上了,它会通过 turbine.instanceUrlSuffix. 加上集群名获取到我们在配置文件里配置的信息

至此,我们就解决了服务配置了 context-path 后,Turbine 监控不到的问题,如果想详细了解其原理,可以查阅相关源码和文档。

 

源码下载:https://github.com/shmilyah/spring-cloud-componets

你可能感兴趣的:(springboot,Spring,Cloud,Spring,Cloud,应用篇,Spring,Cloud,Finchley,Spring,Boot,2.0)