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

2023-07-03 10:11:57 +08:00
 pvcxy18

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

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

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

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

2039 次点击
所在节点    程序员
13 条回复
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
2023-07-03 12:24:09 +08:00
我是手动注册一个 shutdownHook 替代 SpringContextShutdownHook 。在 shutdownHook 中主动下线后休眠 15s ,再执行 context.close()关闭服务

服务关闭是 kill pid 。 会触发自定义 shutdownHook 的关闭逻辑,保证关闭后有 15s 的缓冲期来处理请求。
potatowish
2023-07-03 13:02:13 +08:00
这个应该是 eureka 本地缓存导致的,客户端是通过定时任务去拉取最新的服务列表,服务端实例下线时,本地缓存还没来得及刷新。在重试逻辑中触发一下这个任务就行
pvcxy18
2023-07-03 13:46:47 +08:00
@yisheyuanzhang 这个方案我在网上有看到过,不过似乎有点问题。可以贴代码让我参考一下吗?
pvcxy18
2023-07-03 13:51:41 +08:00
@potatowish 修改 eureka 的负载均衡策略,快速响应服务下线动作吗?请问有相关的参考配置吗?
pvcxy18
2023-07-03 13:52:53 +08:00
@mmdsun 这个…我们没有专门的网关服务的,应用间通过微服务名直接调用,走 eureka 的
mmdsun
2023-07-03 14:14:47 +08:00
@pvcxy18 spring loadbalancer 和以前的 Netflix 的 Ribbon 都支持切换到下一实例的重试的配置。
应用间通过微服务名直接调用,这种应该也有用到客户端负载均衡的组件吧。
simonlu9
2023-07-03 14:50:51 +08:00
erurka 服务端禁止一二级缓存,erurka 客户端请求服务中心时间缩短,erurka 服务端禁止保护模式
twogoods
2023-07-03 15:33:58 +08:00
最佳实践应该就是类似 2 楼说的让实例多留一会儿,多处理一个缓存刷新时间内的请求,如果应用运行在 k8s 上配个 prestop 很方便
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
2023-07-03 23:44:16 +08:00
我们是微服务之间基于 feign 调用,集成了 ribbon,服务下线,jekins 会调用接口,里面会广播一条消息给其他服务,其他服务消费到消息后,会清空本地的 ribbon 缓存,重新拉 eureka 的配置信息
potatowish
2023-07-04 13:01:47 +08:00
2L 这个方案应该是让实例多留一会儿,继续处理请求,直到下一个缓存更新周期后,本地缓存更新到新的上线实例。
这基本可以解决调用到已经下线的实例出现大量异常的问题。但缺点就是需要调用方配合做这样的改造,还有上面提到的在 loadbalancer 上重试下一个实例也是如此。

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

如果你的业务要求更高,要让新的请求尽快切到新的实例,那么在应用层面,可以在 shutdownhook 中发送一个实例下线的消息,调用方收到后立刻拉取最新实例列表。这个是结合了 2L 和 11L 的方案。但缺点就是需要调用方配合做这样的改造,还有上面提到的在 loadbalancer 上重试下一个实例也是如此。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/953566

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX