1

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

我们有一个问题,当其中一个目标 pod 发生故障时,我们会在一段时间内收到多次请求失败,java.net.NoRouteToHostException即 spring 负载均衡器仍在尝试发送到该 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?那我们会失去什么?

4

2 回答 2

0

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

要解决此问题,您必须使用 Kubernetes 中的就绪和活跃度探针。

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,则默认的 Spring Cloud LoadBalancer 配置使用它来检查服务实例。因此,它仅从已启动并正在运行的实例中进行选择。所需要做的就是使用 @EnableDiscoveryClient 注释您的 Spring Boot 应用程序以启用 K8s-native 服务发现。

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

于 2021-12-15T13:41:22.870 回答
0

对此的更新:我们有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 };              
        }
    }
}

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

于 2021-12-21T11:51:00.713 回答