一、目前存在的问题
二、雪崩效应
三、解决方案
四、Hystrix
一、目前存在的问题
现在我们假设一下,服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到服务返回。在高负载场景下,如果不做任何处理,这种问题很可能造成所有处理用户请求的线程都被耗竭,而不能响应用户的进一步请求。
二、雪崩效应
在微服务架构中通常会有多个服务层调用,大量的微服务通过网络进行通信,从而支撑起整个系统。各个微服务之间也难免存在大量的依赖关系。然而任何服务都不是100%可用的,网络往往也是脆弱的,所以难免有些请求会失败。基础服务的故障导致级联故障,进而造成了整个系统的不可用,这种现象被称为服务雪崩效应。服务雪崩效应描述的是一种因服务提供者的不可用导致服务消费者的不可用,并将不可用逐渐放大的过程。
A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
三、解决方案
3.1:超时机制
通过网络请求其他服务时,都必须设置超时。正常情况下,一个远程调用一般在几十毫秒内就返回了。当依赖的服务不可用,或者因为网络问题,响应时间将会变得很长(几十秒)。而通常情况下,一次远程调用对应了一个线程/进程,如果响应太慢,那这个线程/进程就会得不到释放。而线程/进程都对应了系统资源,如果大量的线程/进程得不到释放,并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用。所以必须为每个请求设置超时。
3.2:断路器模式
试想一下,家庭里如果没有断路器,电流过载了(例如功率过大、短路等),电路不断开,电路就会升温,甚至是烧断电路、起火。有了断路器之后,当电流过载时,会自动切断电路(跳闸),从而保护了整条电路与家庭的安全。当电流过载的问题被解决后,只要将关闭断路器,电路就又可以工作了。
同样的道理,当依赖的服务有大量超时时,再让新的请求去访问已经没有太大意义,只会无谓的消耗现有资源。譬如我们设置了超时时间为1秒,如果短时间内有大量的请求(譬如50个)在1秒内都得不到响应,就往往意味着异常。此时就没有必要让更多的请求去访问这个依赖了,我们应该使用断路器避免资源浪费。
断路器可以实现快速失败,如果它在一段时间内侦测到许多类似的错误(譬如超时),就会强迫其以后的多个调用快速失败,不再请求所依赖的服务,从而防止应用程序不断地尝试执行可能会失败的操作,这样应用程序可以继续执行而不用等待修正错误,或者浪费CPU时间去等待长时间的超时。断路器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
断路器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。
四、Hystrix
这里有分别启动了2个项目,一个是MICROSERVICE-PROVIDER-USER专门提供给MICROSERVICE-CONSUMER-MOVIE-RIBBON-WITH-HYSTRIX。
1、MICROSERVICE-PROVIDER-USER提供一个服务用来查询用户
import com.netflix.discovery.EurekaClient;
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/simple/{id}")
public User findById(@PathVariable Long id) {
return this.userRepository.findOne(id);
}
}
2、MICROSERVICE-CONSUMER-MOVIE-RIBBON-WITH-HYSTRIX服务代码如下
application.yml
server:
port: 8011
spring:
application:
name: microservice-consumer-movie-ribbon-with-hystrix
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
instance:
hostname: ribbon
RibbonHystrixController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.itmuch.cloud.study.user.entity.User;
import com.itmuch.cloud.study.user.service.RibbonHystrixService;
@RestController
public class RibbonHystrixController {
@Autowired
private RibbonHystrixService ribbonHystrixService;
@GetMapping("/ribbon/{id}")
public User findById(@PathVariable Long id) {
return this.ribbonHystrixService.findById(id);
}
}
异常回调处理方法
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.itmuch.cloud.study.user.entity.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@Service
public class RibbonHystrixService {
@Autowired
private RestTemplate restTemplate;
private static final Logger LOGGER = LoggerFactory.getLogger(RibbonHystrixService.class);
/**
* 使用@HystrixCommand注解指定当该方法发生异常时调用的方法
* @param id id
* @return 通过id查询到的用户
*/
@HystrixCommand(fallbackMethod = "fallback")
public User findById(Long id) {
return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class);
}
/**
* hystrix fallback方法
* @param id id
* @return 默认的用户
*/
public User fallback(Long id) {
RibbonHystrixService.LOGGER.info("异常发生,进入fallback方法,接收的参数:id = {}", id);
User user = new User();
user.setId(-1L);
user.setUsername("default username");
user.setAge(0);
return user;
}
}
启动类MovieRibbonHystrixApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* 使用@EnableCircuitBreaker注解开启断路器功能
* @author eacdy
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MovieRibbonHystrixApplication {
/**
* 实例化RestTemplate,通过@LoadBalanced注解开启均衡负载能力.
* @return restTemplate
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MovieRibbonHystrixApplication.class, args);
}
}
启动Eureka后将2个服务注册到Eureka中
在浏览器中输入http://localhost:8011/ribbon/1,可以得到正常的结果
我们在user服务关闭,再次访问会得到,并且控制台打印如下日志
我们查看http://localhost:8011/hystrix.stream可以看到(
除实现容错外,Hystrix还提供了近乎实时的监控。HystrixCommand和HystrixObservableCommand在执行时,
会生成执行结果和运行指标,比如每秒执行的请求数、成功数等,这些监控数据对分析应用系统的状态很有用。
)
通过http://localhost:8761/health查看eureka的hystrix节点的状态已经打开
通过http://localhost:8011/health查看hystrix的健康状态也发现findById断路器是打开的