研发协同平台持续交付之代理服务实践
源寶導(dǎo)讀:插件系統(tǒng)大大提高了系統(tǒng)的擴(kuò)展性,有利于模塊化開發(fā)。系統(tǒng)發(fā)布后,當(dāng)我們需要對(duì)系統(tǒng)進(jìn)行擴(kuò)充,可以再不編譯的情況下更新系統(tǒng)的插件即可。基于熱拔插的軟件系統(tǒng)提高了持續(xù)交付能力,在添加新特性的同時(shí)保持核心結(jié)構(gòu)穩(wěn)定。本文將介紹研發(fā)協(xié)同平臺(tái)代理服務(wù)在插件化設(shè)計(jì)方面的技術(shù)實(shí)踐。
一、背景
? ? 插件系統(tǒng)大大提高了系統(tǒng)的擴(kuò)展性,有利于模塊化開發(fā)。系統(tǒng)發(fā)布后,當(dāng)我們需要對(duì)系統(tǒng)進(jìn)行擴(kuò)充,可以再不編譯的情況下更新系統(tǒng)的插件即可。基于熱拔插的軟件系統(tǒng)提高了持續(xù)交付能力,在添加新特性的同時(shí)保持核心結(jié)構(gòu)穩(wěn)定。
? ? ERP是To B產(chǎn)品, To B產(chǎn)品的持續(xù)交付和To C產(chǎn)品的持續(xù)交付是有很大不同的。To B產(chǎn)品需要交付給成千上萬家客戶,每家客戶的產(chǎn)品和環(huán)境都不一樣,要保證ERP產(chǎn)品能持續(xù)的、穩(wěn)定的交付給成千上萬家客戶,在客戶端需要一個(gè)代理服務(wù)來協(xié)同完成。由此,ERP代理服務(wù)應(yīng)運(yùn)而生。
二、設(shè)計(jì)架構(gòu)
? ? ERP 代理服務(wù)在客戶端負(fù)責(zé)產(chǎn)品的持續(xù)交付承擔(dān)在重要的角色,ERP 產(chǎn)品每次的最終交付到客戶,它需要在客戶端完成很多重要的工作:更新包到客戶機(jī) IIS、收集客戶機(jī)更新日志、服務(wù)器運(yùn)行狀態(tài)、同時(shí)由于客戶機(jī)網(wǎng)絡(luò)環(huán)境安全限制時(shí)也承擔(dān)著信息的中轉(zhuǎn)服務(wù),為了日后公司產(chǎn)品的多樣化、環(huán)境的多變性、產(chǎn)品的迅速更新,我們需要隨時(shí)做出的相應(yīng)響應(yīng),并能迅速的完成支撐,因此 ERP 代理服務(wù)在保證自身穩(wěn)定的同時(shí),需要快速的更新自己的產(chǎn)品所承擔(dān)的功能職責(zé)。
? ? ERP代理服務(wù)的核心需求如下:
自更新。
自恢復(fù)。
灰度更新。
業(yè)務(wù)邏輯可升級(jí)。
業(yè)務(wù)邏輯可靈活擴(kuò)展。
較好的容錯(cuò)機(jī)制,在網(wǎng)絡(luò)不穩(wěn)定或其他其他因素的影響下,也能穩(wěn)定持續(xù)運(yùn)行。
? ? 基于以上需求,我們新設(shè)計(jì)了 ERP 代理服務(wù)的架構(gòu)方案,我們將原有 ERP 代理服務(wù)改造為熱拔插的插件支撐方案。
三、框架設(shè)計(jì)
? ? 代理服務(wù)通過支持三種類型的插件熱拔插,實(shí)現(xiàn)多種多樣的需求:
類庫插件:實(shí)現(xiàn)服務(wù)端下發(fā)命令執(zhí)行業(yè)務(wù)處理。
Web 插件:可靈活的提供 API 或者視圖在頁面中呈現(xiàn)。
Console 插件:通過外部啟動(dòng) exe 程序的方式,守護(hù)進(jìn)程或者其他特殊需求。
? ? 插件功能高內(nèi)聚,與框架低耦合,開發(fā)人員根據(jù)規(guī)范,開發(fā)完成并進(jìn)行單元測(cè)試通過后,打包并安裝到宿主中,即可使用。
3.1、插件加載模式
? ? 通過向更新服務(wù)獲取用戶可用的插件集合,動(dòng)態(tài)新增、升級(jí)、卸載插件,通過在內(nèi)存中加載 DLL 的方式熱拔插插件,能有效的更新客戶端服務(wù)器上的代理服務(wù)功能,靜默升級(jí)插件后續(xù)將讓我們的服務(wù)有很高的拓展性。
3.2、目前規(guī)劃的插件
? ? 針對(duì)現(xiàn)有的業(yè)務(wù)場(chǎng)景我們規(guī)劃了多個(gè)插件,并用于實(shí)現(xiàn)不同的功能模塊:
更新包插件:用于更新ERP產(chǎn)品到客戶服務(wù)器的站點(diǎn)中。
更新服務(wù)API插件:用于提供產(chǎn)品注冊(cè)等接口給ERP站點(diǎn)調(diào)用,將相應(yīng)信息上報(bào)至服務(wù)端。
守護(hù)插件:守護(hù)代理服務(wù)運(yùn)行狀況,負(fù)責(zé)重啟、更新代理服務(wù)。
? ? 其中部分插件為熱加載方式,我們可快速的進(jìn)行插件迭代開發(fā),并發(fā)布上線(同時(shí)支持灰度發(fā)布),能快速的應(yīng)對(duì)產(chǎn)品需求。
四、實(shí)現(xiàn)方案
? ??我們從現(xiàn)有的 ERP 代理服務(wù),以及 ERP 的交付特性,來考慮我們系統(tǒng)的可拓展性、可維護(hù)性,同時(shí)在一定程度上引入更新的技術(shù)棧來。我們從三個(gè)部分實(shí)現(xiàn)我們的插件系統(tǒng):
主程序開發(fā)。
公共接口的基礎(chǔ)插件類庫開發(fā)。
各類插件開發(fā)。
? ? 以上,我們需要實(shí)現(xiàn)各部分之間的接口規(guī)范。
4.1、技術(shù)調(diào)研
? ? 我們調(diào)研了最新的 ASP.NET CORE 3.1(https://docs.microsoft.com/zh-cn/aspnet/core/?view=aspnetcore-3.1) 技術(shù)棧,它能很好的支持我們的熱拔插的系統(tǒng)設(shè)計(jì)方案,微軟也給出了簡單的示例,以下是提供一些關(guān)鍵技術(shù)文檔進(jìn)行參考:
ASP.NET CORE 3.1 熱拔插插件的實(shí)現(xiàn)方案。
(https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability?)
ASP.NET CORE 支持獨(dú)立部署方式避免系統(tǒng)需要安裝.NETCORE SDK。
(https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/?view=aspnetcore-3.1)
引入 NLog 日志組件。
(https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3)
? ? 在以上技術(shù)棧的基礎(chǔ)上,我們對(duì)基礎(chǔ)知識(shí)加以深入研究,引入項(xiàng)目的過程中不斷思考優(yōu)化項(xiàng)目結(jié)構(gòu)。
4.2、實(shí)現(xiàn)解析
? ? 我們改變?cè)蠩RP代理服務(wù)的插件方案,采用 AssemblyLoadContext 來實(shí)現(xiàn)熱拔插插件方案,同時(shí)重新調(diào)整主程序框架根據(jù)支持多種的插件的的加載,將公共服務(wù)接口提升到基礎(chǔ)插件類庫中以便繼承插件可以通過依賴注入的方式使用我們的公共接口。
4.2.1、封裝插件加載器SDK
功能特性
支持將dll以文件流的方式加載(可卸載)。
支持將占用dll文件的方式加載(可卸載)。
支持插件文件變更卸載插件并自動(dòng)重新加載。
設(shè)計(jì)目錄:
核心代碼:
// 程序集加載 protected override Assembly Load(AssemblyName assemblyName) {var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);if (assemblyPath == null){var localFile = Path.Combine(Path.GetDirectoryName(MainAssemblyToLoadPath), assemblyName.Name + ".dll");if (File.Exists(localFile)){assemblyPath = localFile;}else{return null;}}// 內(nèi)存方式加載if (IsLoadInMemory){using var file = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);return LoadFromStream(file);}else{return LoadFromAssemblyPath(assemblyPath);} } /// <summary>/// 卸載插件/// </summary>[MethodImpl(MethodImplOptions.NoInlining)]public bool Unload(){ if (!_context.IsCollectible) { Console.WriteLine($"AssemblyLoadContext 不是可回收的"); return false; } if (!_context.IsLoadInMemory) { var mainAssemblyToLoadPath = _context.MainAssemblyToLoadPath; // 判斷資源是否存在引用 var weakRef = new WeakReference(_context, trackResurrection: true); _context.Unload(); _context = null; // 嘗試至多20次垃圾回收,將資源引用釋放 for (int i = 0; weakRef.IsAlive && (i < 20); i++) { GC.Collect(); GC.WaitForPendingFinalizers(); // 睡眠500毫秒,確保GC回收完畢,資源引用釋放 Thread.Sleep(500); } if (weakRef.IsAlive) { Console.WriteLine($"程序集({mainAssemblyToLoadPath})回收失敗,請(qǐng)確認(rèn)程序集未被使用"); } return !weakRef.IsAlive; } else { _context.Unload(); _context = null; return true; }}4.2.2、 主程序功能模塊解析
功能特性及結(jié)構(gòu)分層:
全局異常過濾。
計(jì)劃任務(wù):命令定時(shí)執(zhí)行任務(wù)、定時(shí)上報(bào)任務(wù)、定時(shí)更新插件任務(wù)。
插件加載器:類庫插件加載器、Web插件加載器、控制臺(tái)(exe)類插件加載器。
中間件:請(qǐng)求記錄日志中間件。
應(yīng)用程序全局配置。
服務(wù)層:命令服務(wù)、插件緩存、插件服務(wù)、上報(bào)服務(wù)。
目錄結(jié)構(gòu):
應(yīng)用設(shè)計(jì)結(jié)構(gòu):
4.2.2.1、 啟動(dòng)說明
? ? ERP代理服務(wù)啟動(dòng)前會(huì)注冊(cè)所需服務(wù):
注冊(cè)配置中心服務(wù)。
注冊(cè)日志服務(wù)。
注冊(cè)全局異常處理。
注冊(cè)更新服務(wù)。
注冊(cè)Web插件View視圖引擎目錄。
注冊(cè)各類型插件加載器(類庫插件、Web插件、控制臺(tái)類插件)。
注冊(cè)插件緩存服務(wù)、命令服務(wù)、上報(bào)服務(wù)、插件管理服務(wù)。
注冊(cè)上報(bào)調(diào)度任務(wù)、命令執(zhí)行調(diào)度任務(wù)、更新及加載插件調(diào)度任務(wù)。
? ? 并根據(jù)配置中心配置的服務(wù)啟動(dòng)地址啟動(dòng),改地址用于ERP產(chǎn)品調(diào)用代理服務(wù)上報(bào)產(chǎn)品信息等接口調(diào)用。
4.2.2.2、API接口服務(wù)說明
? ? 目前由更新服務(wù)API插件提供給ERP產(chǎn)品將相應(yīng)信息提交至更新服務(wù),以便后續(xù)我們能很好的利用信息完成客戶的產(chǎn)品交付:
注冊(cè)產(chǎn)品信息。
獲取服務(wù)器狀態(tài)信息。
獲取更新包更新狀態(tài)。
獲取更新頁面地址。
獲取更新服務(wù)狀態(tài)。
? ? 同時(shí)由于該插件可以后期升級(jí)并熱加載,我們可以根據(jù)需求快速迭代插件,為ERP產(chǎn)品提供更多的所需服務(wù)。
4.2.2.3、調(diào)度任務(wù)說明
? ? 應(yīng)用啟動(dòng)后,通過異步方式啟動(dòng)調(diào)度任務(wù),在調(diào)度任務(wù)中完成各自的職責(zé)。
命令任務(wù):通常用于執(zhí)行發(fā)布通知更新包任務(wù)或即時(shí)更新插件。
上報(bào)任務(wù):用于上報(bào)客戶相關(guān)信息、插件運(yùn)行狀態(tài)等信息。
更新插件任務(wù):用于異步加載插件,不影響主程序運(yùn)行,并定期同步客戶端插件與服務(wù)端插件版本信息。
? ? 通過將核心任務(wù)使用調(diào)度任務(wù)異步運(yùn)行的方式,避免影響主程序。
五、寫在最后
? ? 目前我們的項(xiàng)目已經(jīng)進(jìn)入尾聲,我們已基本在運(yùn)用這套方案實(shí)現(xiàn)了原有ERP代理服務(wù)的功能,同時(shí)我們更加友好的支持灰度插件發(fā)布上線。未來我們還會(huì)加入日志主動(dòng)或被動(dòng)上報(bào)、客戶服務(wù)器監(jiān)控,ERP站點(diǎn)運(yùn)行監(jiān)控等功能插件來幫助我們產(chǎn)品發(fā)展的越來越好。
? ? 最后,我們?cè)趯?shí)踐中也遇到了一些問題:
當(dāng)使用 XmlSerializer 操作相關(guān)資源時(shí),由于改對(duì)象目前還不支持可卸載,暫不能用于可卸載插件中 ———?詳情請(qǐng)查閱 Issue
(https://github.com/dotnet/runtime/issues/1388)。
當(dāng)插件依賴了 System.Data.SqlClient 類庫時(shí),會(huì)導(dǎo)致報(bào)異常:
SqlClient is not supported on this platform. ——— ?詳情請(qǐng)查閱 Issue。
(https://github.com/dotnet/SqlClient/issues/115)
此時(shí)我們可改用 Microsoft.Data.SqlClient 包。
(https://devblogs.microsoft.com/dotnet/introducing-the-new-microsoftdatasqlclient/)
? ? 我們?cè)谂朔щy的同時(shí)也在實(shí)踐中成長進(jìn)步。
------ END ------
作者簡介
? ? 曾同學(xué):?研發(fā)工程師,負(fù)責(zé)研發(fā)協(xié)同平臺(tái)產(chǎn)品的研發(fā)工作。
也許您還想看
? ??研發(fā)協(xié)同平臺(tái)持續(xù)集成實(shí)踐
? ??研發(fā)協(xié)同平臺(tái)持續(xù)集成2.0架構(gòu)演進(jìn)
? ? 研發(fā)協(xié)同平臺(tái)微服務(wù)監(jiān)控的技術(shù)實(shí)踐
? ? ERP開放平臺(tái)定制化遠(yuǎn)程高效協(xié)作秘笈
總結(jié)
以上是生活随笔為你收集整理的研发协同平台持续交付之代理服务实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core开发实战(第5课:依赖
- 下一篇: 排名前15位的Kubernetes监控和