目录
2.1.3、主配置类上加入注解@EnableCircuitBreaker
4.1.2、加入注解@EnableCircuitBreaker
4.2.2、加注解@EnableHystrixDashboard
4.3.4、把turbian工程监控端的写入到上面dashboard工程中http://localhost:9001/hystrix
一、分布式系统遇到的问题
在分布式系统中,存在服务A 调用服务B ,而服务B又去调用服务C,服务D,这样的调用过程就是服务扇出。
而在某条扇出的服务调用链路中有一个服务,由于响应时间过程或者抛出异常,导致服务调用者被占用越来越多资源,从而导致整个系统奔溃,整个过程就叫服务雪崩或者级联故障。
1.1、解决方案(应用容错):
1.1.1、超时机制:
超时机制,配置一下超时时间,例如1秒——每次请求在1秒内必须返回,否则到点就把线程掐死,释放资源!
思路:一旦超时,就释放资源。由于释放资源速度较快,应用就不会那么容易被拖死。
1.1.1.1、为RestTemplate设置连接以及读取超时时间:
没用任何springcloud组件,直接通过RestTemplate调用远程服务时设置超时:
@Configuration
public class MainConfig {
@Bean
public RestTemplate restTemplate() {
//设置restTemplate的超时时间
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(2000);
requestFactory.setConnectTimeout(2000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
}
1.1.1.2、调用异常捕获
@RequestMapping("/queryUserInfoById/{userId}")
public UserInfoVo queryUserInfoById(@PathVariable("userId") Integer userId) {
User user = userServiceImpl.queryUserById(userId);
ResponseEntity<List> responseEntity = null;
try {
responseEntity = restTemplate.getForEntity("http://localhost:8002/order/queryOrdersByUserId/"+userId,List.class);
} catch (Exception e) {
throw new TulingTimeoutException(0,"调用超时");
}
List<OrderVo> orderVoList = responseEntity.getBody();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(orderVoList);
userInfoVo.setUserName(user.getUserName());
return userInfoVo;
}
1.1.1.3、全局异常处理
简单的统一异常处理:
@ControllerAdvice//加该注解
public class TulingExcpetionHandler {
@ExceptionHandler(value = TulingTimeoutException.class)//该注解指定处理什么异常
@ResponseBody
public Object dealException() {
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setUserName("容错用户");
userInfoVo.setOrderVoList(null);
return userInfoVo;
}
}
1.1.2、舱壁隔离
有兴趣的可以先了解一下船舱构造——一般来说,现代的轮船都会分很多舱室,舱室之间用钢板焊死,彼此隔离。这样即使有某个/某些船舱进水,也不会影响其他舱室,浮力够,船不会沉。
软件工程里的仓壁模式可以这样理解:M类使用线程池1,N类使用线程池2,彼此的线程池不同,各自独立。并且为每个类分配的线程池大小,例如coreSize=10。举个例子:M类调用B服务,N类调用C服务,如果M类和N类使用相同的线程池,那么如果B服务挂了,M类调用B服务的接口并发又很高,你又没有任何保护措施,你的服务就很可能被M类拖死。而如果M类有自己的线程池,N类也有自己的线程池,如果B服务挂了,M类顶多是将自己的线程池占满,不会影响N类的线程池——于是N类依然能正常工作,
思路:不把鸡蛋放在一个篮子里。你有你的线程池,我有我的线程池,你的线程池满了和我没关系,你挂了也和我没关系。
1.1.3、熔断器
现实世界的断路器大家肯定都很了解,每个人家里都会有断路器。断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。
跳闸一段时间后(例如15秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。
(降级方法中做)可预期的结果给你。
熔断是结果(异常、内存、超时的结果)
降级来解决(不让你的程序去调用你的目标方法,转为调用本地设置可预期的方法。)
发生熔断去触发降级,比方跳闸了,就不让某些去调用该服务,而直接调用本地client中的默认返回。
二、Hystrix(豪猪)
Hystrix(豪猪)是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
Hystrix可用来做熔断、降级、限流。
2.1、Hystrix简单使用:
2.1.1、创建工程tulingvip03-ms-consumer-hystrix-command
2.1.2、导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.1.3、主配置类上加入注解@EnableCircuitBreaker
@EnableCircuitBreaker
@SpringBootApplication
public class Tulingvip03MsConsumerHystrixCommandApplication{}
2.1.4、通过hystrix命令模式 来进行调用
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.tuling.entity.OrderVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class TulingHystrixCommand extends HystrixCommand<List<OrderVo>> {
private Integer userId;
private RestTemplate restTemplate;
@Override
protected List<OrderVo> run() throws Exception {
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://MS-PROVIDER-ORDER/order/queryOrdersByUserId/"+userId, List.class);
List<OrderVo> orderVoList = responseEntity.getBody();
log.info("查询出的orderVoList:{}",orderVoList);
return orderVoList;
}
/**
* 降级方法(回退)
* @return
*/
@Override
protected List<OrderVo> getFallback() {
log.info("触发降级方法========================>");
OrderVo orderVo = new OrderVo();
orderVo.setOrderId(-1);
orderVo.setUserId(-1);
orderVo.setOrderMoney(new BigDecimal(0));
List<OrderVo> orderVos = new ArrayList<>();
orderVos.add(orderVo);
return orderVos;
}
//构造方法
public TulingHystrixCommand(String commandGroupKey, RestTemplate restTemplate, Integer userId) {
super(HystrixCommandGroupKey.Factory.asKey(commandGroupKey));
this.restTemplate = restTemplate;
this.userId = userId;
}
}
调用:
/**
* 通过hystrix命令模式 来进行调用
* @param userId
*/
@RequestMapping("/queryUserInfo/{userId}")
public UserInfoVo queryUserInfo(@PathVariable("userId") Integer userId) {
User user = userServiceImpl.queryUserById(userId);
//TODO 构建调用命令
TulingHystrixCommand tulingHystrixCommand = new TulingHystrixCommand("orderGroupKey",restTemplate,userId);
List<OrderVo> orderVoList =tulingHystrixCommand.execute();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(orderVoList);
userInfoVo.setUserName(user.getUserName());
return userInfoVo;
}
2.1.5、通过注解来指定降级方法
/**
* 通过注解来指定降级方法
* :@HystrixCommand加了该注解,里面的回调方法必须写在本类里,否则调不到。
* 注解里不写方法的话会有一个默认的方法去调。
*/
@HystrixCommand(fallbackMethod ="queryUserInfoFallBack")
@RequestMapping("/queryUserInfo/{userId}")
public UserInfoVo queryUserInfo(@PathVariable("userId") Integer userId) {
log.info("进入queryUserInfo..............");
//User user = userServiceImpl.queryUserById(userId);
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://MS-PROVIDER-ORDER/order/queryOrdersByUserId/"+userId, List.class);
List<OrderVo> orderVoList = responseEntity.getBody();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(orderVoList);
userInfoVo.setUserName("张三");
return userInfoVo;
}
protected UserInfoVo queryUserInfoFallBack(Integer userId) {
log.info("触发降级方法=根据用户ID{}查询订单服务异常:{}",userId);
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(null);
userInfoVo.setUserName("-1");
return userInfoVo;
}
降级处理不是每一个方法都要写,只有那些会高频调用的才需要,普通很少用到的方法不需要特殊处理。
2.2、Hystrix的跳闸机制:
2.2.1、Hystrix的默认配置跳闸父阈值
hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window(时间窗口)内最小的请
求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,
即使19个请求都失败,也不会触发circuit break。达到20个请求失败就会触发熔断。默认20。
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设
为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit(并不是完全关闭,而是半开,释放一次请求,成功了关闭,失败了继续打开。参考下面的跳闸机制三转换图)。默认5000
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的(即上面第一个里的时间窗口值),毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,
则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数
的统计信息。默认10000
2.2.1.1、宕机跳闸
启动服务注册中心以及服务消费者(不启动服务提供者模拟宕机)
测试地址:http://localhost:8001/user/queryUserInfo/1
结果:{"userName": "-1","orderVoList": null}
2.2.1.2、超时跳闸
使用Ribbon+hystrix来进行调用 需要调用超时时间。(设置全局的超时时间)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
#指定设置接口的超时设置
hystrix.command.queryUserInfoByName.execution.isolation.thread.timeoutInMilliseconds=10
@HystrixCommand(fallbackMethod ="defautlUserInfoFallBack",commandKey = "queryUserInfoByName")
@RequestMapping("/queryUserInfoByName/{userName}")
public UserInfoVo queryUserInfoByName(@PathVariable("userName") String userName) {
log.info("queryUserInfoByName..............");
User user = userServiceImpl.queryUserByName(userName);
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://MS-PROVIDER-ORDER/order/queryOrdersByUserId/"+user.getUserId(), List.class);
List<OrderVo> orderVoList = responseEntity.getBody();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(orderVoList);
userInfoVo.setUserName(user.getUserName());
return userInfoVo;
}
protected UserInfoVo defautlUserInfoFallBack(String userName) {
log.info("触发降级方法=根据用户ID{}查询订单服务异常:{}",userName);
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(null);
userInfoVo.setUserName("-1");
return userInfoVo;
}
在服务提供方的被调用方法中设置线程睡眠时间
@RequestMapping("/queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@PathVariable("userId") Integer userId) throws InterruptedException {
log.info("测试降级。。。。。。。");
//超时降级
Thread.sleep(7000);
List<OrderVo> list = new ArrayList<>();
OrderVo orderVo = new OrderVo();
orderVo.setUserId(1);
orderVo.setOrderId(1);
orderVo.setOrderMoney(new BigDecimal(200));
orderVo.setDbSource("tuling_source01");
list.add(orderVo);
return list;
//return orderServiceImpl.queryOrdersByUserId(userId);
}
测试地址:http://localhost:8001/user/queryUserInfo/1
结果:{"userName": "-1","orderVoList": null}
2.2.1.3、异常跳闸
查询一个不存在的订单模拟抛出异常
@RequestMapping("/queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@PathVariable("userId") Integer userId) throws InterruptedException {
log.info("测试降级。。。。。。。");
//超时降级
//Thread.sleep(7000);
if(userId == 2) {
throw new RuntimeException("不存在的用户");
}
List<OrderVo> list = new ArrayList<>();
OrderVo orderVo = new OrderVo();
orderVo.setUserId(1);
orderVo.setOrderId(1);
orderVo.setOrderMoney(new BigDecimal(200));
orderVo.setDbSource("tuling_source01");
list.add(orderVo);
return list;
//return orderServiceImpl.queryOrdersByUserId(userId);
}
测试地址:http://localhost:8001/user/queryUserInfo/2
结果:{"userName": "-1","orderVoList": null}
2.2.2、测试熔断打开以及半开
时间窗口,就是计算出错率的一个时间周期,以当前错误调用的时间往前一段时间内,这段相对时间内的出错率,而不是全部平均稀释。
以异常跳闸为例,在时间窗口内,只要连续失败requestVolumeThreshold的值,再来看hystrix的状态已经调用的现象:
2.2.2.1、查看监控端点hystrix的值
2.2.2.2、在时间窗口内,连续点击失败次数,当此时达到设置的requestVolumeThreshold的阈值,那么就直接进入降级方法,
此时再来看hystrix的监控信息:
2.2.2.3、等到熔断器半开后,测试一个正确的查询,那么熔断器就会关闭,恢复正常调用
2.2.3、跳闸机制三转换图
2.3、Hystrix资源隔离:
2.3.1、线程隔离
核心线程数4个,最大10个。初始化4个,满了来一个加一个,最大10个。不同的不同的线程池
一个方法默认对应一个独立的线程池,
public @interface HystrixCommand:
groupKey:default => the runtime class name of annotated method
commandKey:default => the name of annotated method.
由@HystrixCommand注解属性及源码可知,默认是一个类中一个方法对应一个独立的线程池。但如果同一个类中的方法注解里commandKey的值设置成同一个,则会共用一个线程池。
@HystrixCommand:
2.3.2、信号量隔离
调用次数够了,降级。信号量见并发编程相关的笔记。
三、Feign整合hystrix
3.1、常用配置
3.1.1、开启Fegin对hystrix的支持
# 说明:请务必注意,从Spring Cloud Dalston开始,Feign默认是不开启Hystrix的。
# 因此,如使用Dalston请务必额外设置属性:feign.hystrix.enabled=true,否则断路器不会生效。
# 而,Spring Cloud Angel/Brixton/Camden中,Feign默认都是开启Hystrix的。无需设置该属性。
#开启feign支持hystrix 默认是关闭的(高版本默认关闭,低版本默认开启)
feign.hystrix.enabled=true
3.1.2、超时重试机制
3.1.2.1、开启超时配置:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
默认值是1S,若使用的是Feign+Hystrix的配置的话 ,还需要配置ribbon的超时时间
#设置全局的超时时间(若ribbon+hystrix 不需要设置该超时,只要直接设置timeoutInMilliseconds就可以了)
ribbon.ReadTimeout=2000
ribbon.ConnectTimeout=2000
3.1.2.2、Hystrix调用的超时重试:
加依赖:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
加入配置:
#设置全局的超时时间(若ribbon+hystrix 不需要设置该超时,只要直接设置timeoutInMilliseconds就可以了)
ribbon.ReadTimeout=2000
ribbon.ConnectTimeout=2000
#出现异常的时候 ,对当前实例进行重试的次数
ribbon.MaxAutoRetries=1
#切换实例重试的次数
ribbon.MaxAutoRetriesNextServer=1
#对有所的的操作进行重试(all是对所有方法进行重试,包括get\post\put\delete。true对所有的重试,false对get重试)
ribbon.OkToRetryOnAllOperations=true
如果设置为true,必须对服务接口都进行幂等性处理。
幂等性,指同一份入参,调用一个接口,调用一次和调用N次,结果是一致的。而规范的get请求是天然的幂等性。(所以查询接口可以规范必须使用get请求。)
做服务幂等性的方案:1、加分布式锁。2、在数据库中使用唯一索引。
timeoutInMilliseconds的配置要大于 一个接口的最大调用时间:第一个1是本次调用,第二个1是上面配置的ribbon.MaxAutoRetries本实例重试次数,第三个1是上面配置的ribbon.MaxAutoRetriesNextServer切换实例重试次数,乘以每次调用的超时时间ribbon.ReadTimeout。即需要大于该时间才去进行熔断,不能还没调用完就进行熔断!
#开启重试机制(默认是开启的)
spring.cloud.loadbalancer.retry.enabled=true
#局部配置
微服务实例名称.ribbon.ConnectTimeout=250
微服务实例名称.ribbon.ReadTimeout=1000
微服务实例名称.ribbon.OkToRetryOnAllOperations=true
微服务实例名称.ribbon.MaxAutoRetriesNextServer=2
微服务实例名称.ribbon.MaxAutoRetries=1
3.2、关闭Hystrix对feign的支持
3.2.1、全局关闭
#开启feign支持hystrix 默认是关闭的
feign.hystrix.enabled=false
3.2.2、局部关闭
3.2.2.1、写一个配置类,在配置类中写一个FeignBulider的类
public class MsCustomeFeignApiWithoutHystrixConfg {
@Scope("prototype")
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
3.2.2.2、在FeignClient中指定配置文件
注意“configuration = MsCustomeFeignApiWithoutHystrixConfg.class”
@FeignClient(name = "MS-PROVIDER-ORDER",configuration = MsCustomeFeignApiWithoutHystrixConfg.class,fallbackFactory =MsCustomFeignOrderApiFallBackFactory.class ,path = "/order")
public interface MsCustomFeignOrderApi {
@RequestMapping("/queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@PathVariable("userId") Integer userId);
}
3.2.2.3、测试结果:
1)、不指定config,使用的全局配置,那么由于超时会出现服务降级。
2)、指定config,由于自己配置FeignBuilder,导致不支持服务降级,过三秒后会出现正常结果。
3.3、关闭熔断
3.3.1、全局关闭
# 全局关闭熔断:(关闭之后就不走默认的降级方法,直接返回报错。)
hystrix.command.default.circuitBreaker.enabled=false
3.3.2、局部关闭
# 局部关闭熔断:(生产上全局开启,局部关闭。反过来全局关闭局部开启不生效。只能全局开启局部关闭。)
hystrix.command.<HystrixCommandKey>.circuitBreaker.enabled: false
其中的<HystrixCommandKey> ,是个变量,可以打开服务的hystrix.stream 端点即可看到,也可在Hystrix Dashboard中查看。
一般是接口所在类名(client接口类)#方法名(入参类型),如:hystrix.command.MsCustomFeignOrderApi#queryAll().circuitBreaker.enabled=false
查看路径:
3.4、设置超时
3.4.1、全局设置
# 全局设置超时:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000
3.4.2、局部设置
# 局部设置超时:
hystrix.command.<HystrixCommandKey>.execution.isolation.thread.timeoutInMilliseconds: 1000
3.5、关闭超时
3.5.1、全局关闭
# 全局关闭:
hystrix.command.default.execution.timeout.enabled: false
3.5.2、局部关闭
# 局部关闭:
hystrix.command.<HystrixCommandKey>.execution.timeout.enabled: false
四、hystrix整合监控端点
4.1、访问hystrix.stream的监控端点
4.1.1、引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4.1.2、加入注解@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class ConsumerOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerOrderApplication.class, args);
}
}
4.1.3、加入监控端点配置:
(spring cloud的E版本是不需要加入的 F版本需要加入)
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1); //系统启动时加载顺序
registrationBean.addUrlMappings("/hystrix.stream");//路径。F版本之前不需要加,原来有。也可"/actuator/hystrix.stream",设置查看时访问路径而已。
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
registrationBean.setLoadOnStartup(1); //系统启动时加载顺序
registrationBean.addUrlMappings("/hystrix.stream");//路径。F版本之前不需要加,原来有。也可"/actuator/hystrix.stream",设置查看时访问路径而已。
registrationBean.setName("HystrixMetricsStreamServlet");
4.1.4、查看
访问端点:http://localhost:8001/hystrix.stream
缺点:页面上都是一些json报文,很难使用
4.2、Hystrix+dashboard监控
创建dashBoard工程tulingvip04-ms-dashboard
4.2.1、加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
4.2.2、加注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class Tulingvip04MsDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(Tulingvip04MsDashboardApplication.class, args);
}
}
4.2.3、查看
访问端点路径:http://localhost:9001/hystrix
需要监控的hystrix.stream路径指上面那个显示一堆json数据的路径:http://localhost:8001/hystrix.stream
或者下面集成了turbine组件的工程路径:http://localhost:9002/turbine.stream
4.2.4.1、怎么看监控图:
七色,一线,一圈:
- 七种颜色
- 一个请求实时圈
- 一条线
4.3、集群监控turbian
上面那样不能同时监控多个,且不能看历史数据,不方便。turbian组件可以。turbian将多个服务聚合在一个turbian工程中,然后通过这个工程的路径去查看被监控的多个服务。
创建工程tulingvip-ms-turbine,见tulingvip-cloud
4.3.1、加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
4.3.2、编写配置文件
#最关键的配置项,spring.application.name的值。
turbine:
aggregator:
clusterConfig: default
appConfig: ms-consumer-ribbon-user,ms-consumer-feign-user #最关键的配置项,spring.application.name的值。
cluster-name-expression: "'default'"
instanceUrlSuffix:
ms-consumer-ribbon-user: 你的context-path/actuator/hystrix.strea
4.3.3、加入注解@EnableTurbine
@SpringBootApplication
@EnableTurbine
public class TulingvipMsTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TulingvipMsTurbineApplication.class, args);
}
}