简单谈谈什么是Hystrix,以及SpringCloud的各种超时时间配置效果,和简单谈谈微服务优化

 

转载于~~ https://blog.csdn.net/zzzgd_666/article/details/83314833 

1. 前言(以下的springcloud版本是Dalston.RC1)

以下的springcloud版本是Dalston.RC1
Springcloud框架中,超时时间的设置通常有三个层面:

  1. zuul网关
#默认1000
zuul.host.socket-timeout-millis=2000
#默认2000
zuul.host.connect-timeout-millis=4000
  • 1
  • 2
  • 3
  • 4
  1. ribbon

ribbon:
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
  ReadTimeout: 5000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 3000 #ribbon请求连接的超时时间,默认值2000
  MaxAutoRetries: 0     #对当前实例的重试次数,默认0
  MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 熔断器Hystrix
hystrix:
  command:
    default:  #default全局有效,service id指定应用有效
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 1000 #断路器超时时间,默认1000ms

feign.hystrix.enabled: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.测试各个配置的效果

这里我开了一个Eureka服务中心
开了两个个服务eureka-client,端口分别为80878088,进行负载均衡
开了一个服务eureka-feign去调用eureka-client的方法,模拟eureka-client处理时间过长的时候出现的情况

eureka-client的方法:

/**
   * 测试重试时间
   *
   * @return
   */
  @RequestMapping("/timeOut")
  public String timeOut(@RequestParam int mills) {
    log.info("[client服务-{}] [timeOut方法]收到请求,阻塞{}ms", port, mills);
    try {
      Thread.sleep(mills);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    log.info("[client服务-{}] [timeOut]返回请求",port);
    return String.format("client服务-%s 请求ok!!!", port);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

eureka-feign调用client的方法,通过传参数mills来控制client线程休眠的时间

 /**
     * 测试重试时间
     * @return
     */
    @RequestMapping("/timeOut")
    public String timeOut(@RequestParam int mills){
        log.info("开始调用");
        return feignService.timeOut( mills );
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

service:

 /**
     * 测试springcloud的超时机制
     * @param mills
     * @return
     */
    @RequestMapping(value = "/timeOut",method = RequestMethod.GET)
    String timeOut(@RequestParam(value = "mills") int mills);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

熔断方法:

 @Override
    public String timeOut(int mills) {
        System.out.println("熔断");
        return "熔断了";
    }
  • 1
  • 2
  • 3
  • 4
  • 5

测试1

ribbon:
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
  ReadTimeout: 1000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 3000 #ribbon请求连接的超时时间,默认值2000
  MaxAutoRetries: 0     #对当前实例的重试次数,默认0
  MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1

hystrix:
  command:
    default:  #default全局有效,service id指定应用有效
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 测试 900ms

在这里插入图片描述
在这里插入图片描述
请求正常.

  • 测试 2000ms
    熔断
    在这里插入图片描述

接着测试4000ms, 6000都熔断了

测试2

更换两个超时时间:

ReadTimeout: 3000   #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
  • 1
  • 2
ribbon:
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
  ReadTimeout: 3000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
  MaxAutoRetries: 0     #对当前实例的重试次数,默认0
  MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1

hystrix:
  command:
    default:  #default全局有效,service id指定应用有效
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

测试2000ms:
成功了
在这里插入图片描述

调用4000ms
熔断了
在这里插入图片描述

测试6000ms也是熔断

可见ReadTimeoutConnectTimeout,当调用某个服务等待时间过长的时候, 对超时报错/熔断生效的是ReadTimeout,ConnectTimeout则表示连接服务的时间,一般不用配置太久,1~2秒左右就可以了

测试3

现在来测试ReadTimeouttimeoutInMilliseconds谁起作用
测试2中的配置如下:

 ReadTimeout: 3000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
 timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms
  • 1
  • 2
  • 3

在4000ms熔断了,2000ms正常,说明是ReadTimeout生效, 现在换成:

ReadTimeout: 5000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
 timeoutInMilliseconds: 3000 #断路器超时时间,默认1000ms

  • 1
  • 2
  • 3
  • 4
ribbon:
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
  ReadTimeout: 5000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
  MaxAutoRetries: 0     #对当前实例的重试次数,默认0
  MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1

hystrix:
  command:
    default:  #default全局有效,service id指定应用有效
      execution:
        timeout:
          #是否开启超时熔断
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 3000 #断路器超时时间,默认1000ms

feign.hystrix.enabled: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2000ms 正常
在这里插入图片描述

4000ms 熔断
在这里插入图片描述

说明熔断器timeoutInMilliseconds: 3000起作用了

测试4

这里再测一个配置:
这个enable如果为false, 则表示熔断器不根据自己配置的超时时间进行熔断,这样的话就会收到ribbon的ReadTimeout配置的影响了,超过这个时间,eureka-feign会抛出timeout的异常,这个时候熔断器就会因为这个异常而进行熔断

hystrix:
  command:
    default:  #default全局有效,service id指定应用有效
      execution:
        timeout:
          #是否开启超时熔断
          enabled: false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试4000ms 正常
在这里插入图片描述

测试6000ms 熔断. 此处是因为ribbon的ReadTimeout: 5000
在这里插入图片描述

3.总结

由上面的测试可以得出:

  1. 如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是熔断器hystrix的timeoutInMilliseconds, 此时谁的值小谁生效
  2. 如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon
  3. ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效
  4. ribbon还有MaxAutoRetries对当前实例的重试次数,MaxAutoRetriesNextServer对切换实例的重试次数, 如果ribbon的ReadTimeout超时,或者ConnectTimeout连接超时,会进行重试操作
  5. 由于ribbon的重试机制,通常熔断的超时时间需要配置的比ReadTimeout长,ReadTimeoutConnectTimeout长,否则还未重试,就熔断了
  6. 为了确保重试机制的正常运作,理论上(以实际情况为准)建议hystrix的超时时间为:(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout
ribbon:
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
  ReadTimeout: 10000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 2000 #ribbon请求连接的超时时间,默认值2000
  MaxAutoRetries: 0     #对当前实例的重试次数,默认0
  MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1

hystrix:
  command:
    default:  #default全局有效,service id指定应用有效
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 20000 #断路器超时时间,默认1000ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.微服务优化

4.1 什么是hystrix

我们先来看这么一个图,假如订单服务需要调用积分服务,库存服务,仓储服务,订单服务的线程池有100个线程,这个时候积分服务突然挂了.这时候同时有大量的请求来访问订单服务,最终的结果是这100个线程都会卡在积分服务这里,这时候订单服务也没有多余的线程处理请求了,所以订单服务也差不多挂了.
在这里插入图片描述
这就是微服务中的服务雪崩问题.
而这时你会发现,如果我只是看看这个商品还有多少库存,那么订单服务就只需要调用库存服务就可以了,并不受积分服务的影响.

这个时候就是Hystrix的时刻了,Hystrix是隔离、熔断以及降级的一个框架。

Hystrix的特点,就是针对不同的服务,会搞很多个小小的线程池,比如订单服务请求库存服务是一个单独的线程池,请求积分服务是一个单独的线程池. 这样虽然积分服务的线程池全部卡住了,但是不影响库存服务的调用.

4.2 服务降级和熔断

现在库存服务的问题解决了,积分服务还没解决啊. 比如积分服务网络很差,订单服务要一直傻等积分服务响应吗?单单看一个请求,用户等个几秒可能还没什么,如果100个线程都卡住几秒,后面的请求全部得不到处理.
所以我们可以让Hystrix在一定时间后主动返回,不再等待,这就是熔断.

降级,顾名思义,就是将不重要或者不紧急的任务,延迟处理,或者暂不处理.比如上面的超时熔断,熔断了怎么办?获取不到用户的积分,直接给用户提示网络繁忙,请稍后再试,就是一种延迟处理. 比如秒杀活动,为了防止并发量太大,通常会采取限流措施,降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。

4.3 微服务优化

了解了Hystrix的特性和超时效果,再看看下面这个图,服务A调用服务B和服务C,服务C没有太复杂的逻辑处理,300毫秒内就处理返回了,服务B逻辑复杂,Sql语句就长达上百行,经常要卡个5,6秒返回,在大量请求调用到服务B的时候,服务A调用服务B的hystrix线程池已经不堪重负,全部卡住
在这里插入图片描述
这里的话,首先考虑的就是服务B的优化,优化SQL,加索引,加缓存, 优化流程,同步改异步,总之缩短响应时间
一个接口,理论的最佳响应速度应该在200ms以内,或者慢点的接口就几百毫秒。

a. 如何设置Hystrix线程池大小

Hystrix线程池大小默认为10

hystrix:
	threadpool:
		default:
			coreSize: 10
  • 1
  • 2
  • 3
  • 4

每秒请求数 = 1/响应时长(单位s) * 线程数 = 线程数 / 响应时长(单位s)

也就是

线程数 = 每秒请求数 * 响应时长(单位s) + (缓冲线程数)

标准一点的公式就是QPS * 99% cost + redundancy count

比如一台服务, 平均每秒大概收到20个请求,每个请求平均响应时长估计在500ms,
线程数 = 20 * 500 / 1000 = 10
为了应对峰值高并发,加上缓冲线程,比如这里为了好计算设为5,就是 10 + 5 = 15个线程

b. 如何设置超时时间

还拿上面的例子,比如已经配置了总线程是12个,每秒大概20个请求,那么极限情况,每个线程都饱和工作,也就是每个线程一秒内处理的请求为 20 / 15 = ≈ 1.3个 , 那每个请求的最大能接受的时间就是 1000 / 1.3 ≈ 769ms ,往下取小值700ms.
实际情况中,超时时间一般设为比99.5%平均时间略高即可,然后再根据这个时间推算线程池大小

posted @ 2019-06-28 15:29  众星拱月  阅读(1986)  评论(0编辑  收藏  举报