微服务7-Hytrix实现微服务的容错处理

本文介绍了微服务中应对雪崩效应的断路器模式,重点解析了Hystrix如何实现容错处理。通过事上练的方式展示了Hystrix在断路器状态监控、线程隔离策略、Feign整合、回退机制等方面的运用,并探讨了Hystrix Dashboard监控和Turbine数据聚合。同时,提出了在微服务与Turbine网络不通时,如何借助消息中间件收集数据。
摘要由CSDN通过智能技术生成

格物致知,格Hystrix
某电子商务网站在一个黑色星期五发生过载.过多的并发请求,导致用户支付的请求延迟很久没有响应,在等待很长时间后最终失败。支付失败又导致用户重新刷新页面并再次尝试支付,进一步增加了服务器的负载,最终整个系统都崩溃了。

雪崩效应 当依赖的服务不可用时,服务自身会不会被拖垮?这是我们要考虑的问题。

断路器模式

一个远程调用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。资源就会被耗尽,最终导致服务不可用。
断路器可以理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
断路器可以实现快速失败,当它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就不须浪费CPU时间去等待长时间的超时。
断路器也可以自动诊断依赖的服务是否已经恢复正常。

  • 正常情况下断路器关闭,可正常请求依赖的服务。
  • 当一段时间内,请求失败率达到一定阈值,断路器就会打开。此时,不会再去请求依赖的服务。
  • 断路器打开一段时间后,会自动进入‘半开’状态。此时,断路器可允许一个请求访问依赖的服务。如果请求能够调用成功,则关闭断路器;否则继续保持打开状态。

使用Hystrix实现容错

Hytrix是一个实现了超时机制和短路器模式的工具类库。

  • 包裹请求
  • 跳闸机制
  • 资源隔离: Hystrix 为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就会被立即拒绝,而不是排队等候,从而加速失败判定。
  • 监控: Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功/失败/超时/被拒绝的请求等。
  • 回退机制:当失败/超时/被拒绝,或当断路器打开时,执行回退逻辑。逻辑可以自定义,例如返回一个缺省值。
  • 自我修复:‘半开’状态。

事上练

通用方式整合Hystrix
Spring Cloud 自动将Srping bean 与该注解@HystrixCommand封装在一个连接到Hystrix断路器的代理中。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix //@EnableCircuitBreaker

// 这是用来测试feign 和@FeignClient 注解的接口同时使用
public class MovieApp 
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    public User findByIdFallback(Long id) {
        User user = new User();
        user.setId(-1L);
        user.setName("默认用户");
        return user;
    }

    @HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties= {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
            @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000")
    }, threadPoolProperties = {
            @HystrixProperty(name = "coreSize", value = "1"),
            @HystrixProperty(name = "maxQueueSize", value = "10")
    })
    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id) {
//      return this.restTemplate.getForObject("http://user-service/ycq/" + id, User.class);
        return this.userFeignClient.findById(id);
    }

正常情况下返回结果:

{ id: 1, username: “account1”, name: “张三”, age: 20, balance: 100 }

关闭用户提供者服务则返回:

{ id: -1, username: null, name: “默认用户”, age: null, balance: null }

Hystrix断路器的状态监控与深入理解

关闭服务提供者,hytrix健康状态为UP,说明短路器还是关闭的。
访问Acture提供的health端点,可以查看到:

db: {
    status: "UP",
    database: "H2",
    hello: 1
},
refreshScope: {
    status: "UP"
},
hystrix: {
    status: "UP"
}

强调-执行回退逻辑并不代表断路器已经打开,因为我们的失败率还没有达到阈值(默认是5秒内20次失败)

持续快速请求服务提供者,直到请求快速返回。可以看到断路器打开,不会再请求服务提供者。

refreshScope: {
    status: "UP"
},
hystrix: {
    status: "CIRCUIT_OPEN",
    openCircuitBreakers: [
        "user-service/ycq::UserFeignClient#findById(Long)",
        "MovieController::findById"
    ]
}

Hystrix线程隔离策略与传播上下文

Hystrix的隔离策略两种: 分别是线程隔离和信号量隔离。

  • THREAD(线程隔离):使用该方式,HystrixCommand将会在单独的线程上执行,并发请求受线程池中线程数量的限制。
  • SEMAPHORE(信号量隔离):使用该方式,HystrixCommand将会在调用线程上执行,开销相对较小,并发请求受到信号量个数的限制。
    Hystrix中默认并且推荐使用线程隔离(THREAD),因为这种方式有一个除网络超时以外的额外保护。
    一般来说,只有当调用负载异常高时(例如每个实例每秒调用数百次)才需要信号量隔离,因为这种场景下使用THREAD开销会比较高。信号量隔离一般仅适用于非网络调用的隔离。
    可以使用execution.isolation.strategy属性指定隔离策略。

正常情况下,默认为线程隔离, 保持默认即可。
如果发生找不到上下文运行时异常,可考虑将隔离策略设置为SEMAPHORE。

Feign使用Hystrix

前面讲的是使用注解@HystrixCommand的fallbackMethod属性实现回退的。然而,Feign是以接口形式工作的,它没有方法体。

Spring Cloud默认已为Feign整合了Hystrix,只要Hystrix在项目的classpath中,Feign默认就会用断路器包裹所有方法。

// 1.这是用来测试feign 
@FeignClient(name = "user-service/ycq", configuration = FeignLogConfiguration.class,
        fallback = FeignClientFallback.class)
public interface UserFeignClient {

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public User findById(@PathVariable("id") Long id);

}

@Component
class FeignClientFallback implements UserFeignClient {

    public User findById(Long id) {
        User user = new User();
        user.setId(-1L);
        user.setUsername("为Feign使用Hytrix添加回退");
        return user;
    }

当服务提供者未启动时,返回如下:

{
id: -1,
username: “为Feign使用Hytrix添加回退”,
name: null,
age: null,
balance: null
}

通过Fallback Factory检查回退原因

方法和刚才的为Feign使用Hytrix添加回退一样。事上练后发现走了fallback分支,fallbackFactory,没起效果,删除掉fallback配置就行了。

@FeignClient(name = "user-service/ycq", configuration = FeignLogConfiguration.class,
        fallback = FeignClientFallback.class,
        fallbackFactory = FeignClientFallbackFactory.class)

@Component
class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {

    protected static final Logger LOGGER = LoggerFactory.getLogger(FeignClientFallbackFactory.class);

    public UserFeignClient create(final Throwable cause) {
        return new UserFeignClient() {

            public User findById(Long id) {
                FeignClientFallbackFactory.LOGGER.info("fallback; reason was:", cause);
                User user = new User();
                user.setId(-1L);
                user.setUsername("为Feign使用Hytrix添加回退这个还可以打印日志");
                return user;
            }
        };
    }

}

fallbackFactory刚才的方法里面可以对异常类型进行判断返回不同的回退结果。注意cause有可能是null(该Bug在Feign 9.4.0中已被解决)

为Feign禁用Hystrix

前面讲过,在Spring Cloud中,只要Hystrix在项目的classpath中,Feign就会使用断路器包裹Feign客户端的所有方法。这样方便但很多场景下无用,如何禁用。

  • 指定服务禁用
@Configuration
// 想要禁用Hystrix的@FeignClient引用该配置类即可。
public class FeignDisableHystrixConfiguration {
    @Bean
    @Scope("prototype") 
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

测试的话,则需要谨慎了,这里之事对单个的服务设置的。所以可以5秒内刷新多次,看hystrix的状态是否为打开。如果关闭,也就是状态为UP,则满足我们的测试点。 可惜错了,说明这种配置还是有问题的。


//  @HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties= {
//          @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
//          @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000")
//  }, threadPoolProperties = {
//          @HystrixProperty(name = "coreSize", value = "1"),
//          @HystrixProperty(name = "maxQueueSize", value = "10")
//  })
    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id) {

注释掉才行。当然下面的回退策略也不生效了。

// 1.这是用来测试feign 
@FeignClient(name = "user-service/ycq", configuration = FeignDisableHystrixConfiguration.class,
//      fallback = FeignClientFallback.class,
        fallbackFactory = FeignClientFallbackFactory.class)
public interface UserFeignClient {
  • 全局禁用
    在application.yml 中配置 feign.hystrix.enabled = false

Hystrix的监控

包含starter-hystrix的项目都有了这个功能,访问actuator端点hystrix.stream即可。

Hystrix Dashboard可视化监控数据

  1. 加入@EnableHystrixDashboard
  2. 配置application.xml server.port=8030
  3. 加入依赖
@SpringBootApplication
@EnableHystrixDashboard
public class Monitor 
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        <version>1.2.4.RELEASE</version>
    </dependency>

缺点:只能使用/hystrix.stream端点监控单个微服务实例。
事上练: 因为访问量太少,Dashboard显示的图片不是很直观,这个时候我想到了压测工具JMeter。 场景:100万个用户5秒内请求,抢红包(科大讯飞面试考官很感兴趣)。
先设置JMeter参数:
这里写图片描述
这里写图片描述
我的电脑是8CPU,可以看到JMeter显示每秒处理完595个请求。跑了几秒,后面全开始报错了。

java.net.BindException: Address already in use: connect
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
at java.net.PlainSocketImpl.connect(Unknown Source)
at java.net.SocksSocketImpl.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
at org.apache.jmeter.protocol.http.sampler.hc.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:318)
at org.apache.jmeter.protocol.http.sampler.MeasuringConnectionManager$MeasuredConnection.open(MeasuringConnectionManager.java:114)

短时间内new socket操作过多
而socket.close()操作并不能立即释放绑定的端口
而是把端口设置为TIME_WAIT状态
过段时间(默认240s)才释放(用netstat -na可以看到)
最后系统资源耗尽
(windows上是耗尽了pool of ephemeral ports 这段区间在1024-5000之间)
参考:https://blog.csdn.net/ywb201314/article/details/51258777

格这个问题:
首先C:\Users\16558>netstat -na > netstat.txt 将日志保存后查看进程:
发现所有进程大部分处于TIME_WAIT状态, 而进程总数量为16700多。过了一会儿这个峰值就变成了300个。 知行合一,用知识来证明这个事情是这样的,确实是这个毛病。
后来发现,原来是JMeter我们设置的用户100万太多了,设置1万<16700才行。这种测100万并发的,得搞个压测的微服务(肉鸡够多)才行。
这里写图片描述
这里写图片描述
上图是改成1万个用户请求后的结果,吞吐是1000。
字体颜色标记的意思在右上角有对应的解释,比如红色是Failure等。
到此为止吧,以后再慢慢格这个,已经黔驴技穷了,有人有啥想法,我可以继续格。

Turbine聚合监控数据

刚才讨论的使用/hystrix.stream端点监控单个微服务实例,显然不适用,一般我们会部署多个实例。Turbine可以将所有相关的/hystrix.stream端点的数据聚合到一个组合的/turbine.stream中。效果图:
这里写图片描述
这里首先新建一个工程 hystrix-turbine,利用它整合多个微服务数据。
三步到位:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-turbine</artifactId>
    </dependency>

application.yml配置

server:
  port: 8013
spring:
  application:
    name: hystrix-turbine-app
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8111/eureka
  instance:
    prefer-ip-address: true
turbine:
  appConfig: film-service2, film-service
  clusterNameExpression: "'default'"
@SpringBootApplication
@EnableTurbine
public class HystrixTurbineApp 
{
    public static void main( String[] args )
    {
        SpringApplication.run(HystrixTurbineApp.class, args);
    }
}

最后打开Hystrix Dashboard首页,在URL一栏加上地址即可:
这里写图片描述

使用消息中间件收集数据

场景: 微服务与Turbine网络不通,此时,可借助消息中间件实现数据收集。各个微服务将Hystrix Command的监控数据发送至消息中间件,Turbine消费消息中间件中的数据。如以RabbitMQ为例:

ActiveMQ安装传送门
首先在设置断路器hystrix和turbine(监控)中application.yml中加入:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  1. 改造微服务,服务消费者film-service增加如下依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
 </dependency>
 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
 </dependency>
  1. 改造Turbine
    启动类:注释掉@EnableTurbine,增加@EnableTurbineStream
/**
 * Hello world!
 *
 */
@SpringBootApplication
//@EnableTurbine
@EnableTurbineStream
public class HystrixTurbineApp 
{
    public static void main( String[] args )
    {
        SpringApplication.run(HystrixTurbineApp.class, args);
    }
}

依赖:注释掉starter-turbine,增加starter-turbine-stream和starter-stream-rabbit

<!--    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-turbine</artifactId>
    </dependency> -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-turbine-stream</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>

这里写图片描述
这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值