【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?微服務優雅上下線設計與實踐

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.pswp.cn/web/11697.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/11697.shtml
英文地址,請注明出處:http://en.pswp.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;只能用…