eureka 下有 1 个 A 服务节点,此时发布了第 2 个 A 服务节点(新版本),在发布成功后,会停止第 1 个节点服务。此时在 eureka 图形界面中,会以“DOWN”的形式标注出这个即将被关闭的节点。
但是此时流量仍然可以进入该节点,会导致接口异常。虽然过段时间,等服务彻底停止后,eureka 会调整策略,流量全部走向新节点,但是中间会有一段时间体验很差。
想问下各位公司里通常采用什么方式解决?
ps: 我在网上找到的优雅下线相关案例,往往需要运维层面配合。能否不依赖运维,通过服务本身去实现,即 知道自己的服务要被杀死了,提前通知 eureka 进行主动下线。(这一点我尝试过几次,但效果均不太理想,主动下线有延迟,且优先级很低,通知 eureka 太晚了)
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 |
2
yisheyuanzhang 2023-07-03 12:24:09 +08:00
我是手动注册一个 shutdownHook 替代 SpringContextShutdownHook 。在 shutdownHook 中主动下线后休眠 15s ,再执行 context.close()关闭服务
服务关闭是 kill pid 。 会触发自定义 shutdownHook 的关闭逻辑,保证关闭后有 15s 的缓冲期来处理请求。 |
3
potatowish 2023-07-03 13:02:13 +08:00 via iPhone 1
这个应该是 eureka 本地缓存导致的,客户端是通过定时任务去拉取最新的服务列表,服务端实例下线时,本地缓存还没来得及刷新。在重试逻辑中触发一下这个任务就行
|
4
pvcxy18 OP @yisheyuanzhang 这个方案我在网上有看到过,不过似乎有点问题。可以贴代码让我参考一下吗?
|
5
pvcxy18 OP @potatowish 修改 eureka 的负载均衡策略,快速响应服务下线动作吗?请问有相关的参考配置吗?
|
7
mmdsun 2023-07-03 14:14:47 +08:00
@pvcxy18 spring loadbalancer 和以前的 Netflix 的 Ribbon 都支持切换到下一实例的重试的配置。
应用间通过微服务名直接调用,这种应该也有用到客户端负载均衡的组件吧。 |
8
simonlu9 2023-07-03 14:50:51 +08:00
erurka 服务端禁止一二级缓存,erurka 客户端请求服务中心时间缩短,erurka 服务端禁止保护模式
|
9
twogoods 2023-07-03 15:33:58 +08:00
最佳实践应该就是类似 2 楼说的让实例多留一会儿,多处理一个缓存刷新时间内的请求,如果应用运行在 k8s 上配个 prestop 很方便
|
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); } |
11
liupeng2579793 2023-07-03 23:44:16 +08:00
我们是微服务之间基于 feign 调用,集成了 ribbon,服务下线,jekins 会调用接口,里面会广播一条消息给其他服务,其他服务消费到消息后,会清空本地的 ribbon 缓存,重新拉 eureka 的配置信息
|
12
potatowish 2023-07-04 13:01:47 +08:00 via iPhone
2L 这个方案应该是让实例多留一会儿,继续处理请求,直到下一个缓存更新周期后,本地缓存更新到新的上线实例。
这基本可以解决调用到已经下线的实例出现大量异常的问题。但缺点就是需要调用方配合做这样的改造,还有上面提到的在 loadbalancer 上重试下一个实例也是如此。 如果你的业务要求更高,要让新的请求尽快切到新的实例,那么在应用层面,可以在 shutdownhook 中发送一个实例下线的消息,调用方收到后立刻拉取最新实例列表。这个是结合了 2L 和 11L 的方案。 |
13
potatowish 2023-07-04 13:03:52 +08:00 via iPhone
@potatowish
发错修改 >> 2L 这个方案应该是让实例多留一会儿,继续处理请求,直到下一个缓存更新周期后,本地缓存更新到新的上线实例。这基本可以解决调用到已经下线的实例出现大量异常的问题。 如果你的业务要求更高,要让新的请求尽快切到新的实例,那么在应用层面,可以在 shutdownhook 中发送一个实例下线的消息,调用方收到后立刻拉取最新实例列表。这个是结合了 2L 和 11L 的方案。但缺点就是需要调用方配合做这样的改造,还有上面提到的在 loadbalancer 上重试下一个实例也是如此。 |