【JAVA】BOSS系统发版艺术:构建高效、优雅的微服务部署策略

在现代软件开发领域,微服务架构与容器化部署已迅速成为行业新趋势。微服务架构通过将应用拆分成多个小型、自治的服务单元,每个服务承担某项特定的业务功能。而容器化部署则以其轻量级和高度可移植的特性,为这些微服务的有效打包、分发和运行提供了强大支持。

在这样的环境中,实现微服务的优雅上下线变得至关重要。优雅上下线意味着在进行服务更新、扩展或缩减服务规模时,能够无缝切换,避免或最小化对用户的影响。这种做法不仅保障了系统的高可用性和稳定性,还大幅提升了开发和运维团队的工作效率。

本文将深入探讨如何借助容器化技术,实现微服务的优雅上下线。我们将分享一系列实用的方法和策略,包括滚动升级、就绪检查以及优雅关闭等。通过采用这些策略,您能在进行版本更新、规模调整或故障恢复期间,确保系统的连续稳定运行,从而显著提升整体的系统可靠性和稳定性。

1 项目背景

BOSS物业管理系统(以下简称“BOSS系统”)是碧桂园服务(以下简称“碧服”)体系中的核心主营收费系统,它主要负责管理客户、房屋及车位等基础数据,并支持物业费、合同类、表计类、车位类及临时类费用的全自动化计费。BOSS系统采用微服务架构和容器化部署,拆分成30个不同功能的微服务。这种设计虽然大幅提升了系统的灵活性和可维护性,却也增加了服务发版部署的时长。

此外,在发版过程中,服务可能会出现短暂的中断,或者在服务停止时还有未完成的异步线程的任务,导致业务数据的不完整,进而引发大量的运维工单,增加运维成本的同时也影响了用户体验。

当前,BOSS系统采用了敏捷开发模式,其显著特点之一是小步快跑。这种模式使我们能够以更快的速度推出新功能和优化现有功能,迅速响应用户业务需求的变化。在这种开发模式下,发版效率显得尤为重要。以往,每次部署时长约两小时,再加上发版后的验证回归和测试,整个流程可能需要数小时才能完成。这样漫长的发版流程不仅占用了团队大量的时间和资源,还增加了出错的风险。

2 如何实现高效

2.1 引入发版的checklist

由于BOSS系统的服务拆分的比较细,若全量发版则需要发布30个服务。每次发版不仅包括数据库更改脚本、nacos配置更新及XXL-Job任务调度等内容,还有服务清单和代码迁入的情况。如果在发版前没有进行充分的检查与准备,后续可能需要多次更新服务,极大地增加了整个发版时长。

为了解决这一问题,引入发版checklist显得尤为重要。该checklist能够帮助盘点上线事项,并回顾开发过程中的各个细节。通过checklist,团队可以更有序地执行发版流程,从而提高发版的效率和准确性。

上线checklist包括以下几个关键内容:

1、上线前准备:此阶段需准备数据库脚本、nacos配置、XXL-Job任务以及一些提前编写好的配置文件等;

2、上线步骤:包括更新的SQL、各个模块的更新顺序以及是否依赖公共包等。对于C端应用,需要注意服务端与前端的发布先后顺序;

3、需验证的事项:在每个模块更新完成后,需采取相应的措施来验证其是否正常,例如观察页面、检查日志和监控是否正常等;

4、明确人与时间:checklist应尽可能详细,明确具体的人员和特定时间段的任务安排;

5、评估对用户的影响:在每个步骤完成后,需要评估对用户的影响,并关注相应的内容;

6、提前做好预发回归:预发环境应与生产环境的数据源相通。在预发环境中,可以模拟线上更新的步骤,提前预演一遍。为避免预发环境对线上的影响,可考虑使用白名单控制访问权限,同时注意用户权限的回收,以防止误操作影响线上环境。

2.2 容器升级策略

在容器化部署中,滚动更新允许逐个替换Pod实例以实现零停机的Deployment更新。新创建的Pod将会被调度到可用资源的节点上。

在阿里云k8s中,默认采用滚动升级策略。此策略下“不可用Pod最大数量”和“超出期望的Pod数量”都是25%。然而,当节点资源的内存严重紧张时,日常使用平均内存利用率已经超过80%,并且需要同时更新30个服务,尤其是这些服务配置的内存需求多集中在8至16GB之间,就可能导致发版过程中节点池的内存资源不足以支撑这么多Pod的同时申请,导致容器尝试滚动升级时大量Pod处于pending状态,等待分配资源。

我们通过优化容器升级策略,在不增加节点服务器资源前提下,实现了快速的滚动升级。考虑到常规发布操作安排在非高峰时段,因此可以接受不可用Pod的最大数量控制在25%至80%之间。这种调整显著释放了节点资源,极大地提升了后续的容器滚动升级速率。

2.3 发版汇总

通过最近几次的发版汇总记录进行分析,我们可以发现,初次执行全量发布30个服务的操作耗时约两小时。然而,在引入发版checklist和优化容器升级策略后,第二次进行全量发版的时间大幅缩短至半小时内。目前,全量发版仅需20分钟即可完成,而对于日常的少量服务发版,则仅需10分钟。发版时间的显著缩短,为后续的测试验证工作提供了更加充裕的时间,从而提高了整个发版与验证的效率。

微服务优雅上下线设计与实践

3.1 什么是微服务优雅上下线

微服务优雅上下线的基本原理是指在微服务更新发布过程中确保服务的稳定性和可用性,防止由于服务变更引起的流量中断或错误。

实现微服务的优雅上下线,旨在避免以下问题:

  • 过早的注册服务:服务尚未完全就绪时就注册到了注册中心,开始接受请求,导致业务异常;

  • 过早退出应用程序:服务还在处理请求时,应用程序被强制终止,导致正在进行的请求出现错误。

针对这些问题,我们可以采取以下优化措施:

  • 优雅上线:在服务启动后,等待服务完全就绪后再对外提供服务,或者有一个服务预热过程;

  • 优雅下线:在服务停止前,先从服务注册中心注销,拒绝新的请求,并等待旧的请求处理完毕后再下线服务,从而确保所有请求都能得到妥善处理。

3.2 实现微服务在容器中优雅上下线 

1、优雅上线

在实现微服务的优雅上线过程中,我们可以利用k8s的就绪检查与微服务生命周期对齐,等完成服务注册与准备就绪后,再开始接受外部流量。

就绪检查接口一般包括数据库连接状态、redis连接状态、nacos注册状态及调用预热接口等工作。

我们可使用Spring Boot Actuator提供的健康检查接口/health来做就绪检查:

引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启用liveness和readiness探针

management:server:port: 8088endpoints:web:exposure:include: health,info,metrics,prometheus,monitoring,deregisterendpoint:health:show-details: alwaysprobes:enabled: truehealth:livenessstate:enabled: truereadinessstate:enabled: true

health/readiness接口会严格检查SpringBoot的各项组件服务,比如邮件服务、数据库服务及MQ服务等。当所有组件处于正常状态时,它会返回内容{"status": "UP"},否则返回{"status": "down"}。

2、优雅下线

在实现微服务的优雅下线过程中,我们可以结合使用SpringBoot的优雅停机方案和k8s生命周期管理(停止前处理)来实现服务的优雅退出。

SpringBoot的优雅停机使用方式:

通过配置文件的方式即可开启优雅停机,需要配置server.shutdown属性和宽限期。宽限期会影响到同步请求的超时中断。

# 开启优雅关闭
server:shutdown: graceful
# 配置强制结束时间,不配置的话默认30s
spring:lifecycle:timeout-per-shutdown-phase: 60scloud:loadbalancer:cache:ttl: 10s  

在Spring Cloud LoadBalancer中,为了优化服务调用的性能,减少对服务注册中心的频繁请求,LoadBalancer实现了对服务实例列表的本地缓存。默认设置下,这个缓存的时效为35秒。但是,这一默认缓存过期时间可能会导致在系统上下线过程中出现问题。如果缓存中仍然存储着旧的服务列表,那么这可能会影响到服务的可用性和准确性。

优雅下线接口,这里采用的是手写的方式,还可以用Spring Boot Actuator提供的接口/shutdown端点的方式,但该接口只支持POST的方式。

@Autowired
private NacosAutoServiceRegistration nacosAutoServiceRegistration;@ReadOperation
public String deregister() {Executors.newSingleThreadExecutor().submit(() -> {log.info("Ready to stop service: {}", serviceName);nacosAutoServiceRegistration.stop();log.info("Nacos instance has been de-registered.");});return "{\n" +"    \"status\": \"UP\"\n" +"}";
}

注意:在优雅下线接口中,我们只需要执行退出nacos注册操作即可,无需手动退出spring应用程序。这是因为配置文件已经启用了服务器端的优雅关闭机制。另外,timeout-per-shutdown-phase参数的时间是影响同步请求的超时中断。

容器停止前处理:配置调用优雅退出接口并等待30秒

容器生命周期:

容器终止流程:

1、Pod被删除,状态置为Terminating;

2、将Pod从service的endpoint列表中摘除掉;

3、如果Pod配置了preStop Hook,将会执行(容器停止前处理);

4、发送SIGTERM信号以通知容器进程开始优雅停止;

5、等待容器进程完全停止。如果在terminationGracePeriodSeconds内 (默认30s) 还未完全停止,就发送SIGKILL信号强制杀死进程;

6、容器进程终止,清理Pod资源。

在k8s的容器终止流程中,第五步为容器删除预留了一个最大时间限制,即30秒。如果SpringBoot应用的优雅关闭超时时间和k8s的preStopHooks的总和超过30秒,那么k8s可能会在SpringBoot处理完所有请求之前强制删除容器。

为了避免这种情况,我们可以调整优雅终止的时间。在k8s中,这个时间由terminationGracePeriodSeconds参数控制,其默认值是30s。我们可以根据实际情况调整这个值,但需要确保terminationGracePeriodSeconds的值要大于sleep时间。请注意,terminationGracePeriodSeconds设置的是最大等待时间,并不意味着每次终止都会等待这么长时间。

此外,探索JVM退出的钩子函数(Runtime.addShutdownHook)的使用也是一个很好的实践。通过添加关闭钩子函数,可以实现在程序退出时的关闭资源、优雅退出的功能。这也是SpringBoot优雅退出的原理,ApplicationContext.registerShutdownHook方法是spring框架中的一个方法,用于注册一个JVM关闭的钩子(Shutdown Hook),当JVM关闭时,Spring容器可以优雅地关闭并释放资源。

3、异步线程优雅退出

在实现服务优雅退出过程中,我们遇到了一个挑战:异步线程的优雅退出。由于BOSS系统的业务复杂性,几乎每个服务都使用了异步线程来处理一些耗时操作。然而,在发版期间,如果容器提前退出,那些尚未完成的异步任务可能会被中断,导致业务数据的不完整,进而需要人工介入进行数据修正。

异步线程优雅退出的解决办法:

  • 使用统一的自定义线程池;

  • 配置线程池优雅退出和任务最大结束时间。

@Bean("bossTaskExecutor")
public ThreadPoolTaskExecutor bossTaskExecutor() {log.info("start taskExecutor");ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 配置核心线程数executor.setCorePoolSize(threadPoolCorePoolSize);// 设置最大线程数executor.setMaxPoolSize(threadPoolMaxPoolSize);// 设置队列容量executor.setQueueCapacity(threadPoolQueueCapacity);// 设置线程活跃时间(秒)executor.setKeepAliveSeconds(threadPoolKeepAliveSeconds);// 配置线程池中的线程的名称前缀executor.setThreadNamePrefix("async-service-");// 设置拒绝策略// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);// 等待所有任务结束的最长时间executor.setAwaitTerminationSeconds(threadAwaitTerminationSeconds);// 执行初始化executor.initialize();log.info("创建一个线程池 threadPoolCorePoolSize is [" + threadPoolCorePoolSize + "] threadPoolMaxPoolSize is ["] threadPoolKeepAliveSeconds is [" + threadPoolKeepAliveSeconds + "].");return executor;
}

 关键配置:

等待所有任务结束后再关闭线程池:

executor.setWaitForTasksToCompleteOnShutdown(true)

等待所有任务结束的最长时间:

executor.setAwaitTerminationSeconds(awaitTerminationSeconds)

需要注意的是:要保证异步线程的任务处理完才退出,容器端的

terminationGracePeriodSeconds时间要大于等于awaitTerminationSeconds,这样才能够确保异步线程任务的优雅退出。此外,上述的timeout-per-shutdown-phase时间和异步线程的任务最长时间没冲突。

4、测试结果

为了测试异步线程在发版中是否被中断,我们可以编写一个测试接口来模拟这种情况:

@Autowired
@Qualifier("bossTaskExecutor")
private ThreadPoolTaskExecutor executorService;@ApiOperation(value = "测试异步耗时任务", notes = "测试异步耗时任务")
@GetMapping("/testAsyncTask")
public Response testAsyncTask() throws InterruptedException {executorService.execute(new Runnable() {@SneakyThrows@Overridepublic void run() {for (int i=0;i<=200;i++){Thread.sleep(1000);log.info("testAsyncTask-Thread:"+i);}}});for (int i=0;i<=120;i++){Thread.sleep(1000);log.info("testAsyncTask:"+i);}return Response.ok("200");
}

我们在容器开始部署时调用接口,并通过打印的日志可以观察到异步线程能够处理完,日志打印到了200。然而,在观察容器滚动升级的过程中,我们会发现有一个Pod在Terminating的状态停留了较久时间才退出,这是因为它正在等待异步线程的任务处理完再销毁容器。 

4 总结

综上,通过Spring Boot Actuator的优雅配置和健康检查接口,以及配合k8s的就绪检查策略,我们实现了优雅上线。对于优雅下线,我们通过SpringBoot的优雅停机配置和自定义的优雅下线接口,再配合k8s生命周期中的停止前处理,实现微服务的优雅退出。此外,我们还采用了统一的自定义线程池,并配置了线程池优雅退出机制和任务最大结束时间,以确保发版期间能够妥善处理所有异步任务。

通过微服务优雅上下线实践,我们取得了以下成果:

1、最小化服务中断:通过优雅上下线,可以最小化服务中断的时间和影响范围,从而确保服务的可用性和稳定性;

2、数据一致性和完整性:优雅下线可以确保正在处理的请求能够完成,避免数据丢失和请求失败;

3、提升用户体验:优雅上下线可以确保用户在使用服务时不会遇到任何中断或错误,从而提高用户的使用体验和满意度。

本文作者:

蔡冠怡:碧桂园服务后端开发高级工程师

指导人:

余俭:碧桂园服务技术总监

岳黎明:碧桂园服务架构师

黄志鸿:碧桂园服务运维高级工程师

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/11697.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习 - 不同分类模型的比较

一、模型训练 本案例中&#xff0c;我们将通过四种不同的模型来预测泰坦尼克号乘客的生存情况。 一下是训练的具体步骤。 加载数据 从seaborn库中加载目标数据。该数据集包括多个特征&#xff0c;如 PassengerId, Pclass, Name, Sex, Age, SibSp, Parch, Ticket, Fare, Cabi…

科技查新中的工法查新点如何确立与提炼?案例讲解!

按《工程建设工法管理办法》( 建 质&#xff3b;2014&#xff3d;103 号) &#xff0c;工法&#xff0c;是指以工程为对象&#xff0c;以工艺为核心&#xff0c;运用系 统工程原理&#xff0c;把先进技术和科学管理结合起来&#xff0c;经过一定工程实践形成的综合配套的施工方…

探索美国动态IP池:技术赋能下的网络安全新篇章

在数字化飞速发展的今天&#xff0c;网络安全成为了各行各业关注的焦点。特别是在跨国业务中&#xff0c;如何保障数据的安全传输和合规性成为了企业面临的重要挑战。美国动态IP池作为一种新兴的网络技术&#xff0c;正逐渐走进人们的视野&#xff0c;为网络安全提供新的解决方…

黑马甄选离线数仓项目day02(数据采集)

datax介绍 官网&#xff1a; https://github.com/alibaba/DataX/blob/master/introduction.md DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。 DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre…

Java中List接口中方法的使用(初学者指南)

Java中List接口中方法的使用&#xff08;初学者指南&#xff09; 在Java中&#xff0c;List接口是Collection接口的子接口&#xff0c;它表示一个有序的集合&#xff0c;其中的元素都可以重复。List接口提供了许多额外的方法&#xff0c;用于对元素进行插入、删除、查询等操作…

计算机Java项目|Springboot学生读书笔记共享

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

C++通过json文件配置参数

一、安装nlohmann json nlohmann json&#xff1a;安装_nlohmann安装-CSDN博客 依次执行下面指令&#xff1a; git clone https://gitee.com/cuihongxi/mov_from_github.gitcd json-developmkdir buildcd buildcmake ..makesudo make install 二、安装完成后使用 #include…

华为设备display查看命令

display version //查看版本信息 display current-configuration //查看配置详情 display this //查看当前视图有效配置 display ip routing-table //查看路由表 display ip routing-table 192.168.3.1 //查看去往3.1的路由 display ip interface brief //查看接口下ip信息 dis…

想跨境出海?云手机提供了一种可能性

全球化时代&#xff0c;越来越多的中国电商开始将目光投向了海外市场。这并不是偶然&#xff0c;而是他们在长期的市场运营中&#xff0c;看到了出海的必要性和潜在的机会。 中国的电商市场无疑是全球最大也最发达的之一。然而&#xff0c;随着市场的不断发展和竞争的日益加剧…

visual studio2022 JNI极简开发流程

文章目录 1 创建java类2 生成JNI头文件3 使用visual studio2022创建DLL项目3.1 选择模板中&#xff08;Windows桌面向导&#xff09;3.2 为项目命名3.3 选择应用程序类型为动态链接库3.4 项目概览 4 导入需要的头文件4.1 导入需要的头文件4.2 修改头文件 5 编写C实现6 生成dll文…

服务器3389端口,服务器3389端口风险提示的应对措施

3389端口是Windows操作系统中远程桌面协议&#xff08;RDP&#xff09;的默认端口。一旦该端口被恶意攻击者利用&#xff0c;可能会导致未经授权的远程访问和数据泄露等严重安全问题。 针对此风险&#xff0c;强烈建议您采取以下措施&#xff1a; 1. 修改默认端口&#xff1a;…

Java面试之抽象类和接口

Java的一个重要特性就是抽象&#xff0c;抽象是指将具体的事物抽象成更一般化、更抽象化的概念或模型。在Java中&#xff0c;抽象可以通过抽象类和接口来实现&#xff0c;它们让你能够定义一些方法但不提供具体实现&#xff0c;从而让子类去实现具体细节。 一、抽象类&#xf…

springboot3 集成spring-authorization-server (一 基础篇)

官方文档 Spring Authorization Server 环境介绍 java&#xff1a;17 SpringBoot&#xff1a;3.2.0 SpringCloud&#xff1a;2023.0.0 引入maven配置 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

识别AI论文生成内容,降低论文高AI率

AI写作工具能帮我们在短时间内高效生成一篇毕业论文、开通报告、文献综述、任务书、调研报告、期刊论文、课程论文等等&#xff0c;导致许多人开始使用AI写作工具作为撰写学术论文的辅助手段。而学术界为了杜绝此行为&#xff0c;开始使用AIGC检测系统来判断文章是由AI生成还是…

解锁商业AI,赋能新质生产力发展——思爱普中国峰会探展全纪录

ITValue 钛媒体独家探秘思爱普中国峰会&#xff0c;带你深刻感受SAP助力企业利用以商业AI为代表的数字化技术&#xff0c;实现质的飞跃&#xff0c;通过全数据、全球化、全绿色赋能新型中国企业发展新质生产力。 首发&#xff5c;钛媒体APP ITValue 5月10日&#xff0c;一年一度…

基于NTP服务器获取网络时间的实现

文章目录 1 NTP1.1 简介1.2 包结构1.3 UNIX 时间戳和NTP时间戳 2 代码实现2.1 实现步骤2.2 完整代码 3 结果 在某些场景下&#xff0c;单片机需要通过网络获取准确的时间进行数据同步&#xff0c;例如日志记录、定时任务等。然而&#xff0c;单片机本身无法直接获得准确的标准时…

Vue的学习 —— <vue指令>

目录 前言 正文 内容渲染指令 内容渲染指令的使用方法 v-text v-html 属性绑定指令 双向数据绑定指令 事件绑定指令 条件渲染指令 循环列表渲染指令 侦听器 前言 在完成Vue开发环境的搭建后&#xff0c;若想将Vue应用于实际项目&#xff0c;首要任务是学习Vue的基…

ORA-00932: inconsistent datatypes: expected - got CLOB的分析解决方案

最近在项目中遇到查询数据时报ORA-00932: inconsistent datatypes: expected - got CLOB错误&#xff0c;这个错误很明显是由于查询时类型的不匹配造成的。 问题分析&#xff1a; 一、检查你的查询的实体的类型是否于数据库的保持一致&#xff0c;如果不一致&#xff0c;那么需…

333_C++_编写一个go函数每次从文件中读取固定大小数据,且go作为回调,传递给其他函数中,多次调用,完成逐块传输数据

(core工程文件) tick_transfer_all_t类是一个用于异步传输数据的辅助类,它在某个异步操作完成后将_tick的值设置为0,并返回传输的结果 namespace hl {namespace http{namespace __detail{class tick_transfer_all_t{boost::shared_ptr<unsigned long long> _tick

MySQL 查询库 和 表 占用空间大小的 语句

查看mysql 数据库的大小 SELECT table_schema AS 数据库名称, ROUND(SUM(data_length index_length) / 1024 / 1024, 2) AS 数据库大小(MB) FROM information_schema.tables GROUP BY table_schema;查询数据库中表的 数据量&#xff08;这个方法 有缓存延迟&#xff0c;只能用…