
作为一名研发人员,除了业务开发之外,研发提效是一个永恒的话题,而女娲正是这一话题下进行的一次全面的剖析和实践。
女娲是什么
女娲是业务研发同学(开发、测试、运维)在软件迭代的各个阶段(开发、联调、测试、上线、运维)通过女娲控制台对代码进行动态干预(增强、开箱、派生)使程序运行时注入额外的技术能力(缓存、兜底、Mock、灰度等)的一站式平台,旨在提高研发相关工作效率。

女娲的组成为“一个SDK、一个控制台、若干插件、一个生态”,应用于若干场景,形成若干NoCode/LowCode技术解决方案的应用场景。

女娲工作示意图:

女娲的发展历程

女娲1.0
寓意是“我们不补天,我们只补锅”,最初目标是保障线上的性能和稳定性以及突发应急,希望采用通用的处理模式,将业务中非功能纯技术述求进行提炼,形成热插拔的技术增强插件。业务开发聚焦业务功能需求实现,而非功能需求则由女娲平台动态化组装配置实现,女娲接入“零代码”,达到业务与技术实现分离,提升研发效率。主要的提效场景为兜底、缓存、流控。
女娲2.0
将女娲配置进行界面化,降低女娲使用的复杂度
女娲3.0
随着女娲从1.0、2.0、3.0的升级,女娲的使命不仅聚焦线上服务保障背锅,而是全面升级为技术人员的工具箱,助力研发过程全方位的工作,达到360°空间的提效。
提效场景包含不限于:
女娲产生的背景
3.1 初识提效的几个方面
不管怎么提升自己解决问题的能力和手段,最终的核心目标是提升做事情的效率和质量,这里主要有如下三个方面:
**研发提效方面:**采用合理的技术架构、框架、工具、平台使研发过程简化、易用化、智能化,原先一周才能完成的工作现在只有一天。
**运营提效方面:**采用合理的业务架构、平台化、体系化、配置化、组件化、流程化、模板化能够灵活支持新的业务,原先新的运营场景只能Case By Case的开发、测试、上线流程,变成了快速配置、落地、检验的自动化、体系化流程,让运营一定程度上有更多的空间,提升整体运营效率。
**钱效方面:**采用合理算法模型,通过人群画像、标签体系、分层体系进行精准化投放,提高花钱效率,延长用户生命周期,获得最大ROI,将原先一刀切,粗颗粒的投放精细化,提升用钱效率。

无论哪种提效离都不开“持久”与“稳定”,在此基础上各个提效方面有侧重点:研发要“快”、运营要“活”、花钱要“灵”。
本文聚焦研发提效,研发效率主要包括两大方面:
一是软件的运行质量,也可以说是运行效率,比如程序响应时长,处理吞吐量/并发度、运行的可靠性、安全性。
另一个是软件维护效率:这里的维护是指软件的整个生命周期的维护,维护效率包括开发效率、测试效率,发布效率,运维效率;其中开发效率在代码可读性、易用性、通用性、扩展性和测试性基础上,同时依赖于工具和生态的友好和完善性。

3.2 研发提效的需求矩阵
下面我们进入提效的具体“事”,软件的开发生命周期,实现业务需求是一方面,但为了保障业务需求的很好落地,我们会或很多非功能需求来保障程序运行的稳定,同时我们会辅助大量的测试与验收来检验需求是否正确实施,另外我们也会做很多衍生需求来检验功能需求落地的效果分析。

接下来,我们进一步细化,实实在在走进软件研发工作的点点滴滴中。
比如:
**需求阶段:**产品需求前期配合技术调研方案,趴清现有链路。
**开发阶段:**除功能需求开发外,非功能需求开发,包括:
测试阶段:
**发布阶段:**白名单验证、灰度发布、回滚、降级等正向与逆向操作。
**运维阶段:**用户动线日志,数据血缘关系,异常根因分析、监控、报警、大盘、智能机器人。
**效果回收:**业务埋点,转化率,DUA,业务看板,业务日报。
**项目管理:**通过敏捷开发、迭代开发等项目开发模式组织各个技术节点高效高质量软件交付工作。

我们通过对上面的工作进行归纳总结、提取通用能力、工具化、平台化既能形成我们的“器”,来提升对应工作的效率。不可否认,大家也是这样做的,每个阶段都有很多工具和方法可以使用,很多团队与个人均有一些自己的沉淀来提升对应的工作效率且大多数运行的好好的。
3.3 研发提效的两大问题
虽然提效已经有成熟的工具平台,但依然存在两个主要问题,也是女娲核心解决和修复的问题。
低集成度导致学习成本高和产生大量胶水代码
大部分是以各种成熟的中间件形式而存在,需要时通过相应的“胶水代码”进行数据链路、系统链路打通。
比如我们需要压测,首先需要接入某近端包上线进行流量录制;我们需要数据效果统计,系统健康监控报警时候,首先我们需要埋点日志打印;需要进行缓存存储时,需要存储SDK引入,Bean注入。
这些独立、模块化的工具、框架、平台在使用时,注定需要不少的“胶水代码”进行串联,每一个都以自己为中心,也带来不少的学习成本和工作量。
rpc接口粒度的最小单元限制很多研发工作开展
而微服务的架构下,我们暴露的服务方法是业务的最小单元,一个接口方法对应一个完整的用户行为,接口是程序对外的最小单元。
但接口方法在程序内视角只是一个入口,内部由更基础的技术处理单元串联构成,比如存在下游防腐处理,数据聚合处理,逻辑加工处理,存储操作等等,甚至每一个业务数据单元的读写都是一个处理单元。绝大多数提效工具、平台、中间件仅针对于的网络调用这一层,而是程序内部处理单元视角。
我们的程序大多是面向对象,一个接口的业务逻辑是有若干内部较为独立的处理单元串联而成,当很多时候我们在研发中需要对内部逻辑处理单元进行处理监听、干预、统计时,所有现成工具变动都不实用了。
比如我们要统计一下某个下游接口在我们应用中被哪个出口方法调用了,我们很难找到内部两个组件的关联分析的工具,另外很多时候为了某个特殊的用途,比如用于单元测试,数据订正,应急的后门,用于智能机器人的查询接口,我们需要将内部的一个处理单元单独包一个接口对外暴露出去。
这些的根因是所有工具和平台的最小处理颗粒度为rpc接口,而女娲聚焦的恰恰是代码层面的业务组件。

3.4 女娲产生的导火索:通用缓存
上面从上向下分析让我们意识到传统的研发工具大部分停留到接口一层,很多时候并不能直接支撑我们的述求,我们是否可以思考一下为什么造成这样的结局,我们是否也可以换个角度,是否这些环节有更加高效的方式?
从业务中来,到业务中去,做女娲的引子是做口碑评价架构升级蚂蚁侧系统迁移入淘,然而依赖服务大部分还在蚂蚁,需要给一堆跨域接口添加缓存,通常我们会逐场景进行code-hard;为进一步提效我们会写缓存的工具类,封装一些缓存读写逻辑,在需要的地方尽量最小侵入的方式调用一下;再再进一步我们会通过声明式注解进行缓存声明实现,业务逻辑几乎无侵入,比如Spring @Cacheable注解或自定义实现一套注解。
虽然上面三种形式在一步一步更高效,只是从代码层面从100行缩短50行,50行缩微20行,从代码的易用性和复用性有一定程度的提升,只是做了一定优化,并没有根本上的差异,无法快速应对风险,不能本质解决问题。
总结如下:
除了缓存外,其他的兜底、流控,动态埋点,Mock、单侧等等这些研发过程与业务无关的其他非功能需求存在同样的问题。即:
因此,缺乏完整、不够灵活、复杂度高、烟囱问题致难以快速复制和工作量较大。
女娲的建设
4.1 目标:研发提效数倍以上
面对大量的重复工作量、运行时未知风险较弱的调控能力,传统开发模式引发的一定的效率问题,通过一定效率提升手段使:理想情况下以前硬编码2小时1天的工作量,可以降低到1分钟30分钟即可完成,效率提升至少n倍以上。
4.2 难点:提效场景复杂性和多样性
4.3 核心思路:化功大法七式
那应该采用怎样的效率手段呢,我们分别针对于可做工指标:完整性、适用性、灵活性、易用性、隔离性、复制性进行拆解,最终形成体系化、通用化、动态化、智能化、透明化、规模化、协议化相应的解决方案。
4.3.1 体系化方案for完整性
通过体系化方案形成提效目标的完整性,提供体系化的能力全方位保障技术需求的闭环,比如缓存的预热和效果展示做成通用能力,链路直接打通,减去业务特化的胶水代码,保证完整性。
比如缓存,形成完整的缓存操作闭环:
由于女娲的目标是研发提效,范畴较为广泛,采用何种技术架构最后在若干提效场景均使用,那么依赖通用化的解决能力来提升女娲在方方面面提效的实用性。
比如下面讲到:
**原则:**使逻辑通用、扩展/配置灵活、避免“复制/粘贴”大法(方案:模块化+组件化+配置化方案)
通过动态化的方案提升提效的灵活性,增强运行时干预能力,如更加灵活的手段创建和修改业务缓存实现,提升灵活性,不需要代码发布即可完成相关工作。
比如缓存:缓存插件通过指令下发驱动,指令下发根据运行的实例差异携带不同的运行参数,这些运行参数,站在我们的视角就是配置,集团内选择Diamond作为配置中心是个常规的选择。
通过智能化手段降低复杂度、提升易用性,提供智能的方式降低技术实现的复杂度,降低各种人工设参和构建实例的逻辑实现步骤,从命令式切换为声明式,不用关注技术细节,只需要关注技术目标。
比如缓存,如果要让缓存更智能,通过缓存技术的深入,合理的抽象,成熟的处理模式进行封装,形成一个缓存工具,或者缓存框架,更或者是一个平台,在女娲中则是一个技术插件。
个人理解易用有四重境界:

这四重境界不是互斥关系,而是包含和依赖关系,比如我们的缓存方案:
原则:优先no-code, 其次low-code,再次hard-cord。
女娲的运行环境是沙箱环境,而业务运行的环境是真实环境,做好环境隔离,对业务使用是透明的,对结果、时效均不产生影响,主要体现如上三点:
通过规模化的手段提升提效的快速复制性,提供轻量级接入,构建易用易复制可规模化的解决方案,为了方便缓存方案能快速复制到其他应用场景,所有所有处理的中枢逻辑均封装在女娲SDK中,针对于业务差异化部分均通过女娲控制台来进行配置,应用只需要一次接入,多次使用。就像引入一个第三方lib一样引入即可。
通过合适的接口定义开放出去,业务同学可以根据自己的述求实现,甚至是插件,将能力对外开放,甚至是插件生态和共享。
关键点设计1:三个统一
三个统一、两个体系、一个生态:
三个统一:统一概念 + 统一模型 + 统一流程;
两个体系:多样的插件 + 配套的页面;
一个生态:丰富的生态。

三个统一
概念、模型、流程三个的统一
5.1 概念统一

**插件:**技术概念,插件是近端包中预置实现技术组件,本质上是完成一个独立的技术目标的代码片段,默认并不会执行,是由单独的指令和配置驱动插件执行以及传递执行时所依赖的参数,女娲提供了多个插件,同时提供了简单的插件注册和实现API。
**元件:**技术概念,是插件可以使用的原子能力,如果上面是自上向下的设计的产物,那么元件就是自下向上设计的产物。
**切点:**可以称之为“触点”,技术概念,顾名思义,切入的点,触发扩展逻辑的点,插件是一个Action,那切点这是插件执行的Target,切点分为两类:
举例:
**切面:**Spring的切面(Aspect)是一种用于在应用程序中定义横切关注点的技术。
**场景:**业务概念,代表一个完整的技术能力应用,在业务上也对应的一个完整的应用,一个插件作用于一个切点构成一个场景。
即:1个插件+1个切点->1场景,场景是组合Action + Target的一个Trigger。
**方案:**业务概念,方案是一个场景下插件具体需要如何工作的一组定义,一个场景可以有一到多个方案(即多组定义),一个方案包含插件运行时依赖的若干参数,多个方案则可能匹配其中一个方案(排他)或多个方案协同执行(共享),方案是Action工作的方式,一个方案对应一个Executor,一个场景n个方案则有n+1个Executor,其中1个为多个方案Executor协调的Executor。
5.2 模型统一
消息模型:也是领域模型,是驱动插件工作方案元数据模型,将建立存储模型和插件的桥梁,来源于配置模型,同时需要考虑各式各样插件在领域模型的通用和差异化表达,对应Model Object。
配置模型:也是展示模型,指控制台进行场景配置时的结构化配置模型,该模型站在配置人员的视角进行设计,以配置人员体验为优先,同时也能很好转为为存储模型,对应View Object。
存储模型:所有前端配置最终持久化时的存储模型,该模型方便转换为配置模型也方便转为为领域消息模型,对应Persistent Object
无论是哪种模型,都是针对于一个切点的插件应用场景的配置,即模型的实体是女娲场景和方案对象。

挑战
由于插件太过于多样化,为了灵活性需要,插件运行时需要支持动态的参数是不一样的,比如:
思路
方案一:无统一的消息模型时
所有的插件均各自定义自己的模型消息,插件内部根据各自的消息模型做相应的处理,即定义:缓存消息模型、兜底消息模型、流量录制消息模型。
优点
缺点:
每添加一个新的插件
而实际我们发现很多字段都有类似的含义或者同样的作用,比如:useCacheExpr和useSnapshotExpr均表示插件运行时的前置检查,而cacheEngine和snapshotEngine均表示目标的存储实例对象。
是否有更适用的方案呢?我们这里进行各种场景的归纳总结与统一。
方案二:统一的消息模型(元件聚合消息模型)
插件为达到固有的技术目标,通常需要一系列动作组合实现,即内部可拆解若干原子操作,比如我们将缓存插件内部拆解为读缓存、写缓存、独立条件判断、缓存KV计算,统计等原子操作,不同的插件的原子操作可能有类似的,也可以有个性化的,我们所有插件的原子操作进行聚类,同一个类型下的原子能力基本上具备相同的功效和相同的依赖运行参数,我们将通用的原子操作视为系统的“元件”,大体可以归纳为如下几种,比如:

而这些元件具有自己的运行时配置参数,比如灰度元件有:分桶键表达式、白名单测试组、灰度比列、灰度类型(手动、自动)、灰度计划等运行配置参数,元件拥有各自的元件配置。

不同插件复用同一套消息模型
优点
有统一的配置模型和扩展机制,添加新插件时:
缺点:
方案1 vs 方案2
换个角度想,消息模型是给程序使用,语义的明确性和消息的简洁性和实实在在的工作量和复用性比较来并没那么重要。
因此,我们的消息模型毫无疑问选择方案二。
消息模型是面向程序的,强调通用性、复用性、扩展性;而配置模型是面向用户的,强调简单,直接、明了;展示模型最终是为了转化为消息模型,因此配置模型基于消息模型而设计,如果我们不做加工直接使用存在一些问题。
挑战
缺乏场景语义(语义明确)
统一的消息模型基于技术能力设计,面向插件,采用较为通用的技术化概念,并非用户业务场景场景下业务概念,缺乏对应场景业务语义,并不能满足配置模型简单明了的要求,较为验证影响用户体验。类似于增删改查的数据库技术操作概念,而不是创建,编辑、保存、提交(对应的用户行为)等前端业务概念。
比如我们mock场景,需要针对于某个特殊场景返回某个固定的mock报文,如果按照消息模型直接展示则为内容信息 - 字符串类型内容 - 输入具体字符串值,比如:

而实际展示面向用户应该是这样的,不但能明确知道填写信息是干什么的,还能在这个场景下针对于该字段做特殊的样式和校验逻辑,比如这里的会用json编辑器渲染:

层级较深表达复杂(流程顺畅)
统一的消息模型是一个大而全的模型,由于承载的信息较多,因此会有更多的模块和更深的层级构成,而前端通常是表单、列表、等组件区块进行信息承载,对于层级和模块体现通常会通过子页面、弹窗、分步骤、分Tab进行嵌套,一个页面中如果存在太多的前端区块组件,特别是不同形式的区块组件、更特别的是嵌套的区块组件,不利于信息的表达,显得杂乱,人容易理解的信息流,比如我们常理解的I型、L型,而S型、万花筒式的信息结构更难理解。
比如我们在写入时,我们按层级表达是这样的:

如果基于具体场景打平,我们可以这样表达:

明显下面会更加简洁明了一些,当然例子只展示了冰山一角。
缺乏定制能力(面向程序 or 面向用户)
由于前面我们讲到了消息模型是大而通用,因此具体的场景并不需要所有的属性,因此实际场景编辑中需要进行裁剪;另外为提高配置的效率,我们需要针对于部分字段进行增强配置,增加一些额外的处理能,例如当我们需要对缓存的键值对增加在线自动学习能力;又比如当配置过于复杂时,我们需要让用户step by step来引导用户进行配置。
比如,缓存配置,面向消息模型时,则是缓存键组成部分的录入为:

此处进行展示层的结构化表达为:我们将缓存配置分为缓存基本设置 -> 流量录制 -> 缓存键设置 -> 缓存条件设置 -> 异步加载设置 -> 回放效果分析等步骤,同时针对于缓存键配置时我们增加了选择录制样本进行分析出推荐可选方案,类似如下:


思路
消息模型在不同场景下,增加前端的定制化渲染能力,即场景渲染模板,主要具备如下一些能力:
裁剪(简化表达)
消息模型是一个通用的,大的模型,而特化到某个具体的场景,往往不需要所有的配置,因此进行相应的裁剪,形成按需的子集
拉平(降层级,简化表达)
将消息模型json结构化数据打平,做为表单field字段的name,形成前端表单容易渲染的数据结构,而不用前端多组件的层级表达。

映射(强化表达)
消息模型的每个字段具备场景下具象化展示名称和展示描述,以及额外的展示能力,方便在不同场景下前端根据配置进行渲染。
正如上面将String的内容,映射为:

分组与排序(模块化,结构化表达)
针对于不同场景可以将消息模型拉平和映射后,可进行分组与排序进行模块化、结构化表达。
正如上面例子,将消息模型的多个模块转化为展示模型的多个模块。

最终分拆为多个表单,多个模块。
结论
增加渲染模板
增加场景配置模板,配置场景下如何对消息模型加工形成单独的前端配置模板,作为配置模型的场景定义,即完成裁剪、拉平、映射、分组、排序等工作。
页面动态搭建:前端组件化和动态渲染
将每一个元件的每一个配置项建立一个前端独立组件,根据渲染模板进行循环渲染
如上面介绍,作为工具平台存储模型核心目标就是做配置的持久化,同时方便转化为展示配置模型和领域消息模型,原则上既不面向用户也不面向插件实现。
挑战
存储本质是存储不同女娲插件应用场景下的方案配置,因此配置的主键是场景,场景下配置的是方案。而不同的场景需要配置项是很大不一样的。
站在展示模型,我们可能分步骤进行配置,因此方案下可能分为多个步骤,然后才是该步骤下的表单字段信息。

站在消息模型,我们的同一个方案消息模型存在多个元件构成,每个元件有若干表单字段信息。

是面向消息进行存储,还是面向展示进行存储?如何针对于上面四类信息进行存储,四类信息四张表吗?显然事情没那么粗暴。
思路
面向配置进行存储
存储最紧密的关系是增删改查,特别是配置系统,存储是配置的载体,而存储转消息模型只是一锤子买卖,因此根据耦合度来讲,一定是基于配置进行存储的。
面向方案进行配置
前面讲到了1个插件+1个切点=1个场景,场景是Action + Target的一个Trigger,场景本身承载的信息较少,只是切点和插件的应用组合,下挂若干方案, 而方案存在具体描述和生效时间即内容,因此存储模型主体选择方案即可,而一个场景只是切点和插件的方案的聚合。

纵表存储个场景差异化步骤信息
一个方案存在多步配置,对用户来讲可能分段保存,也可能整体保存,用户的每一次操作和数据存储一一关联,因此将每一步的内容存储到纵表的JSON字段中,而步骤唯一code码与方案id作为纵表的联合唯一键组成。

结论
模型的统一是系统的稳定和工作量收拢的重要基础。
5.3 流程统一

5.3.1 插件开发与注册(开发流程)
开发单插件执行器、定义插件配置文件信息,以及注册插件至插件中心。

1.定义插件配置文件信息(约定大于规范):
消息最终需要存储在diamond,在diamond进行如下设计:
2.插件方案Executor实现
定义统一的Executor接口,待实现方法如下:
3.定义插件Schema&插件注册(一行代码)
在com.alsc.content.sdk.nvwa3.schema.PluginSchemas#allPluginSchema 直接add一个插件Schema实例即可,PluginSchema构造函数参数定义如下:
5.3.2 插件预加载和编排(启动&热加载流程)
插件预加载与编排

配置的若干场景下若干方案的变化,均会由女娲的配置模块进行监听处理.
代理处理流程(AOP)

女娲统一提供ConfigurationAspect进行流量拦截,在女娲引入时,进行手动注册;
调用插件处理链进行业务方案执行,即调用MethodAspectHandlerRegistry.getHandler(methodReference);找到插件处理链;
面向控制台使用的SPI服务:只需要控制台应用初始化才需要配置(com.alsc.content.sdk.nvwa.service.NvWaOpsSpiService)
面向切点的泛化调用SPI服务:通过泛化调用,可以任何一个切到的内部方法进行开箱供外部调用:(com.alsc.content.sdk.outer.user.service.**)具体实现略
状态机

版本控制
**
**

同一个方案配置在不同的环境中通过版本和环境标进行区分。
发布流程
预发和日常只有全量状态才能进行同步至其他环境,线上不允许直接修改配置。

所有场景均满足如下配置流程,且组件基本上各种复用。
通过女娲应用顶部banner,选择本次需要操作的应用和环境

方式1: 包结构 -> 类列表 -> 方法列表 三级选择

方式2: 全部已配置树选择
**
**

方式3: 其他

跳转到配置页和详情页,配置页和详情页根据场景的差异(场景渲染模板)进行动态渲染展示和提交保存。
所有场景方案的状态操作,控制通过方案列操作栏进行快捷操作。
关键点设计2:两个体系
6.1 女娲插件体系
女娲插件是以切点作为组织和调用视角,即:切点 -> 插件链 -> 插件 -> 方案执行器 -> 元件执行器,其中切点对应SpringBean的一个方法;插件链:切点需要动态代理执行处理链。

6.1.1 插件Handler
一个独立的技术能力组件,主要api为如下:
系统提供通用的插件实现com.alsc.content.sdk.nvwa3.plugins.NvWaPlugin,默认使用该插件即可,特殊情况也可以参考改插件进行实现新的插件。
方案是每个插件方案执行时的真正逻辑,主要提供如下三个api:
插件方案根据自己场景继承实现对应api即可。通常不需要三个api都实现,比如只实现aroundHandle,或者实现preHandle + afterHandler,或者preHandle与afterHandler之一。

**
**
树形组成关系
元件是一个树型包含关系。
另外,元件分为有生命周期元件和无生命周期元件,上图中含
的元件为有生命周期元件,相反为无生命周期元件,其中
代表当前元件无效,
代表当前元件有效,
代表定时任务激活、失效生命周期元件。
无生命周期元件
无生命周期元件,默认是生效状态,接口定义如下,有一个输入与输出。
有生命周期元件
有生命周期元件,是指带状态的元件,且状态有自己的生命周期,状态决定该元件是否生效,同时该元件的有效性会通过“观察者模式”通知上一个元件是否生效。
其中根据自身元件配置决定会存在定时任务进行状态激活和失效,比如流控元件设置开始结束时间时。
由于存在场景的处理会联动其他切点场景,比如我们切点A命中内部链路监控场景,表明当前请求上所有切点均需要将调用栈打印出来,即其他切点也均需要按照该插件的要求进行默认处理。而如何联动呢,我们通过女娲上下文对象进行串联。

女娲上下文对象是一个数据容器对象,包含三部分内容:共享字典、隔离字典、处理钩子,定义如下:
public class NvWaContext {
/** * 线程上下文 */ private static ThreadLocal contextTL = new ThreadLocal<>();
/** * 上下文传值数据对象(隔离字典) */ private static ThreadLocal 上下文对象本质是一个通过线程变量进行持有的数据容器对象,当需要时通过线程变量直接获取使用,而不用调用栈的参数将对象显示的传递下去。
同时,上下文对象通常存储一些通用的数据,多个线程之前需要共享,因此线程之间需要将上下文对象传递,传递分为两种新式
引用传递
我们run新线程任务时,将父线程线程对象赋值给新线程的的线程对象,而女娲上下文在线程传递时,默认以引用进行传递,更多减少对象的开销。
女娲中【共享字典】的所有数据默认采用引用传递。
值传递
然而有些场景,如内部链路监控时,如果通过引用传递则存在线程安全问题。
如我们需要递增计算链路中的spanId时,如果多个线程共享全局SpanId计数器,则最终SpanId就会混乱,此时需要一个线程之间隔离的SpanId计数器,该SpanId计数器继承至父线程的SpanId计数器,但仅线程内部可访问。
女娲中【隔离字典】的所有数据默认采用值传递。
具体应该使用隔离字典还是共享字典取决于要存储的数据是value,还是variable,如果是variable且存在线程安全问题则使用值传递,否则采用引用传递。
无论是共享字典的引用传递还是隔离字典的值传递,通过如果NvWaContext的三个静态方法分别打包提取、传递和清理。
女娲插件执行需要进行切点之间进行通信时,或者在所有切点,全局进行默认处理时,女娲开放了如下两个级别的钩子,供插件进行链路干实现:
【请求级】钩子
应用内部进入时的处理钩子,包括初始化和处理结束钩子,如List
NvWaContext.addInitializedHook(nvWaContext -> { NvWaContext.currentContext().addCloneableInstance(TraceSpanId.NV_WA_CONTEXT_TRACE_SPAN_ID, new TraceSpanId()); TraceSolutionExecutor.initRecordingTraceInfoIfNecessary( nvWaContext.getEntryMethodReference(), nvWaContext.getEntryStartTime()); });
【切点级】钩子
每个切点处理时钩子,即List aopBeforeHooks,List aopHandleAfterHooks,会分别在ConfigurationAspect前置和后置环节处理一次,比如在内部链路监控插件,会在切点处理结束时,继续进行埋点处理。
NvWaContext.addAopHandleBeforeHooks((methodReference, startMills, isInitialed) -> { TraceSpanId spanId = NvWaTraceContext.getSpanId(); if(!isInitialed) { spanId.enter(); } }); NvWaContext.addAopHandleAfterHooks((methodReference, method, args, result, e, startMills, isInitialed, context) -> { try { TraceSolutionExecutor.recordingTraceInfoIfNecessary(methodReference, method, args, result, e, startMills, context); } finally { NvWaTraceContext.getSpanId().exit(); }
});
6.2 页面体系
当我们开发一个插件时,通常需要做如下事情:
比如:

前面我们通过“模型的统一” + “流程的统一” + “插件体系”解决了后端实现的烟囱问题,而前端页面如何高效的支撑呢?
针对于4大类页面,梳理“变”与“不变”,尽量复用现有平台或者平台现有组件,其中:
6.2.1 接入乐高3.0深度应用
乐高3.0是集团内的一个低代码开发平台。
考虑乐高应用在乐高平台集中部署,与服务端http/mtop接口隶属于不同的域名,无法进行直接调用,最有效或者尽量低侵入乐高调用的方式,则采用和Spring Web应用进行集成部署。
作为全局配置,其中包括应用选择和环境选择,选择后下一跳继承该选择,方便多步骤操作的连贯,其中应用选择范围为自己AONE有权限的应用,接入女娲可选,未接入女娲置灰。

所有页面尽量统一风格,统一流程,甚至是复用,详见《页面统一配置模板(配置流程)》介绍,特别是配置页面,由于模型的统一导致配置页面的的复用成为可能。
一个插件一个的配置菜单,菜单对应的插件的配置管理列表页。所有插件均复用改配置管理列表页。


页面的组件、事件、样式完全服务,不同插件该页面仅有如下项几点配置不一样。
页面链接不一样
该页面的url链接不一样,但都是以“{插件code}_list”命名
页面全局配置项不一样
配置元数据如下:
{ "businessType":"cache", "cpLikeMatch": false, "solutionSlug":"mock_cfg"}
操作按钮不一样
列表配置项目的操作选项不一样,不同插件配置项对应的状态机,由服务端渲染模板控制,比如Mock的状态图为如下:

基于前面讲到的场景配置模板,进行动态表单渲染,即使用配置模型驱动。
维护元件配置项组件库
参考消息模型元件设计,系统中存在如下10+个元件构成,每个元件的拥有自己的配置项,系统累计存在元件数 * 元件配置项数约100的配置项,而前端展示最终是通过场景渲染模板将消息模型进行“裁剪”+“拉平”+“映射”+“分组排序”形成每个场景特化的配置模型。
其中,每一个元件的配置项是独立的、较为明确的、不变的;而不同场景下配置项组成和别名是动态的,变化的,因此在乐高3.0中将将元件每一个配置项做成一个前端组件。
同一个元件配置项组件在不同场景下:
相同点:
不同点:
抽取如下:

分别对应如下红框部分:

使用循环渲染进行动态化表单渲染
乐高3.0对于动态渲染支持不是很友好,这里只能每一个元件配置项组件放在一个循环容器(x1,x2,x3…)中,然后将所有的元件配置项组件循环容器平铺到一个更大的循环容器y中,服务端将渲染模板处理成如下结构:
{ "code": [{ "title":"场景编码", "desc":"同一个场景同一个方案编码唯一" }], "remark": [{ "title":"场景备注", "desc":"场景备注仅为便于识别作用,无逻辑依赖处理" }], "matchExpr_items": [{ "title":"场景条件", "desc":"无条件或者命中条件时配置才生效" }], ...
}
然后遍历场景渲染模板的配置项,假设有n项,渲染循环容器y n次;每一次循环容器y渲染,仅渲染模板的同名配置项对应的循环容器x渲染一次,其他循环容器x不被渲染。
由于不同的插件配套工具具备不同特性和功能,无法统一进行模板和组件抽取,这里直接使用乐高3的页面搭建能能力,服务端根据需要进行相关接口提供。
比如内部链路分析页面,和其他页面均不一样:

6.2.2 接入BUC&ACL&AONE进行权限认证
6.2.3 服务端通用和个性化接口
关键点设计3:一个生态

7.1数据源桥接模式(RDB、Cache…)
女娲读写对应的数据源有SLS、Lindorm、Tair、Mysql、File等多种类型的数据源,同时,同一个类型的数据源多租户,多个实例管理,不同的场景按需选用。
所有数据源均配置在diamond上,dataId: nvwa.{dataSoureType}.instance.conf,配置项即为对应的链接元数据。
女娲数据源模块提供统一的消费模式。
StoreEngine实例化存储引擎。
7.2 打通监控链路(Sunfire、SLS…)
说明:Sunfire为监控服务,SLS为日志服务
路径:女娲Logger -> 本地File -> Sunfire,针对于每个插件提供标准的Metric埋点信息,打印到本地日志文件,最终在Sunfire以通用的模式进行监控,可根据不同插件定制,多租户多场景链路共享。

7.3 打通数据链路(ODPS、FBI…)
说明:ODPS为分布式文件服务(等价于HDFS),SLS为日志服务(等价于SLK中ES服务)、Blink流式计算平台(等同Flink), FBI可视化报表服务。
路径:女娲Logger(自定义Appender) -> MQ/SLS -> Blink -> Odps -> FBI -> 钉钉, 针对于各类实时,离线统计,通过现有通路进行加工处理,最终在FBI体现或推送钉钉群,可根据不同插件定制,多租户多场景链路共享。

7.4 打通排障链路(Robet、DingDing…)
路径:钉钉机器人 -> 机器人工厂 -> 女娲控制台API -> 女娲SPI,针对于日常运维各种问题分析链路,通过数据的泛化调用和编排完成信息的结构化组装与输出,可根据不同插件定制,多租户多场景链路共享。
和个性化接口
关键点设计3:一个生态
[外链图片转存中…(img-Dz4TtPxR-1697003772249)]
7.1数据源桥接模式(RDB、Cache…)
女娲读写对应的数据源有SLS、Lindorm、Tair、Mysql、File等多种类型的数据源,同时,同一个类型的数据源多租户,多个实例管理,不同的场景按需选用。
所有数据源均配置在diamond上,dataId: nvwa.{dataSoureType}.instance.conf,配置项即为对应的链接元数据。
女娲数据源模块提供统一的消费模式。
StoreEngine实例化存储引擎。
7.2 打通监控链路(Sunfire、SLS…)
说明:Sunfire为监控服务,SLS为日志服务
路径:女娲Logger -> 本地File -> Sunfire,针对于每个插件提供标准的Metric埋点信息,打印到本地日志文件,最终在Sunfire以通用的模式进行监控,可根据不同插件定制,多租户多场景链路共享。
[外链图片转存中…(img-I8POicFh-1697003772249)]
7.3 打通数据链路(ODPS、FBI…)
说明:ODPS为分布式文件服务(等价于HDFS),SLS为日志服务(等价于SLK中ES服务)、Blink流式计算平台(等同Flink), FBI可视化报表服务。
路径:女娲Logger(自定义Appender) -> MQ/SLS -> Blink -> Odps -> FBI -> 钉钉, 针对于各类实时,离线统计,通过现有通路进行加工处理,最终在FBI体现或推送钉钉群,可根据不同插件定制,多租户多场景链路共享。
[外链图片转存中…(img-vBrVTYSH-1697003772249)]
7.4 打通排障链路(Robet、DingDing…)
路径:钉钉机器人 -> 机器人工厂 -> 女娲控制台API -> 女娲SPI,针对于日常运维各种问题分析链路,通过数据的泛化调用和编排完成信息的结构化组装与输出,可根据不同插件定制,多租户多场景链路共享。
