目录

一、问题概述

二、问题分析与解决

三、总结


一、问题概述

对系统进行升级时发生了失败。具体问题表现为在升级过程中,nacos注册中心部分服务注册失败,启动日志报错:

feign.FeignException$ServiceUnavailable: [503] during [POST],

Load balancer does not contain an instance for the service XXXXX ,

No servers available for service: XXXXX,

系统无法正常启动,失败的服务经过手动重启一次或多次,可以成功启动,因存在问题,进行了版本回退,最终导致整个升级过程未能完成。

升级技术清单

序号

类型

版本现状

新版本选择

1

SpringBoot

2.3.2.RELEASE

2.7.18

2

SpringCloud

Hoxton.SR9

2021.0.9

3

SpringCloudAlibaba

2.2.6.RELEASE

2021.0.1.0

4

nacos-client

1.4.2

2021.0.1.0内嵌1.4.2

5

Nacos 服务端

2.2.3

不变

二、问题分析与解决

问题排查过程中,排除了架构版本依赖关系不兼容、版本冲突等问题,问题可能在系统启动调用微服务获取码表的过程中,存在一定的服务发现延迟问题,即对码表初始化方法调用的时机进行了调整和相关配置调整。

1.码表初始化方法调用的时机调整

  • 使用CommandLineRunner 代替@PostConstruct
  • 加入异步任务@Async,不阻塞主线程
  • 对注册中心服务和调用服务实例进行有效性检测

2.设置Feign超时时间

1.码表初始化方法调用的时机调整

调整调用时机

@Component
public class CommandLineRunnerEx implements CommandLineRunner {

    private final ServiceDiscoveryUtil serviceDiscoveryUtil;


    // 使用构造函数注入 serviceDiscoveryUtil
    @Autowired
    public CommandLineRunnerEx(ServiceDiscoveryUtil serviceDiscoveryUtil) {
        this.serviceDiscoveryUtil= serviceDiscoveryUtil;
    }

    /**
     * 服务检测
     * 加入异步任务,使用 @Async 注解后,Spring 会在异步线程中执行 run() 方法,因此不会阻塞主线程或启动过程。 
     */
    @Async
    @Override
    public void run(String... args) throws Exception {
        while (!serviceDiscoveryUtil.isServiceAvailable(“SHOP”)&&!serviceDiscoveryUtil.hasInstances(“SHOP”)) {
            log.warn("等待SHOP 服务可用...");
            try {
                TimeUnit.SECONDS.sleep(5);  // 每5秒检查一次服务,直到服务可用。
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();  // 处理中断
                break;  // 如果线程被中断,退出循环
            }
        }
        init();
    }


    /**
     * 初始化字典组件。
     * 注释@PostConstruct注解,改为应用启动后初始化码表,实现CommandLineRunner接口 
     * 把码表初始化代码放在run方法中执行
     */
   // @PostConstruct //1在构造函数执行完之后执行
    public static void init() {
       //获取码表,调用SHOP相关服务。。。
    }

}


线程池设置


/**
 * @description: 线程池设置
 */
@Configuration
public class ThreadPoolConfig {
    /**
     * 使用 Spring 管理线程池的生命周期,单例
     */
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);// 核心线程池大小,如果任务量频繁且需要快速响应,适当调整核心线程以减少线程创建开销。
        executor.setMaxPoolSize(10);// 最大线程池大小
        executor.setQueueCapacity(500); // 队列容量,默认有界队列LinkedBlockingQueue
        executor.setThreadNamePrefix("消费帮扶ADMIN服务线程-"); // 线程名称前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); //拒绝策略,当任务数量超过队列容量时,直接抛出异常
        executor.setKeepAliveSeconds(60);//60秒后空闲的非核心线程会被销毁
        executor.setAllowCoreThreadTimeOut(true);//核心线程在空闲时间超过 keepAliveTime 后是否可以被销毁。
        //executor.setPrestartAllCoreThreads(false);//是否在创建 ThreadPoolTaskExecutor 时立即启动所有核心线程
        executor.initialize(); // // 初始化线程池
        log.info("--服务线程- executor init--");
        return executor;
    }

}

服务检测

@Component
public class ServiceDiscoveryUtil {
    //服务发现,获取服务的实例列表及元数据。
    @Autowired
    private DiscoveryClient discoveryClient;

    //服务调用时的负载均衡,选择一个实例进行调用
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    /**
     *  Spring Cloud 提供了 LoadBalancerClient 接口,可以用来通过负载均衡器获取注册到 Nacos 上的服务信息。可以在代码中使用它来获取服务实例的信息。需要引入spring-cloud-starter-loadbalancer依赖
     */
    public boolean  hasInstances(String serviceId) {
        ServiceInstance instances = loadBalancerClient.choose(serviceId);
        if (instances != null) {
            System.out.println("可调用服务实例信息: " +serviceId+":"+ instances.getHost() + ":" + instances.getPort());
            return true;
        } else {
            System.out.println("没有"+serviceId+"服务实例可调用!");
            return false;
        }
    }
    //检查服务是否可用
    public boolean isServiceAvailable(String serviceId) {
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);  // 获取所有该服务的实例
        if (instances != null &&  !instances.isEmpty()) {
            System.out.println("注册中心服务实例名:"+serviceId+",个数:"+instances.size());
            return true;
        }else{
            System.out.println("注册中心没有发现服务实例:"+serviceId);
            return false;
        }
    }


}

2.Feign超时时间设置

Spring Cloud 2020.0.0后续版本删除掉了Netflix除Eureka外的所有组件,Netflix组件替代方案:

Netflix

推荐替代品

Hystrix

Resilience4j

Hystrix Dashboard / Turbine

Micrometer + Monitoring System

Ribbon

Spring Cloud Loadbalancer

Zuul 1

Spring Cloud Gateway

Archaius 1

Spring Boot外部化配置 + Spring Cloud配置

负载均衡Ribbon被替换成了Spring Cloud Loadbalancer,所以之前的Ribbon相关配置理论是失效的。

而Feign的默认超时时间是1秒修改超时时间。

三、总结

定期检查项目中的依赖版本,并与官方文档保持同步,确保项目使用的版本始终是经过充分验证的兼容版本。

spring-cloud-alibaba版本说明:

版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub

SpringCloud与SpringBoot版本说明:

Spring Cloud

能提供思路的技术文档:

Spring Cloud Feign启动Load balancer does not have available server for client分析-CSDN博客

网关报错:Load balancer does not have available server for client: xxx_correcttaskremoteapi#savebatchcorrecttask(list)]: -CSDN博客

扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理_openfeign loadbalancer-CSDN博客

Nacos服务发现原理分析 - 小码A梦 - 博客园

解决Feign客户端调用失败-CSDN博客

Spring Cloud 2020.0.0发布 再见了Netflix_spring cloud 哪个版本移除了netflix-CSDN博客

解决gateway使用nacos重启报503 Service Unavailable问题

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐