V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
pvcxy18
V2EX  ›  程序员

springcloud+eureka 如何实现优雅下线?

  •  
  •   pvcxy18 · 2023-07-03 10:11:57 +08:00 · 2025 次点击
    这是一个创建于 533 天前的主题,其中的信息可能已经有所发展或是发生改变。

    eureka 下有 1 个 A 服务节点,此时发布了第 2 个 A 服务节点(新版本),在发布成功后,会停止第 1 个节点服务。此时在 eureka 图形界面中,会以“DOWN”的形式标注出这个即将被关闭的节点。

    但是此时流量仍然可以进入该节点,会导致接口异常。虽然过段时间,等服务彻底停止后,eureka 会调整策略,流量全部走向新节点,但是中间会有一段时间体验很差。

    想问下各位公司里通常采用什么方式解决?

    ps: 我在网上找到的优雅下线相关案例,往往需要运维层面配合。能否不依赖运维,通过服务本身去实现,即 知道自己的服务要被杀死了,提前通知 eureka 进行主动下线。(这一点我尝试过几次,但效果均不太理想,主动下线有延迟,且优先级很低,通知 eureka 太晚了)

    13 条回复    2023-07-04 13:03:52 +08:00
    mmdsun
        1
    mmdsun  
       2023-07-03 10:23:40 +08:00
    刚好负载到下线的那个节点,导致接口不可用?

    应用网关\或者服务加个重试,切换到下一个实例应该就行吧?
    spring.cloud.loadbalancer.retry.enabled=true
    spring.cloud.loadbalancer.retry.max-retries-on-next-service-instance=2
    yisheyuanzhang
        2
    yisheyuanzhang  
       2023-07-03 12:24:09 +08:00
    我是手动注册一个 shutdownHook 替代 SpringContextShutdownHook 。在 shutdownHook 中主动下线后休眠 15s ,再执行 context.close()关闭服务

    服务关闭是 kill pid 。 会触发自定义 shutdownHook 的关闭逻辑,保证关闭后有 15s 的缓冲期来处理请求。
    potatowish
        3
    potatowish  
       2023-07-03 13:02:13 +08:00 via iPhone   ❤️ 1
    这个应该是 eureka 本地缓存导致的,客户端是通过定时任务去拉取最新的服务列表,服务端实例下线时,本地缓存还没来得及刷新。在重试逻辑中触发一下这个任务就行
    pvcxy18
        4
    pvcxy18  
    OP
       2023-07-03 13:46:47 +08:00
    @yisheyuanzhang 这个方案我在网上有看到过,不过似乎有点问题。可以贴代码让我参考一下吗?
    pvcxy18
        5
    pvcxy18  
    OP
       2023-07-03 13:51:41 +08:00
    @potatowish 修改 eureka 的负载均衡策略,快速响应服务下线动作吗?请问有相关的参考配置吗?
    pvcxy18
        6
    pvcxy18  
    OP
       2023-07-03 13:52:53 +08:00
    @mmdsun 这个…我们没有专门的网关服务的,应用间通过微服务名直接调用,走 eureka 的
    mmdsun
        7
    mmdsun  
       2023-07-03 14:14:47 +08:00
    @pvcxy18 spring loadbalancer 和以前的 Netflix 的 Ribbon 都支持切换到下一实例的重试的配置。
    应用间通过微服务名直接调用,这种应该也有用到客户端负载均衡的组件吧。
    simonlu9
        8
    simonlu9  
       2023-07-03 14:50:51 +08:00
    erurka 服务端禁止一二级缓存,erurka 客户端请求服务中心时间缩短,erurka 服务端禁止保护模式
    twogoods
        9
    twogoods  
       2023-07-03 15:33:58 +08:00
    最佳实践应该就是类似 2 楼说的让实例多留一会儿,多处理一个缓存刷新时间内的请求,如果应用运行在 k8s 上配个 prestop 很方便
    yisheyuanzhang
        10
    yisheyuanzhang  
       2023-07-03 17:39:52 +08:00
    @pvcxy18

    public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(DemoApplication.class);
    //启动, 关闭时延迟 30s 销毁容器
    startWithDelayShutdown(springApplication,args, 30);
    }
    /**
    * 延迟停机。服务关闭时,先主动下线服务,延迟一定时间后再关闭服务
    * @param springApplication
    * @param args
    */
    public static void startWithDelayShutdown(SpringApplication springApplication, String[] args, Integer delaySeconds){
    // 默认 deleay 30s
    if(delaySeconds == null){
    delaySeconds = 30;
    }
    //关闭自带的 SpringContextShutdownHook
    springApplication.setRegisterShutdownHook(false);
    //启动 Spring
    ConfigurableApplicationContext context = springApplication.run(args);
    //注册自定义 SpringContextShutdownHook
    Integer finalDelaySeconds = delaySeconds;
    Thread shutdownHook = new Thread("MY-SpringContextShutdownHook") {

    public void run() {
    //我这里是 nacos ,其他注册中心都是一样的
    log.info("服务停止,主动下线");
    NacosAutoServiceRegistration nacosAutoServiceRegistration = SpringContextUtils.getBean(NacosAutoServiceRegistration.class);
    nacosAutoServiceRegistration.stop();
    //下线 30s 后停止
    log.info("停止,休眠{}s", finalDelaySeconds);
    try {
    Thread.sleep(finalDelaySeconds *1000L);
    } catch (InterruptedException e) {
    log.error(e.getMessage(), e);
    }
    log.info("停止,休眠{}s 结束,销毁容器", finalDelaySeconds);
    context.close();
    }
    };
    Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
    liupeng2579793
        11
    liupeng2579793  
       2023-07-03 23:44:16 +08:00
    我们是微服务之间基于 feign 调用,集成了 ribbon,服务下线,jekins 会调用接口,里面会广播一条消息给其他服务,其他服务消费到消息后,会清空本地的 ribbon 缓存,重新拉 eureka 的配置信息
    potatowish
        12
    potatowish  
       2023-07-04 13:01:47 +08:00 via iPhone
    2L 这个方案应该是让实例多留一会儿,继续处理请求,直到下一个缓存更新周期后,本地缓存更新到新的上线实例。
    这基本可以解决调用到已经下线的实例出现大量异常的问题。但缺点就是需要调用方配合做这样的改造,还有上面提到的在 loadbalancer 上重试下一个实例也是如此。

    如果你的业务要求更高,要让新的请求尽快切到新的实例,那么在应用层面,可以在 shutdownhook 中发送一个实例下线的消息,调用方收到后立刻拉取最新实例列表。这个是结合了 2L 和 11L 的方案。
    potatowish
        13
    potatowish  
       2023-07-04 13:03:52 +08:00 via iPhone
    @potatowish
    发错修改 >>
    2L 这个方案应该是让实例多留一会儿,继续处理请求,直到下一个缓存更新周期后,本地缓存更新到新的上线实例。这基本可以解决调用到已经下线的实例出现大量异常的问题。

    如果你的业务要求更高,要让新的请求尽快切到新的实例,那么在应用层面,可以在 shutdownhook 中发送一个实例下线的消息,调用方收到后立刻拉取最新实例列表。这个是结合了 2L 和 11L 的方案。但缺点就是需要调用方配合做这样的改造,还有上面提到的在 loadbalancer 上重试下一个实例也是如此。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5519 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:37 · PVG 10:37 · LAX 18:37 · JFK 21:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.