1、 分布式系统面对的问题
在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务。 如果其中的某一个服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成服务雪崩。
什么是服务雪崩效应?因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应。导致服务不可用的原因有很多,比如程序Bug、大流量请求、硬件故障、缓存击穿等。在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致进一步加大请求流量。所以归根结底导致雪崩效应的最根本原因是大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时, 会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应就产生了。
2、解决方案
超时机制
服务限流(资源隔离)
服务熔断
服务降级
2.1 超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。
RestTemplate设置超时时间
@Configuration
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
//设置restTemplate的超时时间
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectionRequestTimeout(2000);
httpRequestFactory.setConnectTimeout(2000);
httpRequestFactory.setReadTimeout(2000);
return new RestTemplate(httpRequestFactory);
return new RestTemplate();
}
}
全局异常设置
@ControllerAdvice
public class MyExcpetionHandler {
@ExceptionHandler(value = MyTimeoutException.class)
@ResponseBody
public Object handleException() {
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setUsername("超时异常");
userInfoVo.setOrderList(null);
return userInfoVo;
}
}
@Data
@AllArgsConstructor
public class MyTimeoutException extends RuntimeException {
private Integer code;
private String msg;
}
调用时捕获异常
@RequestMapping(value = "/getById/{id}")
public UserInfoVo getById(@PathVariable("id") Integer id) {
User user = userService.getById(id);
String url = "http://service-order/order/findOrderByUserId/" + id;
ResponseEntity<List> responseEntity = null;
try {
responseEntity = restTemplate.getForEntity(url, List.class);
} catch (RestClientException e) {
throw new MyTimeoutException(-1, "调用超时");
}
List<Order> orderList = responseEntity.getBody();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderList(orderList);
userInfoVo.setUsername(user.getUsername());
return userInfoVo;
}
2.2 服务限流(资源隔离)
限制请求核心服务提供者的流量,使大流量拦截在核心服务之外,这样可以更好的保证核心服务提供者不出问题,对于一些出问题的服务可以限制流量访问,只分配固定线程资源访问,这样能使整体的资源不至于被出问题的服务耗尽,进而整个系统雪崩。那么服务之间怎么限流,怎么资源隔离?例如可以通过线程池+队列的方式,通过信号量的方式进行处理。
2.3 服务熔断
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。
我们可以实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状态,这是一个瞬间态, 此时允许一次请求调用该服务的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试。通过”跳闸“, 应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。
2.4 服务降级
有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值,例如:备用接口/缓存/mock数据 。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。
3、Hystrix 使用
3.1 集成、使用
Hystrix(豪猪)是由Netflix开源的一个延迟和容错库,提供超时机制、限流、熔断、降级最全面的实现,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统 的可用性与容错性。
引入依赖
<dependency>
<groupId>o