作者 | 王悦凯
导读
从事B端业务系统研发多年,不免会有这样的思考:B端系统的技术挑战是什么?什么样的业务架构算好架构?本文结合百度合约广告业务的发展历程,介绍广告投放平台从单体架构到微服务架构演进过程中碰到的问题和思考。希望通过本文的介绍,让大家更全面的理解B端系统的技术挑战。
全文11653字,预计阅读时间30分钟。
一、背景
1.1 合约广告概念
合约广告相比竞价广告,最大的特点是预先约定好广告价格,即价格是预先确定的。基于这样的特点,合约广告的投放流程大致可以概括为四个步骤:询价 -> 下单 -> 投放 -> 结算。
-
询价,销售根据客户的营销目标选择合适的营销产品,并提交具体的投放定向和时长,系统结合销售政策,自动计算产出各资源价格
-
下单,客户按照 1 中的报价结果,确认需要购买的资源和具体的付款方式,完成下单后生成订单,每个订单的金额是确认的
-
投放,2 中的订单完成审批后,系统按照事先约定的价格进行广告投放,客户在投中可通过系统进行投放和创意的管理,并可以实时获取投放数据
-
结算,合约广告按照投放模式分为按时间计费和展现量计费,两种计费方式的频次都是天,最终实现在投放周期内逐步地扣除订单全部金额。按天结算是对已产生投放部分的权益保障,毕竟合约广告存在更改甚至退款的场景。
通常来说,合约广告这种广告采买方式更适合品牌展示类广告,效果类广告更多会采用实时竞价。相比竞价广告,合约广告在业务流程上更重投前。下面结合品牌广告业务发展的三个阶段,介绍下投放平台的变化历程。
1.2 业务发展
第一阶段,品牌广告业务高速发展期
2011年,品牌专区产品开始平台化售卖,并逐步建立品牌投放平台:锦囊。在2013-2018 6年间,品牌业务高速发展,诞生了Ax、Bx、Cx等10+条产品线,虽然锦囊平台做为投放的统一入口,但各产品线的投放能力仍旧是独立建设,拥有各自的独立投放平台。在这种模式下,快速灵活地应对了市场的变化和需求,但同时也造成了投放平台多,业务流程割裂的问题。
第二阶段,寻求产品整合和流程统一
2019年开始尝试对合约类平台进行整合,统一各产品售卖流程,包括产品线关停并转、投放平台融合、账户统一、资金池统一、标准化下单等项目。逐步落地合约广告一站式平台的建设 - 天启平台,真正实现合约广告的标准化投放流程,提升规模化投放效率,支撑业务发展。在这个阶段,各广告的投放平台入口逐步收敛,实现了操作入口的统一。
第三阶段,满足复杂营销场景,整合营销
随着合约整合售卖(根据营销场景选择不同广告产品的组合售卖方案,即同一个合同下可下单多类广告,并享有对应的优惠政策)整体趋势的加速增长,原本非标断点式的支持方式已经无法满足业务的增长,平台化的解决方案是整合售卖规模化的必要条件。从2021年Q3起正式启动,天启平台正式定位:统一的合约产品整合售卖平台,满足从简单场景到复杂营销场景的全投放链路服务能力。从技术视角看,旨在通过一个平台(天启),实现多资源广告售卖场景下的统一,包括一个流程、一套账户、一套资金体系、一套投放表达。
1.3 架构演进
对应上述业务发展的三个阶段,合约平台的技术架构也经历了多个版本的演进,浓缩后,可以概括为2大类,2019年前的单体架构和之后的微服务架构。
单体架构
2019年前的单体架构(简称『1.0架构』)图如下所示,整体上分三层,1)统一入口层,提供用户权限管理功能;2)各类广告投放平台独立建设,服务独立部署;3)抽象并沉淀基础工具库,避免各投放平台的重复开发。
△1.0 烟囱式架构
品牌 1.0 架构本质是一个烟囱式架构,各产品线通过独立平台进行投放,开发、测试、上线互不影响,都能做到产品线维度的隔离,包括开发规范和迭代周期。这种架构配合当时品牌团队的组织架构,在品牌产品野蛮式生长的初期起到了很重要的作用,成功孵化了一些创新产品。但是烟囱式架构一个很大的弊端就是『信息孤岛』,随着业务的发展,各投放平台发散式迭代,逐步形成了一个一个的孤岛。从技术视角看,各平台大流程相似,但在实现细节上都不同,更严重的是,存在大量的重复性建设工作,从维护成本和人效上都有很大的优化空间。从业务视角看,各产品的打法也是各自为阵,横向缺少联动,没有形成1+1>2的局面,缺少整体视角的规划。
微服务架构
业务方面,合约广告场景化营销和精细化服务的需求显著;团队方面,各合约类广告平台深度融合,孤岛边界逐步打破。原本烟囱式架构无论在业务复杂度的应对,还是服务稳定性及质量的保障,亦或是研发效能的提升,都已经遇到了瓶颈。基于软件设计的最大目标:『设计符合业务的构造定律的演进方式,一种可以以最小的开发维护成本, 使业务更快更好的流动发展的方式』,19年后,合约平台架构逐步向微服务架构演进,以领域驱动设计为准则,按照合约广告的领域模型划分微服务,构建了业务前台、业务中台、技术组件、基础设施的四层业务架构。另外,基于消息机制解耦各服务,辅以组装式业务流程的理念,拆解各种繁杂的业务流程,抽象并沉淀系统的 meta feature,灵活构造多种差异化的业务解决方案,提升系统的『可玩性』,从架构层面管理了系统风险和业务复杂度(百级别不同类型的售卖和投放场景)。
说到这,有些人会有疑问,对于品牌业务,微服务架构演进是否是必须的呢?是否存在过度设计?
当然,假如不考虑性能、健壮性、可移植性、可修改性、开发成本、时间约束等因素,用任何的架构、任何的方法,系统的功能总是可以实现的,项目总是能开发完成的,只是开发时间、以后的维护成本、功能扩展的容易程度不同。从后验来看(包括系统可用性、扩展性、灵活性三方面),采用微服务架构的决策是正确的,或者说利远大于弊。
在架构的演进过程中遇到了很多技术挑战,后面的内容重点挑了几个方面进行详细阐述。
二、技术实现
2.1 服务架构
首先,整体看下合约广告平台的微服务架构,如下图:
△合约广告平台微服务架构
总共分为四层,业务前台、业务中台、技术组件(PAAS)、基础设施(IAAS)。
- 业务前台,总共分三个模块,天启平台、运营平台、业务前台。
-
天启平台:面向广告主,提供合约广告询价、资源预订、资源下单、投放设置、物料制作等核心能力,支持品专矩阵、展示矩阵、品牌全景3大类产品矩阵,多达300+广告资源的售卖。
-
运营平台:面向销售和内部运营,帮助他们精细化运营广告售卖各阶段,目前售中的投放干预和运营管控能力建设相对完善,在开屏大促和品牌广告日常运营工作中大幅提升操作的效率和安全性,让原本一些『非标』操作通过平台能力让运营实现自助化。
-
业务前台:主要两个作用,1)对天启平台和运营平台的公共部分进行抽象并下沉,避免重复的业务逻辑和流程散布在两个模块,减少开发和运维成本;2) 对各业务中台的公共部分进行上抽,统一沉淀至此模块,比如批量处理,让各业务中台的职责更加纯粹,关注自己领域的建模。另外,针对跨多中台的读写逻辑也统一在这一层实现,起到业务流程编排的作用。
-
业务中台,按照合约广告的业务流程和领域知识,划分各业务服务,总共分为9个业务中心和2个技术中心,每个业务中心都围绕特定的业务领域展开,业务边界非常明确。需要注意的是,9大业务中心仅仅是从业务视角进行定义和划分,并不代表每个中心就只有一个服务。基于技术架构和业务职责,每个业务中心可以继续划分为多个模块,例如投放中心又被划分为 检索适配服务、物料审核服务、实验投放服务等。另外,服务之间严格划定上下游关系(按照响应数据流向定义上下游概念),下游服务可以调用上游服务,上游服务严禁调用下游服务,上游服务的变更对下游服务产生影响必须通过领域事件(异步)的方式来实现,避免服务循环依赖(在服务治理章节中会展开介绍)。因此在整体系统中大量采用异步消息的方式,将业务实体的状态变更视为事件,驱动上游服务,虽然增加了测试阶段的复杂度,但是有效保障了服务间关系的有序性。
-
技术组件,微服务架构后,各模块的代码库也是独立的,对于一些公共能力,如何能够让各服务低成本、无门槛的接入,从而达到统一各模块业务解决方案的目标,减缓业务架构的腐化和熵增。鉴于此,通过 springboot starter 的方式构建了品牌团队的业务中间件 brand-starters,成功收敛了30+模块的实现细节,在系统稳定性和架构健康度方面做出巨大贡献,包括消息发送和消费、异步事件处理、异常报警、分布式锁、分级缓存、常用工具及辅助类等。
-
基础设施,主要指部门级别的基础设施和中台,比如微服务解决方案 ,包括RPC框架、注册中心、全链路追踪、服务网关、虚拟化容器部署、FAAS。
2.2 模块结构
上一节从整体视角介绍了合约广告平台的业务架构,这节从微观视角介绍每个中台的业务架构。各业务中台采用多模块的代码结构,具体模块划分如下:
各模块的依赖关系如下:
△模块结构
值得注意的是:
-
各模块间的依赖关系,demo-web、demo-task、demo-consumer 做为顶层模块,非常薄,这一层的主要作用是独立发布及部署,实现应用级别的隔离。顶层模块调用 demo-app,app模块作为服务的实现,存放了各个业务的实现类,主要分command和query(CQRS 理念)。demo-app 可以调用 demo-domain,也可以调用基础设施层 demo-infrastructure,domain做为领域层主要分3块,model 领域实体,可以是充血模型(DDD的概念);ability 领域能力,是领域对外暴露的服务能力;gateway 领域网关,主要是接口定义,这里的接口可以粗略的理解成一种SPI,也就是交给infrastructure层去实现的接口。
-
demo-client 是服务对外透出的 API,API 的实现存放于 demo-app 模块。出于运维考虑,或者读写分离的设计,可以方便的将需要独立部署的 API 从 app 模块上抽至顶层,做为独立模块进行流水线发布和部署。实现服务的隔离部署,同时又能获得共享同一个代码库的好处。
-
前面提到的domain和infrastructure层的依赖倒置,是一个非常有用的设计,进一步解耦了取数逻辑的实现。domain层依赖接口,不感知具体实现,例如 CustomerGateway 里定义了接口 getByById,要求 infrastructure 的实现类必须定义如何通过消费者Id获取消费者实体信息,而 infrastructure 层可以实现任何数据源逻辑,比如,从 MySQL 获取,从 Redis 获取,还是从外部 API 获取等等。
-
领域与功能的分包策略,每个模块的分包策略都遵循准则:先按照领域分包,再按照功能分包,这样做的其中一点好处是能将腐烂控制在该业务域内。比如消费者 customer 和订单 order两个领域,在 domain 模块下先分成 customer 和 order 两个包,然后再按照 model、ability、gateway 进行功能划分。假设 customer 和 order 是两个后端开发并行开发,两个人对于 dto,util 这些文件夹的命名习惯都不同,那么只会腐烂在各自的业务包下面,而不会将 dto,util,config 等文件夹放在一起,极容易引发文件冲突。
通过上面的应用架构划分,统一了各业务中台的分模块和分包策略,成功降低了各模块的学习和上手成本,较好控制了业务架构和模块代码的腐化程度。同时通过顶层模块的划分,灵活实现了同一代码库多应用部署的能力,从物理层面隔离web服务、定时任务、消息消费、rpc服务,使服务具备灵活按需扩展的能力,进一步提升服务的稳定性。
2.3 服务治理
微服务架构后,服务之间的交互通过网络进行而非单体时代的内存方式,所以整体来说系统会变得更加脆弱,服务治理就是为了解决此类问题。常规的服务治理有四板斧:第一,一定要设置服务调用的超时时间;第二,要考虑重试的逻辑;第三,考虑熔断的逻辑,不要被下游拖死;第四,一定要有限流的逻辑,不要被上游打死。即超时、重试、熔断和限流。没错,这四板斧的确是服务治理中的常规手段,但在我看来,服务治理并不局限于此。我们以终为始,看看服务治理的终极目标是什么?我认为主要3个方面:服务可用性、系统可观测、架构防腐化。
可用性
服务可用性的建设包括系统性能优化、服务自愈和自检能力建设。
服务性能
1.微服务框架升级
服务 RPC 框架整体迁移至部门最新的云原生解决方案 gravity+starlight,取代了原先的 zookeeper+stargate。在服务性能和治理方面有了大幅提升:
-
通信性能提升:starlight采用新版的网络通信库与更灵活的线程池模型。提升2倍通信交互性能
-
稳定性增强:starlight基于gravity做更高效稳定的注册中心,定制无损升级、异常实例摘除等能力。轻松赋能达标99.99%+稳定性。
-
跨语言能力:starlight采用设计更合理的brpc协议作为主协议,可跨语言与C++ Go等brpc服务通信。降低跨语言通信学习成本。
-
云原生治理:starlight+gravity支持更云原生的治理能力,接入统一控制中心增强可控性、实现灰度发布。
-
日志规范排查提速:规范化的日志。秒级定位超时问题、序列化失败问题
迁移过程中,gravity 与 zk 双向同步服务注册信息,实现业务的平滑无感迁移。
2.系统性能深度优化
对合约广告系统进行了全面的性能盘点和优化,性能整体提升2倍,总结为如下7个优化点:
-
单元化,对单个服务进行拆分瘦身(模块结构章节中提到的顶层模块,将定时任务、消息消费与web服务剥离),实现资源隔离,降低等待获取连接的耗时
-
网络传输,统一团队服务部署至北方机房,平响降低60%
-
循环IO,IO 包括远程服务调用和数据库操作,通过批量化改造、多线程并发的手段大幅缩短耗时
-
多级缓存,通过本地 + redis 构建二级缓存,大幅降低读接口耗时
-
异步化,对于耗时的写操作,采用eventlog组件实现异步化 + 自动重试(后面一节会详细展开),例如物料送审、批量创建投放等
-
慢SQL,基于业务查询场景,优化数据表索引定义
-
接口拆分,联合前端,对于非主干流程的耗时数据进行接口拆分,实现非阻塞请求
服务自愈和自检
为了更好的保障系统的可用性,服务需要具备自愈和自检能力。服务自愈能力主要通过幂等处理 + 事件持久化 + 逻辑重试来实现,通过自愈能力的建设,可以大幅降低微服务架构升级后带来的 IO 通信不稳定问题,同时让系统对性能和稳定性较差的外部服务有更好的容忍性,有效提升系统的整体可用性。系统的自愈能力主要解决的是『偶发性』技术问题,比如网络抖动导致的远程调用失败、事务并发导致的乐观锁冲突等,对于代码bug、业务逻辑错误、数据不一致等问题无能为力。所以除了自愈能力,服务还需要具备自检能力,能够将错误自动检测出来,并及时通知到对应人员进行处理。
服务的幂等处理这里简单提一下,不做过多展开,主要有几种方案:
-
数据库唯一键,通过数据库的约束实现新增和删除操作的幂等。
-
数据库乐观锁,通过增加额外字段(版本号)实现更新操作的幂等。
-
防重token令牌,调用方在调用接口的时候先向后端请求一个全局ID做为token,后续请求的时候都带有此token(建议放到 Headers 中),可以解决客户端连续点击或者调用方超时重试等情况,适用于新增、更新和删除操作
-
下游传递唯一序列号,与3不同的是,这个唯一序列号由调用方生成,生成方式可以是无业务含义的全局ID,也可以是带有业务含义的ID,比如订单行ID。服务方通过此ID记录进行存在和不存在的操作(结合redis实现操作的记录),适用于新增、更新和删除操作
自愈能力 - eventlog 基础组件
利用 springboot-starter 机制沉淀 eventlog 基础组件,让业务方低成本接入,使服务具备自愈能力。组件整体架构图如下:
△eventlog 基础组件
步骤1是同步持久化事件,由业务方引入组件后调用现成方法实现;步骤2是异步消费处理事件,整体处理流程采用模板模式+ 策略模式,通过模板模式实现处理流程的业务编排,对事件处理前、中、后都设置扩展点,兼顾业务模块的接入成本和扩展灵活度。事件处理采用策略模式,将事件类型做为策略路由,实现各类事件处理的互相隔离和高扩展性。另外通过可视化配置(amis配置,即爱速搭,低代码前端页面搭建平台),统一监控事件日志表,对超时处理的事件进行报警、清理等操作。
事件日志实体模型采用 event_type + biz_code 的二级模型,event_type 唯一标识了一类事件,是事件处理时的策略路由key,biz_code 由业务方自定义,用来唯一标识某类实体。attach是扩展字段,供业务方自定义,attach不宜过大,如果过大,建议通过biz_code后反查业务数据。
自检能力 - 核检中心
服务的自检能力主要通过核检中心来实现,核检中心功能上分两部分,核检任务 + 消息中心。核检任务按业务场景对数据进行核对校验,解决微服务架构后分布式事务造成的数据不一致,同时,端到端的核检也可以做为在线监控,第一时间感知系统bug;然后通过消息中心将异常通知到对应人员。同时,面对大量的报警信息(系统错误、数据一致性、业务正确性等),消息中心做为统一的管理端,提供了按场景分组报警、历史报警信息查询、报警优先级设定、误报热屏蔽、报警群进组热修改等功能,提升监控的有效性和处理效率。
核检中心整体模块图如下:
△核检中心模块图
-
应用层,两块主要功能,客户端接入和可视化管理。
-
客户端接入,对于 springboot 应用,和 eventlog 组件相同,通过引入starter,低成本实现报警消息的通知(声明式注解和命令式sdk两种方式)。
-
可视化管理,通过消息中心控制台,将散布的核检任务进行收拢,大幅提升日程维护效率。同时对于历史消息,可以进行查询,做到消息的可回溯和追踪。
-
能力层,对应核检中心服务端,整体模块结构遵循上述提到的 COLA,并使用CQRS和事件驱动的设计理念,提升模块的可用性。在消息发送事件的处理模块中,采用策略模式实现多种类型的消息发送,大幅提升模块扩展性,遵循开闭原则。
-
实体层,按照十亿级别数据量进行模型设计和数据库选型。在实体设计中,抽象消息场景的业务概念,较好的聚合了一类共同特征的消息,『共同特征』完全交由业务方定义,很好的平衡了业务灵活性和消息可管理。在数据库选型方面,采用部门开源解决方案BaikalDB(一个分布式可扩展的HTAP存储系统,采用类spanner的架构,支持PB级结构化数据的随机实时读写)。
可观测
系统的可观测程度也是服务治理的一个非常重要目标。提到服务的可观测性,大家容易想到的一般都是:微服务调用关系(拓扑图呈现)、接口性能(各种分位值指标,性能优化利器)、系统异常(各种监控配置)、资源利用率、日志链路追踪(线上排查必不可少)等,这部分主要依托于部门的微服务解决方案 Jarvis平台实现,所以在这里不做过多介绍,这里主要想介绍的服务可观测主要是指上层业务应用。
企业级微服务解决方案Jarvis是商业平台部基础技术团队研发的面向复杂业务系统的应用托管平台,为商业应用提供高可用和分布式的微服务架构解决方案。Jarvis提供了一系列强大的功能,充分利用百度资源虚拟化能力,提供分布式服务框架、服务治理、统一配置管理、分布式链路跟踪、容量规划、高可用及数据化运营等功能。
前面其实已经提到过,合约广告的整体业务链路比较长,经历了售前询价 -> 下单 -> 合同审批 -> 物料制作 -> 投放 -> 计费,每个环节都可能成为广告投放的卡点,如果出现问题,比如流程阻塞,如何能够快速定位问题,甚至提前感知问题,做到整个流程的透明化、可观测呢?
整体思路是以业务实体为中心,记录实体全生命周期的变化,当某个阶段(实体状态)停留时间超出预期,就有可能存在潜在的异常,通过服务看板和消息中心,及时通知对应的运营进行处理跟进。通过追踪业务实体的生命周期,实现系统业务流程的可观测性,为后续流程优化、业务提效提供数据分析基础。
业务实体生命周期的追踪实现方案主要如下图:
△业务实体生命周期追踪
总结来说就是三种方案:
-
通过订阅 mysql binlong,监控业务实体的核心状态字段(对应部门已有解决方案 WATT),一旦字段变更就会触发增量消息,服务端消费后持久化,再以服务看板的形式对外呈现。这种方式的优势是对业务模块无侵入,劣势是依赖业务模块的数据库设计,另外有些实体的生命周期并不一定体现在数据表的字段变更,无法通过订阅 binglog 的方式感知
-
通过日志打印 -> 采集 -> 解析的方式实现实体全生命周期的追踪。这个方式的优势是百度已有一整套现成的解决方案,可以直接复用;劣势是需要侵入业务代码,按照规范打印日志,同时日志采集和解析的配置门槛较高,调试较困难,不容易上手
-
提供用于追踪实体生命周期的埋点 sdk,在需要监控的地方埋点。这个方式的优势是灵活度非常高,可以实现任何追踪需求;劣势是侵入业务代码,需要业务模块显式进行调用
最终我们采用了 1 和 3,业务实体的追踪主要通过方案 3来实现埋点,一些额外辅助信息通过 方案 1 进行同步。
防腐化
很多科学家提到的熵增定律非常好的揭示了自然现象的本质:任何孤立系统,在没有外力作用的情况下,其总混乱度(熵)会不断增大。当然软件系统也是如此,随着软件系统的功能不断增加,系统的混乱度也在不断增大。为了降低软件系统混乱的速度,必须要对其施以外力。那么这里的『外力』可以从哪些角度入手呢?
-
分析每个应用的修改频次,哪些应用频繁修改,哪些应用相对稳定。对于频繁修改的应用,是否引起修改的业务需求是相同的。那么这些大概率绑定在一起修改的服务被拆分在不同的应用,是否是不合理的,值得进一步商榷。如果一个应用,无论什么需求都需要升级,那么这个应用是否已经足够小,是否需要进一步拆分,剥离变与不变,将不变的部分进行抽离和沉淀?
-
定期观察每个服务的调用情况,包括次数、性能和拓扑。是否存在一些冗余服务可以清理?是否存在一些服务性能呈现恶化趋势,需要及时介入优化?是否存在某些业务流程中存在重复调用的情况?如果有,需要制定计划进行治理,否则就会成立历史债务,让业务架构逐步腐化。
-
需要定期去扫描检查应用代码,包括重复逻辑是否散布于多个应用、是否存在不规范的代码逻辑(每个团队根据实践总结沉淀,比如 枚举类的使用、IO 循环调用等)、是否存在冗余代码需要清理、模块结构和分包策略是否符合规范
-
定期的CR各应用代码,是否存在跨领域的逻辑,比如报价中心处理了订单中心的逻辑(非报价域的逻辑),导致微服务的划分边界模糊
防腐化这块工作其实很重要,目前也还处于初级的摸索阶段,需要进一步结合业务实际,沉淀最佳实践。这里先以微服务循环依赖治理为例,做个简单介绍。
当微服务中的循环依赖形成闭环后会造成2类主要危害:
-
服务间强耦合导致各服务很难独立部署,违反了微服务『自治与隔离』的设计初衷,最终微服务架构会逐步演变为『分布式大单体』,失去了微服务架构演进的意义。
-
循环依赖可能会导致一些循环调用或并发问题,造成一些复杂难以定位的问题,下面以用户中心和订单中心为例来说明
△循环依赖导致的并发问题
上述图中有两个服务,订单和用户中心,蓝色箭头表示订单中心对外提供的服务 ,实现订单状态更新,其中会调用用户服务更新客户状态,标记此客户已有下单记录。红色箭头表示用户中心调用订单中心,用户服务在标记完客户状态后反过来会调用订单服务,持久化订单上客户信息。上述调用形成了闭环,最终会引起订单数据库中的版本冲突,导致更新失败。那么如何避免这种循环依赖呢?总结以下几个准则:
-
明确服务边界和定位,划分上下游服务,下游服务可以调用上游服务,但是上游服务不能依赖下游,如果要进行通信,采用领域事件的方式,比如消息通知。
-
数据不要过多冗余,尽量通过数据 id(能够唯一代表数据且不变的属性)来进行关联,即只存引用。
-
如果存在必须要通过上游服务同步调用下游服务才能完成的功能,得反思是否上游服务缺少了相应的业务域。如果不是,可以借业务前台来实现各服务的调用编排,或者肯能存在微服务拆分不合理,这种场景需要重新规划,拆分出一个更上游的服务供调用。
回到刚才的实际例子,治理方案是采用领域事件的改造方式,订单服务是下游服务,用户服务是上游服务,用户服务更新订单客户信息从同步调用方式改为消息通知,不感知具体的订阅方,实现解耦。
2.4 服务迭代
微服务架构改造后,原来单体架构的迭代方式已经不再适用,我们看一个实际例子:
从上述例子中可以看到,微服务架构后,一个业务需求会涉及多个模块,复杂度不尽相同,如果中台服务的每个升级无法做到向前兼容,那么势必会导致需求间的耦合,一旦并行开发的分支增多,因模块间的耦合导致的整体回滚概率将会指数级增长。仍旧是上述这个例子,如果需求1中的中台B有BUG,需要回滚,那么是否导致需求2中的所有模块都要回滚,包括中台D,即使它的改动点非常小。
总结以下,造成上述局面的2个主要原因:
-
迭代升级的视角只有需求维度,中台服务被割裂,缺少另一个以中台为主的视角
-
自动化测试能力不足,过度依赖黑盒测试会导致风险后置
针对这两个问题,我们的解法主要有3个方向:
-
规范需求迭代流程,强化以中台为轴的虚拟团队。
-
从需求评审、技术详设、开发、联调、测试到上线,制定详细的全流程规范,保障团队有序运转。同时,强调各中台的自治,迭代升级必须做到向前兼容,依赖模块的回滚不影响自身的上线计划,反复强调容错、兼容、演进式的设计理念。
-
加强自动化测试能力,达到自动准出标准。自动化测试能力包括各模块的单测和集成测试。模块单测通过插件集成至流水线,对于没有达到覆盖率的代码禁止合入,特殊情况下,可以说明理由,让模块负责人豁免。集成测试从业务前台出发,按照业务场景优先级逐步建设,触发方式分为每日例行任务和回归测试时的手动触发。
-
提供服务 mock 能力,在合适的场景下可以解耦对其他服务的依赖。落地联调中心,实现服务级别的动态 mock 能力,并通过 starter 方式让业务模块低成本接入。整体设计分为两部分,client端和server端,如下图:
△mock 中心
其中,服务端的可视化 mock 规则配置支持动态规则,对于一些无法用动态规则描述的需求也可以通过低成本的代码开发实现定制化逻辑。
最终期望能够让每个中台服务达到『自治与隔离』,从而解耦各需求点的上线,如下图:
可以看到,上线粒度从需求维度细化到了中台 fetaure,使整体交付风险指数级下降。当然,对于微服务架构系统,如何做到高效联调和测试仍是一个正在不断探索的方向。
三、总结
本文主要介绍了合约广告微服务架构演进中的一些最佳实践,做一个简单的总结。
-
服务拆分:从实际业务出发,基于领域驱动模型的理念,建立合适的业务模型。这部分一定要深入业务细节,理解业务运转原理,抽象出业务本质。通过合理服务拆分,可以更高效地解决复杂业务问题,如果将7个业务中台类比为正交的坐标轴,那么原来低维的复杂问题(单体架构)投射到高维后(微服务架构),大概率会变得没那么复杂;另外,对于本质业务复杂度问题,通过服务拆分,可以很好的将复杂性隔离至对应领域服务,更好地管理复杂度,防止外溢。
-
模块结构:借鉴COLA架构,结合实际情况,制定规范,其中包含了多种设计理念:DDD、事件驱动模式、CQRS模式、依赖倒置。相比服务拆分,模块结构是从微观视角制定各微服务的代码组织,减缓腐化速度。
-
服务治理:总结了系统性能、业务可观测性和防腐化三个方面的一些心得和最佳实践,在我看来,一个好的业务架构,能够很好的管控业务复杂度,甄别业务中的『变』和『不变』。所以,定期梳理分析各服务的升级频率、调用关系等是非常重要的。
-
服务迭代:服务及架构的迭代需要平滑有序,做到容错、兼容、演进式,避免大量应用改造。同时各微服务要做到自治与隔离,降低相互之间的耦合,做到在架构演进过程中每个服务都能顺利地『死去』与『重生』,就像生物进化一样。(本文多次提到的领域事件方式是降低服务耦合的有效手段)
所以回到本文开头的2个问题,B端系统最大的技术挑战我认为是业务复杂度的治理,通过合适的技术选型在赋能业务的同时又能很好地管控技术和业务复杂度。我心目中好的业务架构标准就是提升效率,这里的效率包括交付效率、运维效率以及演进效率。
最后想说:没有完美的业务架构,贴合业务实际的就是好架构,一个好的业务架构一定是结合业务实际演进而来的。
————END————
推荐阅读: