在 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 聚合监控不到的问题。
拿之前文章里写的服务 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...状态,之前说过这个可能是因为没有请求数据,那么我们就访问一下接口
访问 http://localhost:8382/gateway/hello
接口已访问,为了证明此时已经有 hystrix 的数据,我们访问一下 http://localhost:8382/gateway/actuator/hystrix.stream
可见,已经产生数据了,此时刷新 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
这种情况是被监控的服务都配置了 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-demo-service-feign 的监控,输入:http://localhost:8581/turbine.stream?cluster=SPRING-DEMO-SERVICE-FEIGN,可以看到也成功监控了
源码在 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