百度代码配置化实践:配置化是业务架构三化之一

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

背景

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

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 次代码。但是配置的升级是千次以上。他们的基础也是配置的持续发布。

总结

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

引用

1. 本文对「服务」、「组件」的定义尊从 Martin Follower 的定义:http://martinfowler.com/articles/microservices.html

2. Roy Thomas Fielding 在 < architectural styles and the design of network-based software architectures >

4. Martin Flowler < Domain Specific Language >

5. Jez Humble / David Farley < Continuous Delivery >

6. Sam NewMan < Building Microservices >

7. Chunqiang Tang etc.. < Holistic Configuration Management at Facebook>

8. Frank van der Linden < Family Evaluation Framework overview & introduction >

发布了155 篇原创文章 · 获赞 313 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Ture010Love/article/details/104340379