在微服务结构中,我们将系统拆分成了很多服务单元,应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是服务依赖自身问题出现调用故障或延迟,而这些问题会导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现故障的依赖响应形成任务积压,最终导致自身服务的瘫痪。
举个例子,在一个电商网站中,我们可能将系统拆分成用户、订单、库存、积分、评论等一系列服务单元。用户创建一个订单的时候,客户端将调用订单服务的创建订单接口,此时创建订单接口又会向库存服务来请求出货(判断是否有足够库存出货)。此时若库存服务因为自身处理逻辑等原因造成响应缓慢,会直接导致创建订单服务的线程被挂起,以等待库存申请服务的响应,在漫长的等待之后用户会因为请求失败而得到创建订单失败的结果。如果在高并发情况之下,因这些挂起的线程在等待库存服务响应而未能释放,使得后来创建订单请求被阻塞,最终导致订单服务也不可用。
在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构想教传统架构更加不稳定。为了解决这样的问题,产生了断路器等一系列服务保护机制。
断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用户在电路上保护路线过载,当线路中有电器发生短路时,“断路器”能够及时切断故障电路,防止发生过过载、发热甚至起火等严重后果。
在分布式架构中,断路器模式的作用也是类似,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免的故障在分布式系统中的蔓延。
针对上述问题,Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。它也是基于Netfix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。
Spring Cloud 服务短路
传统 Spring Web MVC
以 web 工程为例
创建 IndexController:
package com.cyy.cloud.web.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
import java.util.concurrent.TimeoutException;
/**
* @Author: Crystal
* @Date: 2018/10/9 12:13
**/
@RestController
public class IndexController {
private final static Random random = new Random();
/**
* 当方法执行超过100ms时候,抛异常
* @return
*/
@GetMapping("")
public String index() throws Exception{
long excuteTime = random.nextInt(200);
if(excuteTime > 100){ //执行时间超过100ms
throw new TimeoutException("Execution is timeout!");
}
return "Hello World";
}
}
异常处理
通过@RestControllerAdvice
实现
package com.cyy.cloud.web.controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.concurrent.TimeoutException;
/**
* 类似于AOP拦截
* @Author: Crystal
* @Date: 2018/10/9 14:23
**/
@RestControllerAdvice(assignableTypes = IndexController.class)
public class IndexControllerAdvice {
@ExceptionHandler(TimeoutException.class)
public Object faultToleranceTimeout(Throwable throwable){
return throwable.getMessage();
}
}
Spring Cloud Netflix Hystrix
增加Maven依赖
<dependencyManagement>
<dependencies>
<!-- Spring Boot 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 其他依赖省略 -->
<!-- 依赖 Spring Cloud Netflix Hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
使用@EnableHystrix
实现服务提供方短路
修改应用 user-service-provider
的引导类:
package com.segumentfault.spring.cloud.lesson8.user.service;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
/**
* 引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since 0.0.1
*/
@SpringBootApplication
@EnableHystrix
public class UserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceProviderApplication.class, args);
}
}
通过@HystrixCommand
实现
增加 getUsers()
方法到 UserServiceProviderController
:
/**
* 获取所有用户列表
*
* @return
*/
@HystrixCommand(
commandProperties = { // Command 配置
// 设置操作时间为 100 毫秒
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")
},
fallbackMethod = "fallbackForGetUsers" // 设置 fallback 方法
)
@GetMapping("/user/list")
public Collection<User> getUsers() throws InterruptedException {
long executeTime = random.nextInt(200);
// 通过休眠来模拟执行时间
System.out.println("Execute Time : " + executeTime + " ms");
Thread.sleep(executeTime);
return userService.findAll();
}
为 getUsers()
添加 fallback 方法:
/**
* {@link #getUsers()} 的 fallback 方法
*
* @return 空集合
*/
public Collection<User> fallbackForGetUsers() {
return Collections.emptyList();
}
使用@EnableCircuitBreaker
实现服务调用方短路
调整 user-ribbon-client
,为UserRibbonController
增加获取用户列表,实际调用user-service-provider
"/user/list" REST 接口
增加具备负载均衡 RestTemplate
在UserRibbonClientApplication
增加 RestTemplate
申明
/**
* 申明 具有负载均衡能力 {@link RestTemplate}
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
实现服务调用
/**
* 调用 user-service-provider "/user/list" REST 接口,并且直接返回内容
* 增加 短路功能
*/
@GetMapping("/user-service-provider/user/list")
public Collection<User> getUsersList() {
return restTemplate.getForObject("http://" + providerServiceName + "/user/list", Collection.class);
}
激活 @EnableCircuitBreaker
调整 UserRibbonClientApplication
:
package com.segumentfault.spring.cloud.lesson8.user.ribbon.client;
import com.netflix.loadbalancer.IRule;
import com.segumentfault.spring.cloud.lesson8.user.ribbon.client.rule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* 引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since 0.0.1
*/
@SpringBootApplication
@RibbonClient("user-service-provider") // 指定目标应用名称
@EnableCircuitBreaker // 使用服务短路
public class UserRibbonClientApplication {
public static void main(String[] args) {
SpringApplication.run(UserRibbonClientApplication.class, args);
}
/**
* 将 {@link MyRule} 暴露成 {@link Bean}
*
* @return {@link MyRule}
*/
@Bean
public IRule myRule() {
return new MyRule();
}
/**
* 申明 具有负载均衡能力 {@link RestTemplate}
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
增加编程方式的短路实现
package com.segumentfault.spring.cloud.lesson8.user.ribbon.client.hystrix;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.springframework.web.client.RestTemplate;
import java.util.Collection;
import java.util.Collections;
/**
* User Ribbon Client HystrixCommand
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since 0.0.1
*/
public class UserRibbonClientHystrixCommand extends HystrixCommand<Collection> {
private final String providerServiceName;
private final RestTemplate restTemplate;
public UserRibbonClientHystrixCommand(String providerServiceName, RestTemplate restTemplate) {
super(HystrixCommandGroupKey.Factory.asKey(
"User-Ribbon-Client"),
100);
this.providerServiceName = providerServiceName;
this.restTemplate = restTemplate;
}
/**
* 主逻辑实现
*
* @return
* @throws Exception
*/
@Override
protected Collection run() throws Exception {
return restTemplate.getForObject("http://" + providerServiceName + "/user/list", Collection.class);
}
/**
* Fallback 实现
*
* @return 空集合
*/
protected Collection getFallback() {
return Collections.emptyList();
}
}
改造 UserRibbonController#getUsersList()
方法
package com.segumentfault.spring.cloud.lesson8.user.ribbon.client.web.controller;
import com.segumentfault.spring.cloud.lesson8.domain.User;
import com.segumentfault.spring.cloud.lesson8.user.ribbon.client.hystrix.UserRibbonClientHystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Collection;
/**
* 用户 Ribbon Controller
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since 0.0.1
*/
@RestController
public class UserRibbonController {
/**
* 负载均衡器客户端
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${provider.service.name}")
private String providerServiceName;
@Autowired
private RestTemplate restTemplate;
private UserRibbonClientHystrixCommand hystrixCommand;
/**
* 调用 user-service-provider "/user/list" REST 接口,并且直接返回内容
* 增加 短路功能
*/
@GetMapping("/user-service-provider/user/list")
public Collection<User> getUsersList() {
return new UserRibbonClientHystrixCommand(providerServiceName, restTemplate).execute();
}
}
为生产而准备
Netflix Hystrix Dashboard
创建 hystrix-dashboard 工程
增加Maven 依赖
<dependencies>
<!-- 依赖 Hystrix Dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
增加引导类
package com.segumentfault.spring.cloud.lesson8.hystrix.dashboard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* Hystrix Dashboard 引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since 0.0.1
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
增加 application.properties
## Hystrix Dashboard 应用
spring.application.name = hystrix-dashboard
## 服务端口
server.port = 10000
启动user-ribbon-client
、user-service-provider
与hystrix-dashboard
三个项目,访问dashbordhttp://localhost:10000/hystrix.stream
这里用的是Single Hystrix App: http://hystrix-app:port/hystrix.stream
输入你想监控的,比如想监控user-service-provider
,在Hystrix Dashboard下输入http://localhost:9090/hystrix.stream
即可,可以看到如下界面:
参考:
- 小马哥讲SpringCloud GitHub地址:https://github.com/mercyblitz/segmentfault-lessons
- 小马哥讲SpringCloud和SpringBoot的视频:https://pan.baidu.com/s/19tPYUTnKoP6yCeR2vtiuAg
视频链接失效,或者需要密码的,可以私信我,个人邮箱:
850949089@qq.com