【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: 8088
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus,monitoring,deregister
  endpoint:
    health:
      show-details: always
    probes:
      enabled: true
  health:
    livenessstate:
      enabled: true
    readinessstate:
      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: 60s
  cloud:
  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
        @Override
        public 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、提升用户体验:优雅上下线可以确保用户在使用服务时不会遇到任何中断或错误,从而提高用户的使用体验和满意度。

本文作者:

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

指导人:

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ApiHug 官方

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | MarketplaceApiHug-H…

java版数据结构:二叉树

引言&#xff1a;什么是树 树是一种非线性的数据结构&#xff0c;由节点组成&#xff0c;节点之间以边连接。树结构中最重要的特点是&#xff0c;每个节点可以有多个子节点&#xff0c;但每个节点只有一个父节点&#xff08;除了根节点&#xff09;。树结构中没有环路&#xff…

水雨情监测系统—实时监测水位信息

TH-SW3水雨情监测系统是一种专门用于实时监测和收集水文气象数据的自动化系统。它能够实时获取区域内降雨和水情数据&#xff0c;并将其存储到数据库中进行分析处理&#xff0c;从而为防汛指挥人员提供及时准确的信息服务。 水雨情监测系统的主要功能包括实时监测水位、流速、流…

第十一届蓝桥杯大赛软件类决赛 Java A 组

文章目录 发现宝藏【考生须知】试题 A: 合数个数试题 B : 含 2 天数试题 C: 本质上升序列试题 D: 迨尺天涯试题 E: 玩具蛇试题 F: 游园安排试题 G: 画廊试题 H: 奇偶覆盖试题 I: 补给试题 J: 蓝跳跳 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

基于C++基础知识的指针

一、变量与指针 在C中&#xff0c;变量是用来存储数据的一个标识符&#xff0c;而指针是一个变量&#xff0c;该变量存储的是另一个变量的地址。 变量可以是不同的数据类型&#xff0c;包括整数&#xff08;int&#xff09;、浮点数&#xff08;float&#xff09;、字符&#…

deveco studio 打开官方案例,不显示运行按钮。

就拿官方的search举例好了 git 地址 https://gitee.com/harmonyos/samples/tree/master/ETSUI/Search 使用deveco studio打开Search项目&#xff0c;打开Tools->Device-Manager中的Local Emulator本地模拟器&#xff0c; 此时会发现&#xff0c;运行按钮是灰色的&#xff0…

OVZ虚拟化:解锁高性能的虚拟化利器

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 OVZ虚拟化&#xff1a;解锁高性能的虚拟化利器 前言OVZ虚拟化简介OVZ虚拟化的优势OVZ虚拟化的应用场景OVZ虚拟化的部署与管理 前言 在当今快节奏的数字时代&#xff0c;虚拟化技术是推动云计算和容器…

通用人工智能将如何重塑未来

通用人工智能(AGI)是一种人工智能&#xff0c;具有与人类一样的获取知识、应用知识解决问题和理解能力。与专门处理受限任务的狭义人工智能系统不同&#xff0c;AGI寻求发展先进的认知技能&#xff0c;以促进在不同情况下完成复杂任务。AGI是一种人工智能&#xff0c;试图模仿人…

下载源代码并交叉编译riscv FreeBSD系统和内核

RISCV系统曾经让人神秘到无法接触&#xff0c;交叉编译更是只有耳闻&#xff0c;现在随着RISCV的普及&#xff0c;它们神秘的面纱已经被慢慢揭开。 交叉编译作为RISCV系统中的一个重要环节&#xff0c;也随着RISCV的普及而变得更加容易理解和操作。交叉编译允许开发者在一个平…

部署达梦数据库主从配置详细操作DM8

服务器配置 主库 192.168.81.128 实例名 dm-1 从库 192.168.81.129 实例名 dm-2 以下安装部署主从服务器都操作 关闭防火墙 systemctl stop firewalld && systemctl disable firewalld 注意安装前必须创建 dmdba 用户&#xff0c;禁止使用 root 用户安装数据库。…

下载element-ui报错

此错误表示尝试从npm注册表下载“resize observer polyfill”包时超时。这可能是由于网络连接问题或npm注册表服务器的问题。 要解决此问题&#xff0c;您可以尝试以下步骤&#xff1a; 1.重试npm install命令&#xff1a;有时&#xff0c;网络问题会导致临时超时。再次运行npm…

BGP基本配置练习

要求&#xff1a;通过使用BGP来实现所有设备的环回都能ping通 实验的思路 完成所有路由器的IGP配置 使用直连接口建立EBGP对等体关系 使用环回接口建立IBGP对等体关系 使用connect-interface命令修改IBGP的源IP地址 使用next-hop-local命令修改路由传递的下一…

Funakoshi — LipiDye Ⅱ脂滴活细胞成像试剂

Funakoshi LipiDye II是一款适用于长时间活细胞成像以观察动态脂滴&#xff08;LDS&#xff09;合成、移动或降解的绿色荧光染料&#xff1b;是LipiDye&#xff08;货号&#xff1a;FDV-0010&#xff09;的升级版&#xff0c;同时具备超强的光稳定性和高灵敏度等特点。 ➧ 产品…

Cartoon Colections Flower Path 2

高质量的花为Unity游戏引擎优化! 移动优化场景 这款10款3D花卉系列,超过+55种颜色!点击 配有高品质的室内植物和花卉模型。 所有对像都可以在可视化中使用。 - 1024x1024,纹理贴图 - Poly计数:平均8500~125500 tris 下载:​​Unity资源商店链接资源下载链接 效果图:

长难句打卡5.14

This is now a question for Gloria Mackenzie, an 84-year-old widow who recently emerged from her small, tin-roofed house in Florida to collect the biggest undivided lottery jackpot in history. 翻译&#xff1a;这是84岁的孤寡老人歌莉娅 麦肯齐当前所面临的问题…

【正版系统】海外短剧系统功能介绍,前端uniapp+开源。

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、海外短剧系统功能介绍 二、搭建要求 1.系统要求 总结 前言 短剧作为一种快速、紧凑的娱乐形式&#xff0c;正逐渐受到更多海外观众的喜爱。这种需求增长为…

移动端自动化测试工具 Appium 之持续集成

文章目录 一、背景二、前置条件三、代码部分1、pom.xml文件配置2、main入口代码 四、Jenkins 部分1、下载Jenkins2、安装插件3、job配置4、选择构建 五、工程目录六、报告示例七、总结 一、背景 持续集成是老生话谈的事情&#xff0c;用的好不好&#xff0c;看自己公司与使用场…

【链路层和局域网】

文章目录 链路层和局域网网络节点的连接方式数据链路层和局域网链路层导论链路层&#xff1a;上下文链路层服务链路层在哪里实现&#xff1f;适配器通信错误检测奇偶校验校验和&#xff1a;CRC&#xff08;循环冗余校验&#xff09;多点访问链路和协议多路访问协议MAC&#xff…

OpenNJet:引领下一代云原生应用引擎

文章目录 一、前言二、什么是OpenNJet 应用引擎三、OpenNJet的优势3.1 性能无损动态配置3.2 灵活的CoPilot框架3.3 支持HTTP/33.4 支持国密3.5 企业级应用3.6 高效安全 四、centos 安装4.1 生成njet.repo4.2 更新yum 缓存4.3 安装 njet 或 njet-otel 五、OpenNJet配置与部署5.1…

【Nginx <一>⭐️】Nginx 的初步了解以及安装使用

目录 &#x1f44b;前言 &#x1f440;一、 Nginx 介绍 &#x1f331;二、 安装使用 &#x1f49e;️ 三、 总结 &#x1f4eb;四、 章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;前段时间主要在学习 Elasticsearch 相关的知识&#xff0c;花了两周的时间吧&#x…