大家好,又见面了,我是你们的朋友全栈君。
2022年4月25日更新, 对MaxAutoRetries和MaxAutoRetriesNextServer增加了新的理解
简单谈谈什么是Hystrix,以及SpringCloud的各种超时时间配置效果,和简单谈谈微服务优化- 1. 前言(以下的springcloud版本是Hoxton.SR8`配合SpringBoot2.x版本`)
- 2.测试各个配置的效果
- 2.1 ribbon超时配置测试
- 测 ReadTimeout < ConnectTimeout
- 测 ReadTimeout > ConnectTimeout
- 2.2 测试`ReadTimeout`和`timeoutInMilliseconds`谁起作用
- 2.3 测试 hystrix 超时配置enable
- 2.4 测试 重试次数MaxAutoRetries和MaxAutoRetriesNextServer
- 2.5 测试hystrix的超时时间和重试次数
- 3.总结
- 4.微服务优化
- 4.1 什么是hystrix
- 4.2 服务降级和熔断
- 4.3 微服务优化
- a. 如何设置Hystrix线程池大小
- b. 如何设置超时时间
- 2.1 ribbon超时配置测试
- 测 ReadTimeout < ConnectTimeout
- 测 ReadTimeout > ConnectTimeout
- 2.2 测试`ReadTimeout`和`timeoutInMilliseconds`谁起作用
- 2.3 测试 hystrix 超时配置enable
- 2.4 测试 重试次数MaxAutoRetries和MaxAutoRetriesNextServer
- 2.5 测试hystrix的超时时间和重试次数
- 4.1 什么是hystrix
- 4.2 服务降级和熔断
- 4.3 微服务优化
- a. 如何设置Hystrix线程池大小
- b. 如何设置超时时间
1. 前言(以下的springcloud版本是Hoxton.SR8配合SpringBoot2.x版本
)
以下的springcloud版本是Dalston.RC1 Springcloud框架中,超时时间的设置通常有三个层面:
- zuul网关
#默认1000
zuul.host.socket-timeout-millis=2000
#默认2000
zuul.host.connect-timeout-millis=4000
- ribbon
ribbon:
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
ReadTimeout: 5000 #负载均衡超时时间,默认值5000
ConnectTimeout: 3000 #ribbon请求连接的超时时间,默认值2000
MaxAutoRetries: 0 #对当前实例的重试次数,默认0
MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认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
2.测试各个配置的效果
这里我开了一个Eureka服务中心
开了两个个服务eureka-client,端口分别为8087
和8088
,进行负载均衡
开了一个服务eureka-feign8080
去调用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);
}
消费者eureka-feign
调用client的方法,通过传参数mills
来控制client线程休眠的时间
/** * 测试重试时间 * @return */
@RequestMapping("/timeOut")
public String timeOut(@RequestParam int mills){
log.info("开始调用");
return feignService.timeOut( mills );
}
eureka-feign
的service:
/** * 测试springcloud的超时机制 * @param mills * @return */
@RequestMapping(value = "/timeOut",method = RequestMethod.GET)
String timeOut(@RequestParam(value = "mills") int mills);
eureka-feign
的熔断方法:
@Override
public String timeOut(int mills) {
System.out.println("熔断");
return "熔断了";
}
访问8080端口, 调用eureka-feign的/timeOut
接口, 然后eureka-feign再调用client的服务。下面的配置是eureka-feign
的
2.1 ribbon超时配置测试
测 ReadTimeout < ConnectTimeout
代码语言:javascript复制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
- 测试 900ms
请求正常.
- 测试 2000ms 熔断
接着测试4000ms, 6000都熔断了
测 ReadTimeout > ConnectTimeout
更换两个超时时间:
代码语言:javascript复制ReadTimeout: 3000 #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
代码语言:javascript复制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
测试2000ms: 成功了
调用4000ms 熔断了
测试6000ms也是熔断
可见
ReadTimeout
和ConnectTimeout
,当调用某个服务等待时间过长的时候, 对超时报错/熔断生效的是ReadTimeout
,ConnectTimeout
则表示连接服务的时间,一般不用配置太久,1~2秒左右就可以了
2.2 测试ReadTimeout
和timeoutInMilliseconds
谁起作用
测试中的配置如下:
代码语言:javascript复制 ReadTimeout: 3000 #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms
在4000ms熔断了,2000ms正常,说明是ReadTimeout
生效, 现在换成:
ReadTimeout: 5000 #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
timeoutInMilliseconds: 3000 #断路器超时时间,默认1000ms
也就是
代码语言:javascript复制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
2000ms 正常
4000ms 熔断
说明熔断器timeoutInMilliseconds: 3000
起作用了
2.3 测试 hystrix 超时配置enable
这里再测一个配置:
这个enable如果为false
, 则表示熔断器不根据自己配置的超时时间进行熔断,这样的话就会收到ribbon的ReadTimeout
配置的影响了,超过这个时间,eureka-feign会抛出timeout
的异常,这个时候熔断器就会因为这个异常而进行熔断
hystrix:
command:
default: #default全局有效,service id指定应用有效
execution:
timeout:
#是否开启超时熔断
enabled: false
测试4000ms 正常
测试6000ms 熔断. 此处是因为ribbon的ReadTimeout: 5000
2.4 测试 重试次数MaxAutoRetries和MaxAutoRetriesNextServer
代码语言:javascript复制ribbon:
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
ReadTimeout: 3000 #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用,默认0
MaxAutoRetriesNextServer: 1 #切换实例的次数,默认1. 因此总调用的请求数是 (1 MaxAutoRetries)*(MaxAutoRetriesNextServer 1)
- feign调用端的配置如上(
同时设置hystrix的超时时间为1分钟,尽可能大
) - client启动
8090
和8091
两个实例 - 请求一个必然超时的请求(5秒)
8090:
代码语言:javascript复制[INFO ] 2022-04-25 19:55:58.728 [http-nio-8090-exec-9] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8090] [timeOut方法]收到请求,阻塞20000ms
[INFO ] 2022-04-25 19:56:01.728 [http-nio-8090-exec-10] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8090] [timeOut方法]收到请求,阻塞20000ms
8091:
代码语言:javascript复制[INFO ] 2022-04-25 19:55:52.707 [http-nio-8091-exec-10] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8091] [timeOut方法]收到请求,阻塞20000ms
[INFO ] 2022-04-25 19:55:55.701 [http-nio-8091-exec-9] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8091] [timeOut方法]收到请求,阻塞20000ms
可以看出是 ribbon在52秒的时候先请求了 8091, 根据feign的ReadTimeout超时配置, 3秒后(55秒)再次请求了一遍. 3s后失败, 58秒开始转向另一个服务8090请求, 3s后再次失败, 重试1次. 一共 2 2 = 4次
如果增加一个实例8092 同时修改feign的MaxAutoRetriesNextServer配置为2
代码语言:javascript复制ribbon:
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
ReadTimeout: 3000 #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用,默认0
MaxAutoRetriesNextServer: 2 #切换实例的次数,默认1. 因此总调用的请求数是 (1 MaxAutoRetries)*(MaxAutoRetriesNextServer 1)
再次尝试, 发现
8091:
代码语言:javascript复制[INFO ] 2022-04-25 20:12:55.140 [http-nio-8091-exec-8] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8091] [timeOut方法]收到请求,阻塞20000ms
[INFO ] 2022-04-25 20:12:58.101 [http-nio-8091-exec-1] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8091] [timeOut方法]收到请求,阻塞20000ms
8092:
代码语言:javascript复制[INFO ] 2022-04-25 20:13:01.284 [http-nio-8092-exec-7] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8092] [timeOut方法]收到请求,阻塞20000ms
[INFO ] 2022-04-25 20:13:04.289 [http-nio-8092-exec-8] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8092] [timeOut方法]收到请求,阻塞20000ms
8090
代码语言:javascript复制[INFO ] 2022-04-25 20:13:07.299 [http-nio-8090-exec-6] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8090] [timeOut方法]收到请求,阻塞20000ms
[INFO ] 2022-04-25 20:13:10.309 [http-nio-8090-exec-8] com.zgd.springcloud.eurekaClient.controller.ProducerController - [client服务-8090] [timeOut方法]收到请求,阻塞20000ms
可以看到3个实例都被依次重试了1次(调用2次)
当然还可以尝试修改MaxAutoRetries, 这里就不尝试了
所以可以看出:
总调用次数 = (1 单机重试次数) * (1 转移其他服务进行重试的次数)
那hystrix的超时时间也应该按照这个来参考设置
2.5 测试hystrix的超时时间和重试次数
还是上面的配置, 3个实例.
代码语言:javascript复制ribbon:
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
ReadTimeout: 3000 #负载均衡超时时间,默认值5000
ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用,默认0
MaxAutoRetriesNextServer: 2 #切换实例的次数,默认1. 因此总调用的请求数是 (1 MaxAutoRetries)*(MaxAutoRetriesNextServer 1)
- 我们选择请求耗时5秒(满足超时ReadTimeout就行, 但是不能太久, 否则会超过hystrix的超时)
根据上面的公式, 我们知道会 (1 1)*(1 2) = 6次调用, 每次超时为3秒, 一共18秒.
测试17秒
我们设置hystrix的超时为17s
看看
hystrix:
command:
default: #default全局有效,service id指定应用有效
execution:
timeout:
#是否开启超时熔断
enabled: true
isolation:
thread:
timeoutInMilliseconds: 17000 #断路器超时时间,默认1000ms
访问http://localhost:8080/timeOut?mills=5000
等待17秒, 虽然重试了6次, 浏览器在17秒因为hystrix已经返回熔断
timeoutInMilliseconds修改19s(或者可以设置更大)后再试一次
可见这次虽然也是熔断(因为重试必然也是超时), 但是这次时间在18s左右, 还未到hystrix的19秒, (虽然这样测试有点粗糙, 但是打印详细日志的话可以看出和上面的熔断原因还是不一样的)
可见如果我们不希望因为hystrix的超时导致重试失去它的作用, 我们要把hystrix的超时时间配置得比几次超时的时间更大
3.总结
由上面的测试可以得出:
- 如果
hystrix.command.default.execution.timeout.enabled
为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout
,一个就是熔断器hystrix的timeoutInMilliseconds
, 此时谁的值小谁生效 - 如果
hystrix.command.default.execution.timeout.enabled
为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout
抛出的异常而熔断,也就是取决于ribbon - ribbon的
ConnectTimeout
,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效 - ribbon还有
MaxAutoRetries
是单个实例的重试次数,MaxAutoRetriesNextServer
对切换实例的次数(是切换次数,不是重试次数), 如果ribbon的ReadTimeout
超时,或者ConnectTimeout
连接超时,会进行重试操作 - 由于ribbon的重试机制,通常熔断的超时时间需要配置的比
ReadTimeout
长,ReadTimeout
比ConnectTimeout
长,否则还未重试,就熔断了 - 为了确保重试机制的正常运作,理论上(以实际情况为准)建议hystrix的超时时间为:
(1 MaxAutoRetries)*(1 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
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线程池大小
代码语言:javascript复制Hystrix线程池大小默认为10
hystrix:
threadpool:
default:
coreSize: 10
每秒请求数 = 1/响应时长(单位s) * 线程数 = 线程数 / 响应时长(单位s)
也就是
线程数 = 每秒请求数 * 响应时长(单位s) (缓冲线程数)
标准一点的公式就是QPS * 99% cost redundancy count
比如一台服务, 平均每秒大概收到20个请求,每个请求平均响应时长估计在500ms, 线程数 = 20 * 500 / 1000 = 10 为了应对峰值高并发,加上缓冲线程,比如这里为了好计算设为5,就是 10 5 = 15个线程
b. 如何设置超时时间
还拿上面的例子,比如已经配置了总线程是15个,每秒大概20个请求,那么极限情况,每个线程都饱和工作,也就是每个线程一秒内处理的请求为 20 / 15 = ≈ 1.3个 , 那每个请求的最大能接受的时间就是 1000 / 1.3 ≈ 769ms ,往下取小值700ms. 实际情况中,超时时间一般设为比99.5%平均时间略高即可,然后再根据这个时间推算线程池大小
项目地址 https://github.com/zzzgd/MySpringCloud
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/234246.html原文链接:https://javaforall.cn