服务器关闭时负载平衡失败
Loadbalancing fails when a server is down
我写了一组简单的微服务,架构如下:
总而言之,我添加了 spring-boot-starter-actuator
以添加 /health
端点。
在Zuul/Ribbon配置中我添加了:
zuul:
ignoredServices: "*"
routes:
home-service:
path: /service/**
serviceId: home-service
retryable: true
home-service:
ribbon:
listOfServers: localhost:8080,localhost:8081
eureka.enabled: false
ServerListRefreshInterval: 1
因此,每次客户端调用 GET http://localhost:7070/service/home
时,负载均衡器将选择在 8080 或 8081 端口上运行的两个 HomeService 之一,并将调用其端点 /home
。
但是,当其中一个 HomeService 关闭时,负载均衡器似乎没有意识到(尽管有 ServerListRefreshInterval
配置)并且如果它尝试调用关闭实例将失败 error=500
.
我该如何解决?
我收到并测试了 spring-cloud team 的解决方案。
解决方案是here in github
总结:
- 我已将
org.springframework.retry.spring-retry
添加到我的 zuul 类路径
- 我已将
@EnableRetry
添加到我的 zuul 应用程序
- 我在我的 zuul 配置中添加了以下属性
application.yml
server:
port: ${PORT:7070}
spring:
application:
name: gateway
endpoints:
health:
enabled: true
sensitive: true
restart:
enabled: true
shutdown:
enabled: true
zuul:
ignoredServices: "*"
routes:
home-service:
path: /service/**
serviceId: home-service
retryable: true
retryable: true
home-service:
ribbon:
listOfServers: localhost:8080,localhost:8081
eureka.enabled: false
ServerListRefreshInterval: 100
retryableStatusCodes: 500
MaxAutoRetries: 2
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: true
ReadTimeout: 10000
ConnectTimeout: 10000
EnablePrimeConnections: true
ribbon:
eureka:
enabled: false
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
调试超时可能很棘手,考虑到单独有 3 个级别的路由(Zuul→Hystrix→Ribbon),不包括异步执行层和重试引擎。以下方案适用于 Spring Cloud 版本 Camden.SR6 及更新版本(我已在 Dalston.SR1 上检查过):
Zuul 通过 RibbonRoutingFilter
路由请求,这会创建带有请求上下文的 Ribbon 命令。 Ribbon 命令然后创建一个 LoadBalancer 命令,该命令使用 spring-retry 执行命令,根据 Zuul 设置为 RetryTemplate
选择重试策略。 @EnableRetry
在这种情况下什么都不做,因为这个注释在重试代理时启用了带有 @Retryable
注释的包装方法。
这意味着,您的命令持续时间被限制为这两个值中的较小值(参见 this post):
- [
HystrixTimeout
],这是调用 Hystrix 命令的超时
- [
RibbonTimeout * MaxAutoRetries * MaxAutoRetriesNextServer
](仅当 Zuul 在其配置中启用它们时才重试),其中 [RibbonTimeout = ConnectTimeout + ReadTimeout
] 在 http 客户端上。
为了调试,在RetryableRibbonLoadBalancingHttpClient#executeWithRetry
或RetryableRibbonLoadBalancingHttpClient#execute
方法中创建断点很方便。此时,您有:
ContextAwareRequest
实例(例如 RibbonApacheHttpRequest
或 OkHttpRibbonRequest
)带有请求上下文,其中包含 Zuul 的 retryable
属性;
LoadBalancedRetryPolicy
具有负载均衡器上下文的实例,其中包含 Ribbon 的 maxAutoRetries
、maxAutoRetriesNextServer
和 okToRetryOnAllOperations
属性;
RetryCallback
带有 requestConfig 的实例,其中包含 HttpClient 的 connectTimeout
和 socketTimeout
属性;
RetryTemplate
个具有所选重试策略的实例。
如果没有命中断点,则意味着org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient
bean 没有被实例化。当 spring-retry 库不在类路径中时会发生这种情况。
我写了一组简单的微服务,架构如下:
总而言之,我添加了 spring-boot-starter-actuator
以添加 /health
端点。
在Zuul/Ribbon配置中我添加了:
zuul:
ignoredServices: "*"
routes:
home-service:
path: /service/**
serviceId: home-service
retryable: true
home-service:
ribbon:
listOfServers: localhost:8080,localhost:8081
eureka.enabled: false
ServerListRefreshInterval: 1
因此,每次客户端调用 GET http://localhost:7070/service/home
时,负载均衡器将选择在 8080 或 8081 端口上运行的两个 HomeService 之一,并将调用其端点 /home
。
但是,当其中一个 HomeService 关闭时,负载均衡器似乎没有意识到(尽管有 ServerListRefreshInterval
配置)并且如果它尝试调用关闭实例将失败 error=500
.
我该如何解决?
我收到并测试了 spring-cloud team 的解决方案。
解决方案是here in github
总结:
- 我已将
org.springframework.retry.spring-retry
添加到我的 zuul 类路径 - 我已将
@EnableRetry
添加到我的 zuul 应用程序 - 我在我的 zuul 配置中添加了以下属性
application.yml
server:
port: ${PORT:7070}
spring:
application:
name: gateway
endpoints:
health:
enabled: true
sensitive: true
restart:
enabled: true
shutdown:
enabled: true
zuul:
ignoredServices: "*"
routes:
home-service:
path: /service/**
serviceId: home-service
retryable: true
retryable: true
home-service:
ribbon:
listOfServers: localhost:8080,localhost:8081
eureka.enabled: false
ServerListRefreshInterval: 100
retryableStatusCodes: 500
MaxAutoRetries: 2
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: true
ReadTimeout: 10000
ConnectTimeout: 10000
EnablePrimeConnections: true
ribbon:
eureka:
enabled: false
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
调试超时可能很棘手,考虑到单独有 3 个级别的路由(Zuul→Hystrix→Ribbon),不包括异步执行层和重试引擎。以下方案适用于 Spring Cloud 版本 Camden.SR6 及更新版本(我已在 Dalston.SR1 上检查过):
Zuul 通过 RibbonRoutingFilter
路由请求,这会创建带有请求上下文的 Ribbon 命令。 Ribbon 命令然后创建一个 LoadBalancer 命令,该命令使用 spring-retry 执行命令,根据 Zuul 设置为 RetryTemplate
选择重试策略。 @EnableRetry
在这种情况下什么都不做,因为这个注释在重试代理时启用了带有 @Retryable
注释的包装方法。
这意味着,您的命令持续时间被限制为这两个值中的较小值(参见 this post):
- [
HystrixTimeout
],这是调用 Hystrix 命令的超时 - [
RibbonTimeout * MaxAutoRetries * MaxAutoRetriesNextServer
](仅当 Zuul 在其配置中启用它们时才重试),其中 [RibbonTimeout = ConnectTimeout + ReadTimeout
] 在 http 客户端上。
为了调试,在RetryableRibbonLoadBalancingHttpClient#executeWithRetry
或RetryableRibbonLoadBalancingHttpClient#execute
方法中创建断点很方便。此时,您有:
ContextAwareRequest
实例(例如RibbonApacheHttpRequest
或OkHttpRibbonRequest
)带有请求上下文,其中包含 Zuul 的retryable
属性;LoadBalancedRetryPolicy
具有负载均衡器上下文的实例,其中包含 Ribbon 的maxAutoRetries
、maxAutoRetriesNextServer
和okToRetryOnAllOperations
属性;RetryCallback
带有 requestConfig 的实例,其中包含 HttpClient 的connectTimeout
和socketTimeout
属性;RetryTemplate
个具有所选重试策略的实例。
如果没有命中断点,则意味着org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient
bean 没有被实例化。当 spring-retry 库不在类路径中时会发生这种情况。