服務容錯保護SpringCloud Hystrix
文章目錄
- 服務容錯保護SpringCloud Hystrix
-
- 一、快速入門
- 二、請求合并
- 三、Hystrix 儀表盤
- 四、Turbine 叢集監控
一、快速入門
eureka 9001
userservice 9003
userservice 9006
ribbonconsumer 9005
在 ribbonconsumer 中加入 pom 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
在 ribbonconsumer 工程的主類 SpringcloudRibbonConsumerApplication 中使用 @EnableCircuitBreaker 注解開啟斷路器功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class SpringcloudRibbonConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringcloudRibbonConsumerApplication.class, args);
}
}
還可以使用 SpringCloud 應用中的 @SpringCloudApplication 注解來修飾應用主類,該注解中包含了上述所引用的三個注解
@SpringCloudApplication
public class SpringcloudRibbonConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringcloudRibbonConsumerApplication.class, args);
}
}
改造服務消費方式,在 helloService 函數上增加 @HystrixCommand 注解來指定回調方法
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "helloFallback")
public String helloService(){
long start = System.currentTimeMillis();
String result = restTemplate.exchange("http://SERIVCE-USER/hello", HttpMethod.GET, null, String.class).getBody();
System.out.println("spend time :" + (System.currentTimeMillis() - start));
return result;
}
public String helloFallback(){
return "error";
}
}
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@Autowired
HelloService helloService;
@GetMapping(value = "ribbon-consumer")
public Object helloConsumer(){
return helloService.helloService();
//return restTemplate.exchange("http://SERIVCE-USER/hello", HttpMethod.GET, null, String.class).getBody();
//return restTemplate.getForEntity("http://SERIVCE-USER/hello",String.class).getBody();
}
}
修改 userservice 9003
@RestController
@Slf4j
public class HelloController {
@GetMapping(value = "/hello")
public String hello() throws InterruptedException {
int sleepTime = new Random().nextInt(3000);
log.info("sleepTime:"+sleepTime);
Thread.sleep(sleepTime);
return "Hello World 9003";
}
}
重新開機 userservice 9003 和 ribbonconsumer 9005 後,連續通路 http://localhost:9005/ribbon-consumer 幾次,當 userservice 9003 Spend time 大于 2000 的時候,就會傳回 error,即服務消費者因調用的服務潮濕進而觸發熔斷請求,并調用回調邏輯傳回結果。
Hystrix 預設逾時時間為 2000 毫秒
二、請求合并
Spring Cloud中Hystrix的請求合并
服務提供者 SERIVCE-USER 9003 修改
@GetMapping(value = "/getUserInfo/{id}")
public User getUserInfo(@PathVariable String id){
return new User("張三","123");
}
@GetMapping(value = "/getUserInfos/{ids}")
public List<User> getUserInfos(@PathVariable String ids){
System.out.println("getUserInfos---------"+ids+"Thread.currentThread().getName():" + Thread.currentThread().getName());
List<User> list = new ArrayList<>();
list.add(new User("張三","123"));
list.add(new User("李四","1234"));
list.add(new User("王五","12345"));
return list;
}
服務消費者
RIBBON-CONSUMER 9005
UserService
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;
public String getUserInfo(Long id) {
return restTemplate.exchange("http://SERIVCE-USER/getUserInfo/1", HttpMethod.GET, null, String.class).getBody();
}
public String getUserInfos(List<Long> ids) {
String usr = "http://SERIVCE-USER/getUserInfos/"+ids.toString();
String result = restTemplate.exchange(usr, HttpMethod.GET, null, String.class).getBody();
return result;
}
}
UserBatchCommand 批處理指令,繼承自HystrixCommand,用來處理合并之後的請求
public class UserBatchCommand extends HystrixCommand<List<User>> {
private List<Long> ids;
@Autowired
UserService userService;
public UserBatchCommand(List<Long> ids, UserService userService) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CollapsingGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CollapsingKey")));
this.ids = ids;
this.userService = userService;
}
@Override
protected List<User> run(){
String result = userService.getUserInfos(ids);
List<User> users = JSONObject.parseArray(result,User.class);
return users;
}
}
UserCollapseCommand 繼承自HystrixCollapser來實作請求合并
public class UserCollapseCommand extends HystrixCollapser<List<User>, User, Long> {
private UserService userService;
private Long id;
public UserCollapseCommand(UserService userService, Long id) {
super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("userCollapseCommand")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100)));
this.userService = userService;
this.id = id;
}
@Override
public Long getRequestArgument() {
return id;
}
@Override
protected HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Long>> collapsedRequests) {
List<Long> ids = new ArrayList<>(collapsedRequests.size());
ids.addAll(collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));
UserBatchCommand userBatchCommand = new UserBatchCommand(ids, userService);
return userBatchCommand;
}
@Override
protected void mapResponseToRequests(List<User> batchResponse, Collection<CollapsedRequest<User, Long>> collapsedRequests) {
System.out.println("mapResponseToRequests");
int count = 0;
for (CollapsedRequest<User, Long> collapsedRequest : collapsedRequests) {
User user = batchResponse.get(count++);
collapsedRequest.setResponse(user);
}
}
}
- 1.首先在構造方法中,我們設定了請求時間窗為100ms,即請求時間間隔在100ms之内的請求會被合并為一個請求。
- 2.createCommand方法主要用來合并請求,在這裡擷取到各個單個請求的id,将這些單個的id放到一個集合中,然後再建立出一個BookBatchCommand對象,用該對象去發起一個批量請求。
- 3.mapResponseToRequests方法主要用來為每個請求設定請求結果。該方法的第一個參數batchResponse表示批處理請求的結果,第二個參數collapsedRequests則代表了每一個被合并的請求,然後我們通過周遊batchResponse來為collapsedRequests設定請求結果。
測試
@RequestMapping("/test7")
public void getUserBatchTest() throws ExecutionException, InterruptedException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
UserCollapseCommand bc1 = new UserCollapseCommand(userService, 1l);
UserCollapseCommand bc2 = new UserCollapseCommand(userService, 2l);
UserCollapseCommand bc3 = new UserCollapseCommand(userService, 3l);
UserCollapseCommand bc4 = new UserCollapseCommand(userService, 4l);
Future<User> q1 = bc1.queue();
Future<User> q2 = bc2.queue();
Future<User> q3 = bc3.queue();
User user1 = q1.get();
User user2 = q2.get();
User user3 = q3.get();
Thread.sleep(3000);
Future<User> q4 = bc4.queue();
User user4 = q4.get();
System.out.println("user1>>>"+user1);
System.out.println("user2>>>"+user2);
System.out.println("user3>>>"+user3);
System.out.println("user4>>>"+user4);
context.close();
}
- 1.首先要初始化 HystrixRequestContext
- 2.建立 UserCollapseCommand 類的執行個體來發起請求,先發送3個請求,然後睡眠3秒鐘,再發起1個請求,這樣,前3個請求就會被合并為一個請求,第四個請求因為間隔的時間比較久,是以不會被合并,而是單獨建立一個線程去處理。
控制台輸出
getUserInfos---------[1, 2, 3]Thread.currentThread().getName():http-nio-9003-exec-2
getUserInfos---------[4]Thread.currentThread().getName():http-nio-9003-exec-3
通過注解實作請求合并
在UserService中添加兩個方法,如下:
@HystrixCollapser(batchMethod = "test11",collapserProperties = {@HystrixProperty(name ="timerDelayInMilliseconds",value = "100")})
public Future<User> test10(Long id) {
return null;
}
@HystrixCommand
public List<User> test11(List<Long> ids) {
String usr = "http://SERIVCE-USER/getUserInfos/"+ids.toString();
String result = restTemplate.exchange(usr, HttpMethod.GET, null, String.class).getBody();
List<User> users = JSONObject.parseArray(result,User.class);
return users;
}
在test10方法上添加@HystrixCollapser注解實作請求合并,用batchMethod屬性指明請求合并後的處理方法,collapserProperties屬性指定其他屬性。
調用
@RequestMapping("/test8")
@ResponseBody
public void test8() throws ExecutionException, InterruptedException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
Future<User> f1 = userService.test10(1l);
Future<User> f2 = userService.test10(2l);
Future<User> f3 = userService.test10(3l);
User b1 = f1.get();
User b2 = f2.get();
User b3 = f3.get();
Thread.sleep(3000);
Future<User> f4 = userService.test10(4l);
User b4 = f4.get();
System.out.println("b1>>>"+b1);
System.out.println("b2>>>"+b2);
System.out.println("b3>>>"+b3);
System.out.println("b4>>>"+b4);
context.close();
}
執行結果同之前一樣
getUserInfos---------[1, 3, 2]Thread.currentThread().getName():http-nio-9003-exec-1
getUserInfos---------[4]Thread.currentThread().getName():http-nio-9003-exec-2
三、Hystrix 儀表盤
解決Hystrix Dashboard 一直是Loading …的情況
Hystrix儀表盤監控單執行個體節點
spring cloud2 hystrix沒有hystrix.stream路徑解決方案
建構 hystrix-dashboard 9007
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.zk.springcloud</groupId>
<artifactId>springcloud-hystrix-dashboard</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud-hystrix-dashboard</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</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.SR1</spring-cloud.version>
</properties>
<dependencies>
<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</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
為應用主類加上 @EnableHystrixDashboard , 啟用 Hystrix Dashboard 功能
spring.application.name=hystrix-dashboard
server.port=9007
啟動該應用,通路 http://localhost:9007/hystrix/ 可以看到 Hystrix Dashboard
在 ribbon-consumer 9005 的 dependencies 節點新增 spring-boot-starter-actuator 監控子產品以開啟監控相關的端點,并確定已經引入斷路器的依賴
spring-cloud-starter-hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在 ribbon-consumer 9005 確定在服務執行個體的主類中已經使用 @EnableCircuitBreaker 注解,開啟了斷路器功能,在啟動類中加入如下代碼
@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;
}
這樣就可以直接使用 http://localhost:9005/hystrix.stream 檢視監控資訊
重新開機 ribbon-consumer ,在 Hystrix Dashboard 的首頁輸入 http://localhost:9005/hystrix.stream 就是用于 Hystrix Dashboard 來占線監控資訊的接口
四、Turbine 叢集監控
Spring Cloud:Turbine監控資料聚合【Finchley 版】Could not initiate connection to host, giving up: [xxx]
建立 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.zk.springcloud.turbie</groupId>
<artifactId>springcloud-turbie</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud-turbie</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</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.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
<version>2.0.1.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在 application.properties 中加入 Eureka 和 Turbine 的相關配置
spring.application.name=turbie
server.port=9008
management.server.port=9009
eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9004/eureka/
turbine.app-config=RIBBON-CONSUMER
turbine.cluster-name-expression="default"
turbine.combine-host-port=true
turbine.instanceUrlSuffix.default = /hystrix.stream
通路 http://localhost:9008/hystrix/ 開啟對 http://localhost:9008/turbine.stream 的監控