在使用 Hystrix Dashboard 组件监控服务的熔断状况时,每个服务都有一个 Hystrix Dashboard 主页,当服务数量很多时相当不方便。为了同时监控多个服务的熔断器的状态,可以使用 Turbine。Turbine 用于聚合多个 Hystrix Dashboard,将多个 Hystrix Dashboard 组件的数据放在一个页面上展示,进行集中监控。
创建父项目
本例采用 Maven 多模块结构,首先创建一个父项目 spring-cloud-turbine,其 pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wuychn</groupId>
<artifactId>spring-cloud-turbine</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eureka-server</module>
<module>service-provider</module>
<module>service-consumer-hi</module>
<module>service-consumer-hello</module>
<module>turbine-monitor</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring.cloud.version>Finchley.RELEASE</spring.cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
创建服务注册中心
在父项目下新建一个子模块 eureka-server,作为服务注册中心,其 pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-turbine</artifactId>
<groupId>com.wuychn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml:
server:
port: 9001
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
serviceUrl:
defaultZone: http://localhost:9001/eureka/
程序启动类:
package com.wuychn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
创建服务提供者
在父项目下新建一个子模块 service-provider,作为服务提供者,其 pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-turbine</artifactId>
<groupId>com.wuychn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-provider</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml:
server:
port: 9003
spring:
application:
name: service-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9001/eureka/
程序启动类:
package com.wuychn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
在 service-provider 中,有两个 Controller,一个是 HiController,一个是 HelloController,它们都有一个 REST API 接口,供服务消费者消费,HiController 如下:
package com.wuychn.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HiController {
@Value("${server.port}")
private String port;
@GetMapping("/hi")
public String hi(String name) {
return "hi " + name + ", I am from port:" + port;
}
}
HelloController 如下:
package com.wuychn.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(String name) {
return "Hello, " + name;
}
}
创建服务消费者 service-consumer-hi
在父项目中新建一个子模块 service-consumer-hi,作为服务消费者,它会使用 Feign 调用 service-provider 的 /hi 接口。其 pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-turbine</artifactId>
<groupId>com.wuychn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-consumer-hi</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml:
spring:
application:
name: service-consumer-hi
server:
port: 9004
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9001/eureka/
feign:
hystrix:
enabled: true
程序启动类:
package com.wuychn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
@EnableHystrixDashboard
public class ServiceConsumerHiApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerHiApplication.class, args);
}
}
HiController:
package com.wuychn.controller;
import com.wuychn.service.HiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HiController {
@Autowired
private HiService hiService;
@GetMapping("/hi")
public String hi(@RequestParam(required = false, defaultValue = "wuychn") String name) {
return hiService.hi(name);
}
}
HiService:
package com.wuychn.service;
import com.wuychn.client.HiFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class HiService {
@Autowired
private HiFeignClient hiFeignClient;
public String hi(String name) {
return hiFeignClient.hi(name);
}
}
HiFeignClient:
package com.wuychn.client;
import com.wuychn.client.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "service-provider", configuration = FeignConfig.class, fallback = HiFeignFallback.class)
public interface HiFeignClient {
// 如果不添加@RequestParam注解,会被转化为POST请求,从而报405
@GetMapping("/hi")
String hi(@RequestParam(value = "name") String name);
}
@Component
class HiFeignFallback implements HiFeignClient {
@Override
public String hi(String name) {
return "sorry, " + name + ", please wait..";
}
}
FeignConfig:
package com.wuychn.client.config;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static java.util.concurrent.TimeUnit.SECONDS;
@Configuration
public class FeignConfig {
@Bean
public Retryer reignRetryer() {
return new Retryer.Default(100, SECONDS.toMillis(1), 5);
}
}
HystrixDashboardConfig:
package com.wuychn.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HystrixDashboardConfig {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
创建服务消费者 service-consumer-hello
service-consumer-hello 和 service-consumer-hi 类似,只不过它是调用 service-provider 的 /hello 接口。
创建 turbine-monitor
最后,在父项目下新建一个子项目 turbine-monitor,作为 Turbine 聚合监控的工程,其完整的 pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-turbine</artifactId>
<groupId>com.wuychn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>turbine-monitor</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>
application.yml:
server:
port: 9006
spring:
application:
name: turbine-monitor
turbine:
aggregator:
clusterConfig: default
appConfig: service-consumer-hi,service-consumer-hello
clusterNameExpression: new String("default")
instanceUrlSuffix: /hystrix.stream
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9001/eureka/
其中,turbine.aggregatorappConfig 配置了需要监控的服务名,本例中,需要监控的服务有 service-consumer-hi 和 service-consumer-hello。clusterNameExpression 默认为服务名的集群,使用默认就可以。instanceUrlSuffix 和被监控服务中的 registrationBean.addUrlMappings(“/hystrix.stream”) 指定的路径一致。
程序启动类如下:
package com.wuychn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@SpringBootApplication
@EnableTurbine
public class TurbineMonitorApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineMonitorApplication.class, args);
}
}
到这里,代码就写完了,整个项目的结构如图:
依次启动 eureka-server、service-provider、service-consumer-hi、service-consumer-hello 和 turbine-monitor,在浏览器中访问 http://localhost:9004/hystrix,在界面上依次输入监控流的地址 http://localhost:9006/turbine.stream、监控间隔时间 2000 毫秒和 title,单击 Monitor Stream,可以看到如下界面:
至此,使用 Turbine 聚合监控完成。