京东中台化底层支撑框架技术分析及随想
导读
近几年,除AIGC外,软件领域相关比较大的变化,就是各相关业务领域开始如火如荼地建设中台和去中台化了。本文不探讨中台对公司组织架构涉及的变化和影响,只是从中台化演进的思路,及使用的底层支撑技术框架进行分析探讨,重点对中台及前台协作涉及到的扩展点及热部署包的底层技术细节,结合京东实际落地情况,对涉及的核心技术框架进行源码初探分析,探讨技术框架的考虑点,拓宽大家的思路,欢迎一起交流。
导读
近几年,除AIGC外,软件领域相关比较大的变化,就是各相关业务领域开始如火如荼地建设中台和去中台化了。本文不探讨中台对公司组织架构涉及的变化和影响,只是从中台化演进的思路,及使用的底层支撑技术框架进行分析探讨,重点对中台及前台协作涉及到的扩展点及热部署包的底层技术细节,结合京东实际落地情况,对涉及的核心技术框架进行源码初探分析,探讨技术框架的考虑点,拓宽大家的思路,欢迎一起交流。
01 序言
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
中台及其建设,区别于单体应用建设及微服务建设,最大的差异在于中台建设有一个较为独特的概念,即前台角色。中台建设并采用标准协议,开放了一系列标准丰富的能力供前台角色去编排、扩展使用。从外部的视角来看,其实看不到中台和前台,单纯还是功能交付,外界的感知没有太大的差异;从产研的视角来看,功能交付是中台能力叠加一系列前台个性化能力的结合物,职责上期望通过中台底层能力建设和前台个性化能力增强,两方独立开发,再通过底层技术支撑框架来将两方能力进行结合,期望达到中台、前台角色分工清晰,独立交付,提升交付速度的美好愿望。
从上述定义就可以看到,在外部视角感知不强的情况下,交付速度其实是个很明显的衡量指标。中台建设成功的标准,重点并不在于对中台建设的丰富能力进行衡量,也不在于前台开发了多少独立的扩展能力,仍在于同之前未建设中台相比,交付速度是否有质的提升。若这个关键指标没有变化,预估此类建设思路也会出现相关的变化及转型,转型的下一步思路和方法也有不少,不在这里探讨。
后续的内容,均会聚焦中台的底层技术支撑框架(后文简称为Matrix),来将中台、前台两方能力进行结合的技术细节和考虑点进行展开。
在笔者看来,Matrix框架,作为京东实施PaaS化相关的技术解决方案,期望的是建立划分合理的业务领域,完成业务建模及抽象,分离出核心逻辑及高频个性化业务,并将个性化的逻辑隔离出来,期望交给名为前台的组织或者部门去共建开发,来达成中台逻辑稳定,前台可以并行开发,最终提升交付效率的目的。
当然,在京东或者其他商业化公司,应用系统覆盖的业务领域及技术方向存在多种,包括但不限于各端的APP等前台、各端的web前台、各应用后端系统、各数据算法平台、各报表运营平台等等。目前的Matrix框架对其他某些领域可能存在不适应的情况,在笔者看来,着实属于正常情况。世界上的工具万万千,刀枪剑戟 斧钺钩叉 闲棍槊棒 鞭锏锤抓;各类解决方案千千万,很难存在一个锤子可以砸所有的钉子。但是只要总体的理念是合适的,并为其他各类适配此理念的工具提供合适成长的土壤,预判此解决方案可以逐步丰满成熟。
02 底层支撑框架分析
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
本文重点对底层支撑框架涉及的几个核心技术点进行展开:前台包热部署设计原理、前中台隔离原理、前台业务身份设计原理。
03 前台包热部署设计原理
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
本文对前台包发布、热部署的技术方案及思路进行探究,让我们的读者了解其设计理念。这样以后在工作中遇到类似前台包发布相关的问题,亦或者后续有机会使用并研究其他类似的技术框架,均可以做到举一反三,心中了然。
3.1 热部署基本原理
问题:
1)使用前台扩展包,在发布平台操作完成(完成推送、生效)后,应用端进行扩容上线,扩展点包是否可以自动拉取加载、自动挂载运行?
2)使用前台扩展包,在发布平台操作完成(完成推送、生效)前,应用端进行扩容上线,扩展点包是否可以自动拉取加载、自动挂载运行?
3)使用前台扩展包,在发布平台对部分容器完成前台包灰度推送,但没有触发生效,此时对这些容器执行重启操作,推送的前台包是否会挂载运行?
可以先看下如下的架构设计图(笔者个人理解,与官方理解可能存在一定的差异):
相关名词及对应的业务含义简要说明:
4)监控及安全相关:控制台及Matrix SDK相互合作,采集心跳、扩展点执行时间等各类统计维度数据,此处也是一个较为复杂的体系,不详细展开。
备注:以上架构图,只是笔者理解的Matrix技术框架的示意图,仅供参考。
从图中可以看到,我们的前台包发布、热部署存在“推”、“拉”两条数据链路,两条数据链路也分别适配不同的业务场景,如下详细展开。
3.1.1“推”链路
此链路的全过程可以表述为:软件实施人员在控制台,通过推送、生效等可视化界面工具操作完成后,控制台会将用户操作数据实时写入至相关的数据中心,数据中心依赖的核心组件DUCC(一种类似ZK的存储介质)会对外发布实时变更消息。集成了SDK的应用容器收到DUCC变更消息通知后,感知控制台的操作并依据操作类型不同做出差异化的反映:SDK依据操作的不同指令,在应用容器中执行不同的业务操作。
4)探活:对应的功能可表述为:SDK接收到指令后,从当前的容器服务器发送相关的心跳包,用以采集监控当前容器服务器相关的实时状态数据。此功能在Matrix技术框架中中对应的处理类为:DoAliveActionImpl。
“推”链路适用的场景为:应用的场景为实时性要求较强的相关场景,包括但不限于新版本灰度及发布、版本回滚及控制、版本下线及监控等。此类场景要求在控制台执行相关的操作步骤后,控制台及相关的应用容器在短时间内(秒级或者毫秒级内)做出实时反映。主打的就是保障通知的及时性。
3.1.2“拉”链路
此处需要注意一点,在京东现有环境内,容器扩容包含2步操作:
1)部署平台创建新的docker环境,完成应用实例的创建,并处理复制更新好相关的镜像环境;
“拉”链路适用的场景为:
1)对实时性要求不是很高的场景,如应用扩容等;
3.1.3 、“推”和“拉”两条链路设计理念说明
1、只存在“推”单链路:
需要应对的复杂场景:软件实施人员在控制台实时操作“发布”、“推包”等操作。
复杂场景下需要解决的问题:软件实施人员执行的操作,在“拉”链路下,在应用端要可以及时感知到。
可选的解决方案:可选的方案有2个:1)控制台和应用端建立直接联系,在控制台完成相关操作后,直接通过联系渠道将操作传递给应用端;2)应用端定时获取数据中心的最新应用状态数据。但是方案一基本变相等同于是“推”链路,此处可以忽略不计。方案二理论上可行,但是也存在2个弊端:1)定时的时长不好控制,太长了,则操作可被感知具有一定的延迟性,存在客户体验不佳的情况;2)定时时长太短了,多个应用无脑同时请求数据中心,对数据中心的高可用提出了极高的要求。对此弊端,我们可选的方案有:制定业务可接收的延时时间,并定时去请求数据中心。并对请求时,请求数据和返回数据存在数据过大的情况,采取数据极限裁剪、数据压缩等方案。并设定独立专业团队,通过建立双数据中心等机制,保障数据中心的安全性及高可用性。
3.2热部署相关注意事项
对于这种情况,推荐3类解决方案:
1)每次只进行小批量扩容,保障扩容数量的合理性;
2)仍然采取大批量扩容,但在应用发布启动时采用小批量的方式分批进行;
3.3问题解答及分析
问题1)使用前台扩展包,在发布平台操作完成(完成推送、生效)后,应用端进行扩容上线,扩展点包是否可以自动拉取加载、自动挂载运行?
答案:可以。此时应用端扩容上线,因为应用服务器上不存在任何前台包,会采取“拉”链路,将相关的前台包一次性拉取到本地进行加载、自动挂载运行。
答案:可以。此时仍然是“拉”链路在生效,但是需要注意一点,次数“拉”链路拉取的数据,为发布平台管理的上一次(如果有的话)上线的最新信息。在完成扩容上线后,如果我们没有在发布平台对新扩容的机器进行推包、发布等操作,线上应用会存在并运行两个版本的前台包。
答案:不会。虽然说前台包已完成灰度推送,相关的前台包文件已在应用容器中存在,但是容器执行重启操作时,SDK会自动按分组、IP等检测最新已生效的版本,若发现当前版本并没有生效,哪怕这个前台包文件在容器中已存在,也不会挂载运行。
情况2:若是我们讲前台包相关的目录中的部分文件进行移除(具体移除了哪类文件此处不详细展开),“拉”链路识别前台包文件已存在,“拉”链路不会得以执行。但是因为文件已损毁,相关的数据不完整,SDK在记载的过程中,会抛出相关的错误异常,并给出相关的错误信息,最终呈现的效果为整个应用启动失败。
04 前中台隔离原理
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
前文介绍的热部署操作完毕后,前台扩展包,就可以正确在中台相关的容器中完成部署了加载了。那么此处问题来了,前台和中台会共用某些相关的类,如何保障执行的安全性及可靠性呢。那么就不得不提到了类隔离机制,和隔离机制背后的原理:双亲委派模型。
此模型的简要代码可以表述如下:
protected Class<?> loadClass(String name,boolean resolve)
throwsClassNotFoundException
{
synchronized(getClassLoadingLock(name)){
// First, check if the class has already been loaded
Class<?> c =findLoadedClass(name);
if(c ==null){
long t0 =System.nanoTime();
try{
if(parent !=null){
c = parent.loadClass(name,false);
}else{
c =findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if(c ==null){
// If still not found, then invoke findClass in order
// to find the class.
long t1 =System.nanoTime();
c =findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if(resolve){
resolveClass(c);
}
return c;
}
}
在Matrix框架下,中台相关的逻辑和前台相关的业务包,是由2个独立的ClassLoader类分开管理,其中前台相关的业务包,是由名为com.jd.matrix.core.classloader.BizClassLoader的类进行管理。我们先来看下这个类的具体逻辑:
public class BizClassLoader extends URLClassLoader{
public BizClassLoader(URL[] urls){
super(urls);
}
protected Class<?>loadClass(String name,boolean resolve) throwsClassNotFoundException{
Class<?> clazz =null;
clazz =this.findLoadedClass(name);
if(clazz !=null){
return clazz;
}else{
try{
ClassLoader jdkClassLoader =ClassLoaderFactory.getJDKClassLoader();
clazz = jdkClassLoader.loadClass(name);
if(clazz !=null){
return clazz;
}
}catch(ClassNotFoundException exception){
}
if(ExportClassManager.match(name)){
clazz =ClassLoaderFactory.getInternalClassLoader().loadClass(name);
if(clazz !=null){
return clazz;
}
}
try{
clazz =this.findClass(name);
}catch(ClassNotFoundException exception){
}
if(clazz ==null){
clazz =ClassLoaderFactory.getInternalClassLoader().loadClass(name);
}
return clazz;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException{
returnsuper.findClass(name);
}}
我们可以看到,Matrix会结合容器侧的spring配置里的exportClassConfig属性,使用自定义的类加载器(BizClassLoader)去加载垂直业务包里的类,主要有两个核心内容:
1、对于不同的前台业务包里的类,分别使用BizClassLoader的不同实例去加载,对于垂直业务包里的自己开发的业务类,即使不同的垂直业务包中存在全限定名相同的类,但因为加载他们的BizClassLoader实例不同,不会出现冲突问题,类隔离的要求能够得到满足。
05 前台业务身份设计原理
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
中台和前台合作的巧妙之处,在于中台开放了多种标准的、可扩展的能力,前台基于自己的不同的业务身份实现多种个性化业务场景。即一种中台的能力,可对应多个前台扩展部署包。那么运行时,中台如何知道应该使用哪些前台包呢?
从中台建设初期来看,本来预计这种方式,以后一定会废弃掉。从过去几年的实践经验来看,这个地方预判错了,这种形式目前仍然是主流的形式,只是不同的系统对同一个业务身份的识别,逻辑千差万别,在串联业务流程的过程,确实会存在较为痛苦的情况。
需要注意一点,目前底层框架生成字节码的时间点,并不是系统自动加载或者热部署生效的时候,而是尝试命中业务逻辑的时候。预判肯定会存在执行过程中第一次速度缓慢的情况,实际使用的时候可以多加注意。
#{ClassStart}
import java.util.*;
#{Package}
public class #{ClassName} {
#{MethodInfo}
}
#{ClassEnd}
#{PackageStart}
import #{TypeName};
#{PackageEnd}
#{VarStart}
#{Type} #{Var}=(#{Type})context.get("#{Var}");
#{VarEnd}
#{executeStart}
public boolean execute(Map context){
#{VarInfo}
return #{Express};
}
#{executeEnd}
#{allStart}
private boolean all(#{Var} ){
Iterator iterator =#{Collection}.iterator();
while (iterator.hasNext()) {
#{ItemType} #{ItemVar}=(#{ItemType})iterator.next();
if(!(#{Right})){
return false;
}
}
return true;
}
#{allEnd}
#{anyStart}
private boolean any(#{Var} ){
Iterator iterator = #{Collection}.iterator();
while (iterator.hasNext()) {
#{ItemType} #{ItemVar}=(#{ItemType})iterator.next();
if((#{Right})){
return true;
}
}
return false;
}
#{anyEnd}
业务身份命中基本流程:此处其实无需多言,业务身份命中的逻辑其实隐含在前面介绍的前台包业务逻辑实际执行的地方,只是Matrix框架在执行逻辑前,创造了一个类似会话(Session)的概念,在会话中去对业务身份是否命中执行相关匹配逻辑,实际就是对相关的前台业务包执行fiter方法,判断评估业务身份命中。在会话(Session)中对前台包依次执行filter去匹配业务身份。
存在的风险:
1)部分前台包业务身份识别逻辑较重,会导致整体中台逻辑运行缓慢,存在性能风险;对应的建议解决方案:业务身份识别逻辑一定要轻量级;
Matrix对业务身份排名的具体代码如下所示,代码逻辑参见BizCodeSpec.compareTo方法:
public int compareTo(BizCodeSpec o) {
if (o == null) {
return -1;
} else if (this.priority != null && o.priority != null) {
int ret = o.priority - this.priority;
if (ret != 0) {
return ret;
} else if (this.versionSpec != null && o.versionSpec != null) {
ret = this.versionSpec.compareTo(o.versionSpec);
if (ret == 0) {
ret = o.bizCode.compareTo(this.bizCode);
}
return ret;
} else {
return 0;
}
} else {
return 0;
}
}
除这2点外,可能还存在其他各类场景对应的最佳实践,有待我们进一步探索。
06 思考
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
对号入座,快看看你的应用系统用了哪些高并发技术?
无用代码扫描组件设计京东广告研发——AIGC在京东广告创意的技术应用