业务配置化

https://blog.csdn.net/Ture010Love/article/details/104340379/
业务架构有三化——配置化、产品化、自动化,配置化解决业务系统灵活性、动态可变的问题,产品化解决工具复用提效的问题,自动化让机器工作、解决人力成本问题。本文来自百度刘志伟、韩炳涛两位同学对百万行配置化经验的分享,具备有一定总结的抽象性

背景
互联网软件市场是一个快速变化的市场,优秀的服务层出不穷,所以互联网软件公司需要快速推出服务抢占市场、并且能够快速响应用户的需求,否则就面临被淘汰的命运。这跟达尔文主义的观点是一致的:



It is not the strongest species that survive, nor the most intelligent, but the ones most responsive to change.



大型且成熟的互联网产品为了能保持适应快速变化的市场则尤其困难,比如百度某广告系统由百万级的 C++/JAVA 代码构成,上百人工作在上面,并且每天进行着大量的业务需求迭代。由于系统演化多年,代码实现常常耦合,代码上的迭代容易引发问题,相应的回滚处理都比较复杂。静态代码构成的大型系统在编译、重型的测试上都耗费很多时间。系统不断重启升级,也影响了对外提供服务,甚至会影响收入。为了能够使复杂系统仍然能快速应对变化,我们进行了一系列服务化以及配置化的实践。



从14年逐渐兴起的微服务架构是作为面向服务架构的一个特定方法,强调的是小而独立的服务,根本上还是对从领域里抽象成服务并进行管理。从快速应对变化的视角上看,微服务的技术异构性使得我们在面临不同的问题的时候,能够选择最适合的技术进行快速解决,以及能对服务进行组合,从而快速实现针对不同的客户提供不同的功能。除此之外,微服务在弹性、扩展性上等等都有很好的优势。此类的文章已经比较多,本文重点讲述配置化架构的实践。



什么是配置化架构
在软件系统中的配置通常指的是软件系统中的对象、对象属性、数据等,借助独立于软件系统的标准数据格式语言,比如xml、json、yaml,进行表达,从而能够在只改变标准数据格式语言且不改变软件系统本身功能的情况下,就能改变系统行为的方式。



应用配置的场景非常多,比如在百度用C++开发的搜索引擎控制模块,把用户查询的关键字通过消息,发送到后端存储服务上进行检索。查询消息的超时时间,以及消息在超时后需要重新尝试的次数,都是参数类的数值。我们可以通过C++代码里直接用常量表示,也可以通过配置的方式指定。他们的不同点是,一旦数值发生变化,静态代码需要做重新编译、重启升级、往往还需要通过繁重的测试以及很多的人工操作。配置为快速变更提供了可能,不需要做编译,也可以通过动态加载的方式避免系统的重启,甚至如果变更是安全可控的,繁重的测试以及很多人工操作都可以节省。再比如开关,借助于Google 的 Gflag,我们可以在运行时灵活的控制某个功能的关闭和开启。



架构有着非常多的定义,Roy Thomas Fielding 在 < architectural styles and the design of network-based software architectures > 把软件架构定义一种架构元素(组件、连接器、数据)的配置表示。这里配置的语义变得更宽泛了,已经不再是指单纯的标准数据格式语言(xml, json, yaml)类的配置,而是一种能够描述架构的语言。使用配置更宽泛的语义,配置化架构定义如下:



配置化架构是指可配置的方式构建软件的方法。它是在领域建模的基础上,以配置表述业务,以配置组织架构元素(服务、组件、数据),并对配置进行规范化、自动化的管理。



配置化架构的基础是对领域的抽象以及软件系统的高度的自动化。



优缺点
配置化架构的开发方式,相对于代码开发,无论是在服务的级别,还是在组件内部的级别,甚至是内部参数,都可以在不对软件功能进行变更的情况下,就能实现软件系统内的这些元素进行调整。配置化架构的优势是在较低的变更成本下,实现快速的调整软件系统。



然而,配置的校验往往是依赖于标准数据格式(xml、json、yaml)的解释器或者额外的 schema 工具,相比于编程语言,还是缺乏更严格语义上的校验保证;而且目前往往对于配置的编辑也缺乏业务级的测试,这些都可能引入bug。所以在实现配置化架构的时候,我们需要在校验、自动化测试、以及基础设施上做很多工作;而且实现配置化架构的时候,有时候还需要借助领域特定语言,这些都引入了额外的开发、学习以及维护成本。



平衡优缺点,建议还是从需求变更的频率的角度去考虑,在需要快速调整软件系统的情况下,实现快速以及高度自动化的配置化架构是非常有价值的。



技术基础
和微服务架构一样,配置化架构也不是被发明出来的,而是从复杂软件系统总结出的趋势或模式。它依赖于主要技术如下:



对于领域业务进行合理的抽象和划分。为了能够进行配置化管理,业务代码必须要进行业务领域的抽象和划分,需要面向对象以及领域驱动开发的技术。



为了能够对业务进行刻画,我们需要借助于领域特定语言(Domain Specific Language)。



配置化架构能够进行快速调整的基础是完备的自动化基础设施。传统的周期性发布无法满足持续、快速调整的需求,借助于 DevOps 的方法论,配置化的开发做到配置的持续发布。



架构评估
几年以前欧洲的几个大学和几家软件公司经过多年在实践中总结,从商业、软件架构、开发流程、组织角度(BAPO)提出 FEF 软件开发的评估体系,在软件架构上,配置化成为软件架构的最高级别。配置化架构是架构努力的方向。



实现模式
本章是在实践配置化架构时候,针对通用问题及其方案的总结,为在实践配置化架构的时候,遇到同类的问题提供实现参考。本篇文章采用了分类的方式进行总结,「风格」指的是从领域业务模型的角度上看,用配置去实现业务的三类方式;「语言」指的是在采用何种语言去实现配置化;而「基础设施」指的是基本的配置管理、发布流程等。虽然采用了分开论述的方式,但是实际上,应用配置化架构去解决问题的时候,是综合应用「风格」、「语言」以及「基础设施」的结果。这会在案例部分展现解决问题的全貌。



本篇文章介绍的是几类概要的实践,详细的方案以及更多的实现模式会通过后续文章持续发布。



风格



参数式



模型式



脚本式



语言



标准数据格式



领域特定语言



嵌入脚本语言



配置自动生成



可视化



基础设施



配置管理



持续发布



实现模式:风格
配置化架构的基础之一是以配置对业务进行抽象上描述,从这个角度看,配置化架构可以归纳为三类风格,参数式、模型式以及脚本式。



参数式



参数式的配置是表达业务的最简单的方式,也是最常见的一种形式。比如对于功能的开关、阈值、基础组件参数值(比如消息的超时时间)等等,通常采用简单的 K-V 形式进行表达。标准数据格式语言,如 XML,JSON,YAML 等等都是支持参数式配置常用的工具。



模型式



模型式配置化架构解决的问题相比于参数式更为复杂,它往往需要对领域业务进行建模,才能通过配置进行表达。



比如,当我们希望能灵活的对业务进行组合,要做到这样的事,我们首先需要对不同的业务抽象成不同的组件,分清楚哪些是业务组件要做的事,哪些是组件之间的关系。服务编排依靠内聚度与耦合度,强调服务的嵌套、松耦合性。这里说的组件拓扑指的的是将表达业务的组件和表达组件之间的关系进行分离。然后我们可以用配置表达组件的属性,以及组件拓扑,好处是能快速调整组件的属性以及关系,进而能灵活的组合不同的服务、组件完成不同的业务。



以配置表达的拓扑关系,为了满足更多特定的业务,往往需要借助于领域特定语言,增加丰富的语义。



以离线的数据处理为例,期望能做到特征抽取,特征合并,用户属性管理等等业务进行组合,对于不同的输入请求,需要完成的业务不同。所以这些业务代码,被抽象成组件:负责特征提取的组件、负责合并特征的组件、负责用户属性的组件等等。每个组件都有自己独立的属性配置,不同组件的以树状关系进行组合。



在应用领域特定语言的基础上,把配置具备了更丰富的语义,能表达引用、默认值、覆盖等语义后,系统可以按照拓扑树的结构,通过反射的方式初始化内存中也是树结构的组件,每个组件会加载自己的配置。这种配置化的方式可以快速调整组件的属性,也可以快速调整组件的组合方式,达到灵活的表达业务的效果。



脚本式



当业务不属于参数式和模型式的时候,比如业务代码各式各样,无法用抽象的配置语言统一表达,我们可以通过脚本语言实现配置化。一般来说都是指在静态语言里嵌入了动态语言。



以展示广告为例,UI 需要在不同的请求处理地方进行实验(实验是通过使得不同的人看到不同的展示广告,根据效果决策出更好的广告展现),然而实验的增加、变更、删除都是极其频繁的变更。静态代码在处理实验代码的时候变得很庞杂,并且也不能快速进行实验的变更。因此我们使用了 luajit ,通过 protocol buffer 和 UI 模块进行交互,完成实验的逻辑。



实现模式:语言
在明确了哪类风格的配置化架构后,我们需要去选择合适的配置语言去解决相应的问题。比如常见的是采用标准数据格式语言,比如 xml, json 或者 yaml 等。或者采用可视化的界面进行配置的编辑。本文介绍使用领域特定语言以及代码生成配置。



领域特定语言
当我们希望能以配置表达更多的需求,比如表达逻辑语义,常见的配置使用方式就无法满足了。我们需要在配置上构建更多的语义来表达业务。



领域特定语言(DSL)是指针对某一个特定的领域,设计实现出具有受限表达性的语言。DSL 的优势在于,在特定领域下,更能直观、自然的去描述业务的方法。



我们可以创造出新的语言表达需求。但有时候我们也会在标准 yaml、json、xml 等语法上,针对特定的需求,进行封装,使配置表达更多的业务。业界有很多这样的案例,比如 IBM 的 DB2 JSON Library 给 JSON 增加了 query 语义。



仍然以实验为例,为了使得实验本身的信息,尤其是 condition、action 进行直观的表达、灵活的组合以及快速修改,避免了之前用 C++ 代码更新的方式。给 yaml 增加了相应的表达语义。



4955:



    OTHER_INFOMATION: description



    CONDITIONS:



        - $ SIGN(user.domain) in TEXT_FILE(‘domain_blacklist.txt’)



        - $ user.is_lu == 1



    ACTIONS:



        - $ pb.dcpub.noad = se_lib.NOAD_REASON.DROP_FLOW



        - $ ->FLAG_get_lu2_from_prediction_service = 1



 



配置自动生成
采用配置化的方式进行开发,配置规模会逐渐变得很大。我们需要确保编写与维护配置的成本要比编辑代码更简洁。虽然说配置的更新相比于代码更轻量级,然而代码相比配置,具备更好的模块化以及可重用性,使得代码在大规模的情况下仍然可以得到很好的管理。



结合配置与代码的各自优势,我们可以把配置当成代码进行管理:以编程语言(比如 Python)生成配置,实际上配置变成方便程序识别的中间产物,RD 管理的是生成配置的代码,利用代码的模块化以及代码重用性,避免配置的维护成本更高。



用户基于定义好的、可复用的对象以及 Python 函数,编写自己的 Python 函数,填写自己特定的配置值,完成后调用 compiler 生成配置。



案例
在离线任务部署的时候,每个部署任务都需要从一份配置模板生成自己特定的配置。配置的模板通过 Thrift 对象以及 Python 函数进行定义。下面是以这种方式生成一个组件的例子。



实现模式:基础设施



配置管理



大部分的配置并不需要额外的管理操作,配置文件存储在 SVN/GIT 等版本管理工具上,配置内容也都不需要额外的逻辑控制,我们可以称这种简单的情况为「最简单的配置管理」。然而在配置规模变大或者需要对配置内容进行控制的时候,需要就更好的配置存储管理。



从整体上考虑配置管理的功能时候就会发现,可以按经典的三层架构模式去理解配置管理,配置管理需要:用户交互层、业务逻辑控制层以及存储层(数据访问层)。



「最简单的配置管理」是情况是:用户交互层提供给用户进行交互的是纯配置文件而不是通常理解上的可视化界面,并且由于不需要额外的配置内容逻辑控制,所以没有业务逻辑控制层。但它仍然是一种配置管理。当我们需要更多的功能时候,我们可以在「最简单的配置管理」上进行增加。



这是在展示广告系统构架的三层架构配置管理,主要的内容是:



提供 web UI,让用户可视化的编辑配置内容,与查看各种统计等功能。UI 到后端的逻辑控制层采用 restful API 进行交互



在配置的逻辑控制层,我们对配置的内容进行基本校验、统计、存储等逻辑控制功能



在后端存储,由于要复用 SVN/GIT 的版本管理功能、冲突检测合并功能、用户权限控制等功能,并且避免维护独立的Sql类存储服务器的工作量,我们采用 SVN/GIT 作为后端存储管理。



持续发布
配置化架构所期望的目标是能快速对软件系统做出调整。然而有时候即便是软件本身的配置能做出快速调整了,但是在传统的长周期发布方式下,除了要进行等待,对于缓存的很多变更,需要更复杂的测试以及一旦出现问题,排查以及回滚都会非常困难,这些情况都导致了不能达到快速变更系统的目的。



持续发布是配置化架构的一个基础。配置的变更不会缓存、积压起来,而是会触发包括测试、部署的流水,进而发布到线上。即使中间有错误由于变更被拆的很小会被快速定位以及能做到快速回滚。



这是一个在展示广告系统持续发布的示意图。从存储出发,一旦Jenkins/Hudson 检测到配置的 SVN/GIT 变化,就会触发 Jenkins/Hudson Job 流水线。目前我们的 Job 流水线主要有 2 类,首先要构建配置的 package,然后进行轻量级的自动化测试进行校验。我们甚至可以对配置进行分类,对于特定的的配置变更,比如对于不断调整的参数值,只经过基本的校验就可以直接部署到线上。



除了百度,我们从 Facebook 的论文了解到,Facebook 可以做到每天升级 2 次代码。但是配置的升级是千次以上。他们的基础也是配置的持续发布。



总结
本文总结配置化架构的技术进行了概述,并且总结一些实现上的模式。更多的模式实践,以及应用配置化的案例,会发布在后续的文章中。



问题
在很多业务应用中,往往有很多文案及按钮的业务逻辑,很容易因为产品的策略变更而变化,或因为来了新业务而新增条件判断,或因为不同业务的差异性而有所不同。如果通过代码来实现,通常要写一串if-elseif-elseif-else语句,且后续修改扩展比较容易出错,需要重新发布,灵活性差。 可采用配置化的方法来实现按钮逻辑,从而在需要修改的时候只要变更配置即可。业务逻辑的代码形式一般是:



public Boolean getIsAllowBuyAgain() {
if (ConditionA) {
return BoolA;
}
if (ConditionB) {
return BoolB;
}



if (CondtionC && !CondtionD && (ConditionE not in [v1,v2])) {
return BoolC;
}
return BoolD;
}
本文讨论了三种可选方案: 重量级的Groovy脚本方案、轻量级的规则引擎方案、超轻量级的条件匹配表达式方案。



方案
Groovy脚本
需要在界面上进行编辑和及时刷新到应用中的代码,可以使用 Groovy 脚本来替代。



可参阅:“使用yaml+groovy实现Java代码可配置化”



规则引擎
多样化可变的业务逻辑和规则集合非常相似,可以考虑采用一款轻量级的规则引擎。通过配置平台来管理规则集合。



使用规则引擎的示例可参阅: “Java Drools5.1 规则流基础【示例】(上)”



可选用一款轻量级的Java开源规则引擎作为起点。



条件表达式
对于轻量级判断逻辑,采用条件表达匹配。条件表达匹配,实质是规则引擎的超轻量级实现。



条件表达式方案可参阅:“详情文案的轻量级表达式配置方案”



选择
三种方案的对比如下:



方案 灵活性 性能 易懂性 适用场景
Groovy 脚本 极高,凡是代码解决都能用Groovy脚本解决 需要缓存,几十到几百毫秒 业务人员不易读懂 代码配置化,方便技术人员使用
规则引擎 较高 几十毫秒 适合业务人员读懂 大量规则和推理,规则之间有关联
条件表达式 特定 几到几十毫秒 适合业务人员读懂 少量复合条件,相互独立



SaaS软件在实际部署使用过程中势必需要面对各类型租户,租户需求千差万别,为了最大程度满足使用,构建的SaaS应用需要实现最大程度的可配置。前面已针对数据可配置、界面可配置、功能可配置进行详细描述,现再详细阐述流程可配置。



基础理论
抽象业务流程,将业务流程的流转看做是一个流水生产线。包含三种核心概念,分别是:原材料、通道、加工、原材料在事先配置好的通道中流转,经过多处加工最后得出预期的产品。



原材料可看成是原始数据,通道看成是数据关联,加工看成是一个一个的服务。原数据通过数据关联连接对应的服务,其中服务包含三要素:输入(I)、输出(O)、操作(A),一旦原始数据符合数据关联要求,就可顺利通过I流入,对应的A将会依据定义好的逻辑对原始数据进行处理,最终数据从O流入。



整套流程可通过多套数据关联链接起来,原始数据经过一步一步处理,最终将会被加工成预计需要的结果。



设计原则
整个流程化设计原则是:组件组装,将业务流转过程中涉及的核心模块拆分成组件,流程可配置化的过程就是对整个服务流程组件进行生产和组装的过程。



结合是实际业务场景,对应的组件可划分为5大类,分别是:服务、关联、规则、节点、约束与依赖。




  1. 服务



服务的定义包含三个模块,分别是输入、操作、输入。其中操作属于核心模块,定义了该服务所要执行的具体操作。整个服务体可概述为可重用的软件模块,可以被看出是不可分割的功能体,如果有看过《SaaS可配置化-功能可配置》就会知道,其实服务的对应的就是系统的“原子功能”。




  1. 关联



关联最重要的作用就是连接规则与服务,通过关联将不同功能的服务串联起来,进而实现业务数据流的流转。




  1. 规则



规则用于对数据进行判断,并依据判断结果来选择下一个关联。由于示意图可看出:整体由三部分组成,条件、出分别是输入、口。根据条件选定对应的出口,出口再与关联链接,进入完成业务逻辑的流转




  1. 节点



节点的引入是为了支持并行时序,多任务并行,通过对应的关联汇集到设定的节点中。任务间具有一定的时序关联,执行完一个任务后,同时开启若干个任务,它们都完成后再触发后续任务





  1. 约束与依赖
    约束针对SaaS模式多租户情况提出,在实现流程可配置时,需要添加约束也就是隐性条件,确保各租户间数据的隔离。依赖描述的是规则与规则之间,存在数值与逻辑互为条件或不可分离的情况。




  2. 解决方案
    上面有对流程可配置的基础理论和原则进行详细的阐述,下面结合实际场景对流程可配置产品的使用过程做一定的描述。




  3. 创建节点
    在这里定义的节点需要区分设计原则中的节点概念,这里的节点更多是针对前端用户定义的,其基础含义就是数据在流转过程中需要经过的各个任务阶段,在设计SaaS过程中需要注意节点有对应负责人,操作及数据可见权限。





例如:针对一个审批节点,在配置流程过程中需要配置具体的审核人员,是否具有“通过”,“退回”操作,是否可查看,编辑审核列表中的某些数据项。



节点类型:



在SaaS产品设计中,成功创建节点后,还需要考虑提供租户对节点权限进行设置。常见的节点权限设置往往通过限制该节点负责人,对节点包含字段的操作权限来实现。



例如:对于一需要提交的表单,管理员可通过设置其中字段为“可见”、“可编辑”、“隐藏”进而实现权限的控制。




  1. 添加流程
    开始介绍的创建节点,针对具体使用场景。节点创建完毕后自然而然是添加流程操作,进而实现流程的可配置化。一直描述的流程其实数据流转的方向或途径,租户在使用SaaS过程中会产生文档/产品/财务数据/项目/任务等数据,这些数据只有通过流程才能一一串联起来,进而实现应有的价值。



在实际设计过程中,可通过设计三部分:流程节点、分支和权限进而实现添加流程操作,其中流程节点和权限已介绍。



分支的主要作用是确定数据的流向,在实际业务场景中,需要依据不同的条件流向不同的节点,例如:在财务审核中,小于10000,财务经理审核,大于10000财务总监审核。这个时候,可以以1000作为分支流转的判断条件进而实现数据流向的可配置性。



当然,分支流程的核心设计点在于实现分支判断条件的灵活性。因为针对不同的业务场景,需要不同的对比判断条件,包括数值对比,逻辑判断等


Category architect