研发协同平台持续交付之代理服务实践
源宝导读:插件系统大大提高了系统的扩展性,有利于模块化开发。系统发布后,当我们需要对系统进行扩充,可以再不编译的情况下更新系统的插件即可。基于热拔插的软件系统提高了持续交付能力,在添加新特性的同时保持核心结构稳定。本文将介绍研发协同平台代理服务在插件化设计方面的技术实践。
一、背景
插件系统大大提高了系统的扩展性,有利于模块化开发。系统发布后,当我们需要对系统进行扩充,可以再不编译的情况下更新系统的插件即可。基于热拔插的软件系统提高了持续交付能力,在添加新特性的同时保持核心结构稳定。
ERP是To B产品, To B产品的持续交付和To C产品的持续交付是有很大不同的。To B产品需要交付给成千上万家客户,每家客户的产品和环境都不一样,要保证ERP产品能持续的、稳定的交付给成千上万家客户,在客户端需要一个代理服务来协同完成。由此,ERP代理服务应运而生。
二、设计架构
ERP 代理服务在客户端负责产品的持续交付承担在重要的角色,ERP 产品每次的最终交付到客户,它需要在客户端完成很多重要的工作:更新包到客户机 IIS、收集客户机更新日志、服务器运行状态、同时由于客户机网络环境安全限制时也承担着信息的中转服务,为了日后公司产品的多样化、环境的多变性、产品的迅速更新,我们需要随时做出的相应响应,并能迅速的完成支撑,因此 ERP 代理服务在保证自身稳定的同时,需要快速的更新自己的产品所承担的功能职责。
ERP代理服务的核心需求如下:
自更新。
自恢复。
灰度更新。
业务逻辑可升级。
业务逻辑可灵活扩展。
较好的容错机制,在网络不稳定或其他其他因素的影响下,也能稳定持续运行。
基于以上需求,我们新设计了 ERP 代理服务的架构方案,我们将原有 ERP 代理服务改造为热拔插的插件支撑方案。
三、框架设计
代理服务通过支持三种类型的插件热拔插,实现多种多样的需求:
类库插件:实现服务端下发命令执行业务处理。
Web 插件:可灵活的提供 API 或者视图在页面中呈现。
Console 插件:通过外部启动 exe 程序的方式,守护进程或者其他特殊需求。
插件功能高内聚,与框架低耦合,开发人员根据规范,开发完成并进行单元测试通过后,打包并安装到宿主中,即可使用。
3.1、插件加载模式
通过向更新服务获取用户可用的插件集合,动态新增、升级、卸载插件,通过在内存中加载 DLL 的方式热拔插插件,能有效的更新客户端服务器上的代理服务功能,静默升级插件后续将让我们的服务有很高的拓展性。
3.2、目前规划的插件
针对现有的业务场景我们规划了多个插件,并用于实现不同的功能模块:
更新包插件:用于更新ERP产品到客户服务器的站点中。
更新服务API插件:用于提供产品注册等接口给ERP站点调用,将相应信息上报至服务端。
守护插件:守护代理服务运行状况,负责重启、更新代理服务。
其中部分插件为热加载方式,我们可快速的进行插件迭代开发,并发布上线(同时支持灰度发布),能快速的应对产品需求。
四、实现方案
我们从现有的 ERP 代理服务,以及 ERP 的交付特性,来考虑我们系统的可拓展性、可维护性,同时在一定程度上引入更新的技术栈来。我们从三个部分实现我们的插件系统:
主程序开发。
公共接口的基础插件类库开发。
各类插件开发。
以上,我们需要实现各部分之间的接口规范。
4.1、技术调研
我们调研了最新的 ASP.NET CORE 3.1(https://docs.microsoft.com/zh-cn/aspnet/core/?view=aspnetcore-3.1) 技术栈,它能很好的支持我们的热拔插的系统设计方案,微软也给出了简单的示例,以下是提供一些关键技术文档进行参考:
ASP.NET CORE 3.1 热拔插插件的实现方案。
(https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability )
ASP.NET CORE 支持独立部署方式避免系统需要安装.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)
在以上技术栈的基础上,我们对基础知识加以深入研究,引入项目的过程中不断思考优化项目结构。
4.2、实现解析
我们改变原有ERP代理服务的插件方案,采用 AssemblyLoadContext
来实现热拔插插件方案,同时重新调整主程序框架根据支持多种的插件的的加载,将公共服务接口提升到基础插件类库中以便继承插件可以通过依赖注入的方式使用我们的公共接口。
4.2.1、封装插件加载器SDK
功能特性
支持将dll以文件流的方式加载(可卸载)。
支持将占用dll文件的方式加载(可卸载)。
支持插件文件变更卸载插件并自动重新加载。
设计目录:
核心代码:
// 程序集加载
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;}}// 内存方式加载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})回收失败,请确认程序集未被使用"); } return !weakRef.IsAlive; } else { _context.Unload(); _context = null; return true; }}
4.2.2、 主程序功能模块解析
功能特性及结构分层:
全局异常过滤。
计划任务:命令定时执行任务、定时上报任务、定时更新插件任务。
插件加载器:类库插件加载器、Web插件加载器、控制台(exe)类插件加载器。
中间件:请求记录日志中间件。
应用程序全局配置。
服务层:命令服务、插件缓存、插件服务、上报服务。
目录结构:
应用设计结构:
4.2.2.1、 启动说明
ERP代理服务启动前会注册所需服务:
注册配置中心服务。
注册日志服务。
注册全局异常处理。
注册更新服务。
注册Web插件View视图引擎目录。
注册各类型插件加载器(类库插件、Web插件、控制台类插件)。
注册插件缓存服务、命令服务、上报服务、插件管理服务。
注册上报调度任务、命令执行调度任务、更新及加载插件调度任务。
并根据配置中心配置的服务启动地址启动,改地址用于ERP产品调用代理服务上报产品信息等接口调用。
4.2.2.2、API接口服务说明
目前由更新服务API插件提供给ERP产品将相应信息提交至更新服务,以便后续我们能很好的利用信息完成客户的产品交付:
注册产品信息。
获取服务器状态信息。
获取更新包更新状态。
获取更新页面地址。
获取更新服务状态。
同时由于该插件可以后期升级并热加载,我们可以根据需求快速迭代插件,为ERP产品提供更多的所需服务。
4.2.2.3、调度任务说明
应用启动后,通过异步方式启动调度任务,在调度任务中完成各自的职责。
命令任务:通常用于执行发布通知更新包任务或即时更新插件。
上报任务:用于上报客户相关信息、插件运行状态等信息。
更新插件任务:用于异步加载插件,不影响主程序运行,并定期同步客户端插件与服务端插件版本信息。
通过将核心任务使用调度任务异步运行的方式,避免影响主程序。
五、写在最后
目前我们的项目已经进入尾声,我们已基本在运用这套方案实现了原有ERP代理服务的功能,同时我们更加友好的支持灰度插件发布上线。未来我们还会加入日志主动或被动上报、客户服务器监控,ERP站点运行监控等功能插件来帮助我们产品发展的越来越好。
最后,我们在实践中也遇到了一些问题:
当使用 XmlSerializer 操作相关资源时,由于改对象目前还不支持可卸载,暂不能用于可卸载插件中 ——— 详情请查阅 Issue
(https://github.com/dotnet/runtime/issues/1388)。
当插件依赖了 System.Data.SqlClient 类库时,会导致报异常:
SqlClient is not supported on this platform. ——— 详情请查阅 Issue。
(https://github.com/dotnet/SqlClient/issues/115)
此时我们可改用
Microsoft.Data.SqlClient
包。(https://devblogs.microsoft.com/dotnet/introducing-the-new-microsoftdatasqlclient/)
我们在努力克服困难的同时也在实践中成长进步。
------ END ------
作者简介
曾同学: 研发工程师,负责研发协同平台产品的研发工作。
也许您还想看
研发协同平台持续集成实践
研发协同平台持续集成2.0架构演进
研发协同平台微服务监控的技术实践
ERP开放平台定制化远程高效协作秘笈
研发协同平台持续交付之代理服务实践相关推荐
- 研发协同平台持续交付2.0架构演进
源宝导读:为了打通CI/CD环节,实现持续的端到端的交付能力,RDC平台提供了在线化的更新服务,随着业务量增长与场景的需要,我们对更新服务架构重新设计,实现了2.0版本.本文将介绍更新服务2.0的架构 ...
- 研发协同平台持续集成Jenkins作业设计演进
源宝导读:Jenkins作为一个开源的持续集成工具,被大家广泛使用.本文将分享,Jenkins在明源云研发协同平台中的运用,以及在其作业设计方面的演进历程. 一.作业设计1.0 起初,为了尽快推出研发 ...
- 研发协同平台持续集成实践
源宝导读:"持续集成"是敏捷最佳实践中,保证高质量交付的关键环节之一.本文将分享,在大规模研发在线协同的背景下,如何支撑在线持续集成的高性能和高可用. 一.什么是持续集成 在< ...
- 研发协同平台持续集成2.0架构演进
在上篇<研发协同平台持续集成实践>一文中我们分享了为什么要做持续集成,技术选型,工作原理以及实践落地.今天我们从架构上来分享一下架构层面的设计和演进. 持续集成1.0 在最开始设计的过程中 ...
- 研发协同平台数据库死锁处理及改进
源宝导读:数据库死锁是高并发复杂系统都要面临课题,处理死锁问题没有一招制敌的标准方法,需要具体问题具体分析.本文将基于研发协同平台遇到的死锁案例,介绍从监控.分析到处理的完整过程和经验总结. 一.背景 ...
- 研发协同平台架构演进
导读 系统架构是一个系统的灵魂,然而一个好的架构(或者更确切的说,一个合适的系统架构)不是一蹴而就,一下子就能完全设计出来的,而是随着系统发展,逐步演进的.本文将介绍明源云研发协同平台的架构从0到1, ...
- 掌门持续交付流水线大规模实践
CD 平台是掌门的持续交付系统,流水线功能自 2020 年 3 月在 CD 平台上正式开放,目前已稳定运营一年多,超过 900 个应用启用了流水线功能,应用接入率超 60%,下图展示了流水线功能的关键 ...
- 承担集团数万应用、研发人员日常工作,阿里持续交付平台的设计、迭代之道...
摘要: 阿里持续交付平台已经经历了 8 年的不断迭代进化,成长为集团几万应用所依赖的最重要的研发工具,它的效率直接影响着几万研发日常工作.但平台不能只是工具的堆砌,更需要针对互联网时代的研发模式进行深 ...
- 承担集团数万应用、研发人员日常工作,阿里持续交付平台的设计、迭代之道... 1
摘要: 阿里持续交付平台已经经历了 8 年的不断迭代进化,成长为集团几万应用所依赖的最重要的研发工具,它的效率直接影响着几万研发日常工作.但平台不能只是工具的堆砌,更需要针对互联网时代的研发模式进行深 ...
最新文章
- Service与AIDL详解
- Linux系统状态检测及进程控制--2
- C# MongoDB简单增删改查使用
- (一)导学(前端框架面试-聚焦Vue/React/Webpack)
- 【Java】辨析JUnit4中的@AfterClass、@BeforeClass、@after、@before
- Grub 之常用命令和Windows引导修复(二)
- 综合前置接口报文规范_浅谈用HttpRunner进行接口自动化测试
- vue的h函数_Vue中render中的h箭头函数
- apache ii评分怎么评_APACHEⅡ评分系统
- springboot 整合mybatis 报错Invalid bound statement (not found)
- wgs84转cgcs2000 java_CGCS2000坐标系与WGS84的相互投影转换
- 排行前五的web3风投公司2022年都投了什么
- MySQL 创库 查库 等基本操作
- 数据分析模型篇—安索夫矩阵
- ImageBox网页图片批量下载器
- cpci无法检索_图书馆无法进行CPCI-S检索 - 北京大学图书馆(PKULibrary)版 - 北大未名BBS...
- silverlight beet - 动态设置Clip
- OPPO手机计算机怎么打符号,OPPO手机怎么开启输入的九键快捷符号调频功能
- 什么是AJAX的同步异步?
- DIY时钟类--广州百田笔试之一
热门文章
- c#代码实现GPS数据的有效性校验
- js模版引擎handlebars.js实用教程——with-终极this应用
- 理解 Delphi 的类(七) - 认识类的多态
- 面向全球用户的Teams app之夏令时篇
- MS CRM 2011 Form与Web Resource在JScript中的相互调用
- JavaScript校验网址
- 用Cocos2dx开发棋牌游戏的观点解析
- 【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动
- Java程序员从笨鸟到菜鸟之(一百零四)java操作office和pdf文件(二)利用POI实现数据导出excel报表...
- 生成jar文件的方法