SpringCloud 学习笔记4


Hystrix

简介

Hystix,即熔断器。主页:https://github.com/Netflix/Hystrix/ , 已经停更两年多了。

20200819141026

Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

20200819141119

熔断器的工作机制

20200819141728

正常工作的情况下,客户端请求调用服务API接口:

20200819141817

当有服务出现异常时,直接进行失败回滚,服务降级处理:

20200819141853

当服务繁忙时,如果服务出现异常,不是粗暴的直接报错,而是返回一个友好的提示,虽然拒绝了用户的访问,但是会返回一个结果。

这就好比去买鱼,平常超市买鱼会额外赠送杀鱼的服务。等到逢年过节,超时繁忙时,可能就不提供杀鱼服务了,这就是服务的降级。

系统特别繁忙时,一些次要服务暂时中断,优先保证主要服务的畅通,一切资源优先让给主要服务来使用,在双十一、618时,京东天猫都会采用这样的策略。


动手实践

改造服务消费者

引入依赖

首先在服务消费者 ConsumerDemo 中引入Hystix依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

开启熔断

ConsumerDemo 的启动类上面,添加 @EnableCircuitBreaker 注释,开启熔断:

@EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

也可以使用 @SpringCloudApplication 注解,来代替 @EnableCircuitBreaker@SpringBootApplication@EnableDiscoveryClient 注解,下面是 `` 注解的源码:

20200820101603

编写降级逻辑

当目标服务的调用出现故障,我们需要快速解决,给用户一个友好提示。因此需要提前编写好,失败时的降级处理逻辑,要使用 @HystixCommond 注解来完成。

改造 ConsumerController ,在用来访问的 User 服务的方法中声明一个失败时的回滚处理函数:

@RestController
@RequestMapping(value = "consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    private static final Logger logger = LoggerFactory.getLogger(ConsumerController.class);
    
    @HystrixCommand(fallbackMethod = "queryByIdFallback")
    @GetMapping("{id}")
    public String queryById(@PathVariable(value = "id") Long id) {
        String url = "http://user-service/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        assert user != null;
        return user.toString();
    }

    public String queryByIdFallback(Long id) {
        logger.error("查询用户信息失败,id: {}", id);
        return "不好意思,服务器正忙!";
    }
}

注意:因为熔断的降级逻辑方法必须和正常逻辑方法的有相同的参数列表和返回值声明。如这里的熔断降级逻辑方法和政策逻辑方法的参数列表都只有一个 Long 类型的数据,返回值都是 String 类型的数据。


说明:

  • @HystrixCommand :该注释用于指定一些方法,这些方法作为 hystrix 命令进行处理。
  • fallbackMethod :该成员变量用于指定一个降级逻辑的方法。
  • 超时时间默认是1000毫秒。

改造服务提供者

改造服务提供者,随机休眠一段时间,以触发熔断:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id) {
        // 为了演示超时现象,我们在这里然线程休眠,时间随机 0~2000毫秒
        try {
            Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userMapper.selectByPrimaryKey(id);
    }
}

启动测试

启动 EurekaServer(注册服务)、UserService、ConsumerDemo,通过服务消费者 ConsumerDemo 调用服务提供者 UserService,UserService 提供的服务随机休眠0-2000毫秒:

20200820113136

调用服务:

20200820112536

查询失败:

20200820113509

查询成功:

20200820113400

优化

虽然熔断实现了,但是我们的重试机制似乎没有生效,是这样吗?

其实这里是因为我们的Ribbon超时时间设置的是1000ms:

20200820132357


而Hystix的超时时间默认也是1000ms,因此重试机制没有被触发,而是先触发了熔断。

所以,Ribbon的超时时间一定要小于Hystix的超时时间。

我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。

配置文件中设置:

hystrix:
  command:
  	default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 设置hystrix的超时时间为6000ms

方法中的设置:

@HystrixCommand(commandProperties = 
    @HystrixProperty(
                name = "hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 
                value = "6000")
)
@GetMapping("{id}")
public String queryById(@PathVariable(value = "id") Long id) {
    String url = "http://user-service/user/" + id;
    User user = restTemplate.getForObject(url, User.class);
    assert user != null;
    return user.toString();
}

在编写降级逻辑的时候,我们也可以使用 @DefaultProperties 注解,来指定降级逻辑的方法,该注解和 @HystrixCommand 注解不同,@DefaultProperties 注解在类上使用,类中所有的方法都可以开启剪辑逻辑(用@HystrixCommand 注解开启),@HystrixCommand 注解在方法上使用,只能是对应的某个方法使用降级逻辑:

@DefaultProperties(fallbackMethod = "defaultFallback")
@RestController
@RequestMapping(value = "consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    private static final Logger logger = LoggerFactory.getLogger(ConsumerController.class);
    
    @HystrixCommand					// 该方法开启降级逻辑
    @GetMapping("{id}")
    public String queryById(@PathVariable(value = "id") Long id) {
        String url = "http://user-service/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        assert user != null;
        return user.toString();
    }

    public String defaultFallback() {
        return "不好意思,服务器正忙!";
    }
}

熔断服务

熔断原理

熔断器也叫断路器,其英文名为 Circuit Breaker

20200820140332

状态机有三个状态:

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。
  • Open:打开状态(断路器打开),所有请求都会被降级。Hystrix 会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,默认失败比例的阈值是 50% ,请求次数最少不低于20次。
  • Half Open:半开状态,Closed 状态不是永久的,关闭后会进入休眠时间(默认是5秒),随后熔断器后自动进入半开状态。此时释放部分请求通过,若这些请求都是健康的,则会完全打开断路器,否则继续保持关闭,再次进入休眠计时。

动手实践

为了能够精准的控制请求的成功或失败,服务消费者的调用业务中添加一段逻辑:

在 ConsumerDemo 例子中:

@RestController
@RequestMapping(value = "consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    private static final Logger logger = LoggerFactory.getLogger(ConsumerController.class);

    @HystrixCommand(
            commandProperties = {
                    @HystrixProperty(
                            // 熔断器请求阈值次数 10 次。
                            name = "circuitBreaker.requestVolumeThreshold",
                            value = "10"
                    ),
                    @HystrixProperty(
                            // 熔断器关闭状态时,休眠计时时间(毫秒为单位)。
                            name = "circuitBreaker.sleepWindowInMilliseconds",
                            value = "10000"
                    ),
                    @HystrixProperty(
                            // 失败请求百分比,单失败请求百分比达到60%时,熔断器打开。
                            name = "circuitBreaker.errorThresholdPercentage",
                            value = "60"
                    )
            },
            fallbackMethod = "queryByIdFallback"
    )
    @GetMapping("{id}")
    public String queryById(@PathVariable(value = "id") Long id) {
        /* 这个方法中可以添加一些逻辑,来判断访问 */
        // 例如,随便写个逻辑
        // if (id == 1) {
        //     throw new RuntimeException("不好意思,服务器正忙!")
        // }
        String url = "http://user-service/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        assert user != null;
        return user.toString();
    }

    public String queryByIdFallback(Long id) {
        logger.error("查询用户信息失败,id: {}", id);
        return "不好意思,服务器正忙!";
    }
}

说明:

  1. commandProperties 参数:对于注解 @HystrixCommand 中指定的方法,配置一些数据。
  2. @HystrixProperty 注解:指定要被修改的变量。
  3. requestVolumeThreshold : 触发熔断的最小次数(默认是20次)
  4. sleepWindowInMilliseconds : 触发熔断的失败请求最小占比(默认为 50%)
  5. errorThresholdPercentage : 休眠时常(默认是 5000 毫秒)

这样,如果参数 id 为 1,一定失败,其他情况可能会成功,也可能会跳转到失败逻辑方法中。

当我们疯狂访问 id = 1 的请求时,超过我们设定的请求阈值次数 10 次,就会触发熔断。断路器开启,一切请求都会被降级处理。此时如果访问 id != 1 的请求,会发现返回的也是失败,而且失败时间相对于正常访问出错的时间短的很多倍。


posted @ 2021-03-04 19:59  LeeHua  阅读(205)  评论(0编辑  收藏  举报