Spring Cloud Kubernetes 的负载均衡问题

Posted

技术标签:

【中文标题】Spring Cloud Kubernetes 的负载均衡问题【英文标题】:Load balancing problems with Spring Cloud Kubernetes 【发布时间】:2022-01-18 15:21:59 【问题描述】:

我们在 Kubernetes 中运行 Spring Boot 服务,并使用带有 RestTemplate 的 Spring Cloud Kubernetes 负载均衡器功能来调用其他 Spring Boot 服务。我们拥有此功能的主要原因之一是历史原因 - 之前我们使用 Eureka 在 EC2 中运行我们的服务以进行服务发现,并且在迁移之后我们保持 Spring 发现客户端/客户端负载平衡(更新依赖项等)它与 Spring Cloud Kubernetes 项目一起使用)

我们有一个问题,当其中一个目标 pod 发生故障时,我们会在一段时间内使用java.net.NoRouteToHostException 请求多次失败,即弹簧负载平衡器仍在尝试发送到该 pod。

所以我对此有几个问题:

发生这种情况时,目标实例不应该自动删除吗?所以它可能会发生一次,但在那之后,目标pod列表会被修复?

或者如果没有,我们需要添加一些其他配置来处理这个问题 - 例如重试/断路器等?

一个更普遍的问题是 Spring 的客户端负载均衡为 Kubernetes 带来了什么好处?没有它,我们的服务仍然可以使用 Kubernetes 内置的服务/负载平衡功能调用其他服务,这应该可以处理 pod 自动关闭的问题。 Spring 文档还谈到了能够从 POD 模式切换到 SERVICE 模式 (https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/index.html#loadbalancer-for-kubernetes)。但是这种服务模式不就是 Kubernetes 自动做的吗?我想知道这里最简单的解决方案是否不是完全删除 Spring Load Balancer?那我们会失去什么?

【问题讨论】:

如果您删除它,您将需要编写自己的代码来使用服务发现选择实例。该问题可能与缓存有关 - 您是否尝试过缩短缓存 TTL?此外,您可以尝试使用 Instance HealthCheck 并禁用主缓存机制作为替代(健康检查有自己的缓存) - docs.spring.io/spring-cloud-commons/docs/current/reference/html/… 【参考方案1】:

当这个时候目标实例不应该被自动删除吗? 发生?所以它可能会发生一次,但在那之后目标 pod 列表 会修吗?

要解决此问题,您必须使用 Kubernetes 中的 Readiness 和 Liveness Probe。

Readiness 将在时间间隔内检查您的应用程序所具有的端点的运行状况。如果应用程序失败,它会将您的 POD 标记为未准备好接受流量。所以没有流量会流向那个 POD(副本)。

如果应用程序失败,

Liveness 将重新启动您的应用程序,因此您的容器或我们可以说 POD 将再次出现,一旦我们收到来自应用程序的 200 响应,K8s 将标记您POD 准备好接受流量。

您可以在应用程序中创建简单的端点,根据需要提供 200 或 204 响应。

阅读更多:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

确保您的应用程序使用 Kubernetes 服务相互通信。

Application 1 > Kubernetes service of App 2 > Application 2 PODs

要启用基于 Kubernetes 服务名称的负载平衡,请使用 以下属性。然后负载均衡器会尝试调用应用程序 使用地址,例如 service-a.default.svc.cluster.local

spring.cloud.kubernetes.loadbalancer.mode=SERVICE

在 Kubernetes 上使用 Spring Cloud LoadBalancer 最典型的方式是 与服务发现。如果您有任何 DiscoveryClient classpath,默认 Spring Cloud LoadBalancer 配置使用它 检查服务实例。结果,它只选择 已启动并正在运行的实例。所需要的只是注释 使用 @EnableDiscoveryClient 启用 Spring Boot 应用程序 K8s 原生服务发现。

参考:https://***.com/a/68536834/5525824

【讨论】:

【参考方案2】:

对此的更新:我们有 spring-retry 依赖项,但重试不起作用,因为默认情况下它只适用于 GET 并且我们的大多数调用都是 POST(但可以再次调用)。添加配置spring.cloud.loadbalancer.retry.retryOnAllOperations: true 解决了这个问题,因此在第二次尝试时使用替代实例重试应该可以避免大多数此类故障。

我们还添加了一个 RetryListener,用于在某些连接异常时清除服务的负载均衡器缓存:

@Configuration
public class RetryConfig 

    private static final Logger logger = LoggerFactory.getLogger(RetryConfig.class);
    
    // Need to use bean factory here as can't autowire LoadBalancerCacheManager -
    // - it's set to 'autowireCandidate = false' in LoadBalancerCacheAutoConfiguration
    @Autowired
    private BeanFactory beanFactory;
    
    @Bean 
    public CacheClearingLoadBalancedRetryFactory cacheClearingLoadBalancedRetryFactory(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) 
        return new CacheClearingLoadBalancedRetryFactory(loadBalancerFactory);
    
    
    // Extension of the default bean that defines a retry listener
    public class CacheClearingLoadBalancedRetryFactory extends BlockingLoadBalancedRetryFactory 

        public CacheClearingLoadBalancedRetryFactory(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) 
            super(loadBalancerFactory);
        

        @Override
        public RetryListener[] createRetryListeners(String service) 
            
            RetryListener cacheClearingRetryListener = new RetryListener() 
                
                @Override
                public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback)  return true; 
                
                @Override
                public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) 

                @Override
                public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) 
                    
                    logger.warn("Retry for service  picked up exception: context , throwable class ", service, context, throwable.getClass());
                    
                    if (throwable instanceof ConnectTimeoutException || throwable instanceof NoRouteToHostException) 
                
                        try    
                            LoadBalancerCacheManager loadBalancerCacheManager = beanFactory.getBean(LoadBalancerCacheManager.class);                                        
                            Cache loadBalancerCache = loadBalancerCacheManager.getCache(CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME);            
                            if (loadBalancerCache != null)                     
                                boolean result = loadBalancerCache.evictIfPresent(service);
                                logger.warn("Load Balancer Cache evictIfPresent result for service  is ", service, result);                             
                                                       
                         catch(Exception e) 
                            logger.error("Failed to clear load balancer cache", e);
                        
                    
                                               
            ;
                
            return new RetryListener[]  cacheClearingRetryListener ;              
        
    


这种方法有什么问题吗?可以将这样的东西添加到内置功能中吗?

【讨论】:

以上是关于Spring Cloud Kubernetes 的负载均衡问题的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Kubernetes(二)

spring-cloud-kubernetes官方demo运行实战

部署 spring-cloud-kubernetes kubernetes-hello-world-example 失败

Spring Cloud Kubernetes 支持 Spring Cloud 负载均衡器吗?

#yyds干货盘点#spring-cloud-kubernetes与SpringCloud Gateway

存在 spring-cloud-kubernetes 配置映射依赖项的 spring 应用程序启动异常