当前位置: 首页 > 科技观察

SpringCloud应用无损离线实践

时间:2023-03-22 15:14:25 科技观察

1.实践背景现在Java主流的微服务技术栈无疑是SpringCloud,这也是经销商技术部微服务实践使用的技术栈。注册中心采用公司技术部的nacos。在SpringCloud的实践中,大家普遍遇到的问题就是应用默认无法无损下线,需要更多的辅助措施才能达到无损下线的效果。本文主要分享我们团队解决应用无损下线的一些实践。2.离线有损。有损下线是指在应用下线过程中,部分请求没有得到正确处理,出现异常请求,影响应用的可用性,影响用户的使用。有损离线原因分析:丢失原因一:springboot实例默认收到停止信号TERM后,立即停止服务。如下图所示,当springboot实例收到TERM信号并立即关闭时,很有可能请求队列中还有请求,还有一些请求正在处理中。如果立即关闭,这些请求将丢失。解决方法:springboot2.3及以上版本提供了优雅关机(GracefulShutdown)配置。如下:server:shutdown:graceful配置优雅关闭后,实例的关闭流程如下图所示:springboot实例收到TERM信号后,不会立即关闭应用,而是进入优雅处理阶段.这个宽限时间的长度是可以配置的,默认是30秒。而springboot在网络层拒绝新请求的进入,它会等待队列中的请求和正在处理的请求被处理完或者优雅时间耗尽后才会关闭应用。这样,只要为应用配置一个合适的优雅关闭周期,就可以避免这种请求丢失的情况。丢失原因2:在默认使用注册中心的情况下,服务下线状态无法实时通知调用方。在注册中心引入微服务的场景下,provider(服务提供者)、registry(注册中心)、consumer(服务调用者)通常是这样调用服务的:服务下线时的时序图如下:当provider收到TERM信号时,进入Gracefulshutdown阶段,也就是图中的时间点1,provider在网络层将不再接收新的请求,然后consumer会停止向provider发送请求,直到时间point4.所以丢失的时间=时间点4-时间点1。3.方案一:provider下线后会第一时间通知consumer。Ribbon默认使用轮询方式拉取服务列表,默认时间间隔为30秒,也就是说时间点3-时间点2=30秒(最长)一个服务,如果30秒不是available,情况相当糟糕,有两种方法可以改善,一种是将轮询间隔缩短到5秒左右;另一种是实现注册中心的事件推送方式,通知消费者更新服务列表,尽可能缩短丢失时间。但这只能治标不治本,因为虽然丢包时间缩短了,但还是有几秒钟的丢包,每次上线都需要丢包请求还是不能接受的。解决方案二:在提供者关闭前通知消费者下线。如果可以手动干预下线过程,我们大概会这样做:先把要下线的provider实例从负载中移除,确保下线实例不再有流量,然后再真正执行下线命令。这样就不会有流量损失。只需将此过程自动化即可完美解决。幸运的是,k8s提供了preStop配置。在k8s平台上,preStop和TERM信号的关系如下:先执行preStop,然后k8s会向pod发送TERM信号,k8s会设置一个宽限期让pod停止。宽限期会被强制终止,宽限期从preStop调用开始计算。如果采用这种方案,服务下线的时序图如下:可以看出,消费者不再向提供者发送流量后,提供者开始执行关闭,这样就不会有损失。4、实现步骤1)为了缩短轮询间隔,在springboot应用中添加如下配置:ribbon:ServerListRefreshInterval:5000#服务列表刷新间隔2)在springboot应用中添加preStophook,展开springbootactuator节点。具体代码如下。其中sleepSeconds可以根据项目配置。3)在云平台配置preStop4)安全加固由于prestophook接口是暴露的,如果服务直接暴露在公网,可能会被恶意扫描调用,所以需要对接口进行安全加固.我们在prestophook接口中添加token参数,例如:prestop-hook-api?token=xxxxxxxxxxxxxxx每个项目可以约定不同的token值,并对token值进行验证。token值不正确的访问会被拒绝,这样恶意扫描会更容易无法访问prestophook接口。我们将代码实现封装在harmless-starter中,并集成到项目模板中,用户只需要配置sleepSeconds即可。5.总结本文主要记录经销商技术部门在保证SpringCloud应用下线无损方面的一些实践探索和总结。并对SpringCloud服务下线造成的损失原因进行了全面的分析,并给出了最终的解决方案和实现。希望这也能帮助其他人。