简介
一个切实可行的复杂系统 势必是从一个切实可行的简单系统发展而来的,从头开始设计的复杂系统 根本不切实可行,无法修修补补让它切实可行。你必须由一个切实可行的简单系统重新开始。——盖尔定律。
最开始,只是一个简单的需求,然后基于简单的需求实现了一个系统。然后不停的开始加小功能,尤其是需求方来自不同的人员的时候,无目的的扩充,会让系统逐步变得很臃肿、零散,中间还往往伴随着 项目负责人的变动,代码风格的不一致等。
项目的演化有两个方向
- 广度,觉得系统不错,让它承担更大的职责边界。 尤其要警惕跟系统功能很类似,但本质不是一个东西的需求
-
深度
- 更高性能,数据更全,界面更友好等
- 项目的各个要素 从特定 变成灵活配置。不仅 项目的职责边界 会变化,某个要素的意涵 与项目设计之初也可能会有所不同。
- 在设计一个系统时,必须描述清楚项目的边界和假设。一旦新功能跨了边界,最好是重新设计,而不是很丑陋的兼容实现。 另一个方面,加功能的时候通常只是立足于功能本身,而没有从全局视角来调配问题。辽沈战役中,有一个阻击阵地失守了,连长想夺回阵地再汇报。最后,所属团长撤职,连长枪毙。
- 项目必须周期性的调整,基于变化,调整项目的边界。评价项目是否应该重构(以及重构方案的好坏)的重要标准是:项目当前状态与理想状态的差距。
项目本身会腐化、臃肿,越来越多的老代码、历史遗留,最后应对问题的时候越来越力不从心,就好像一个王朝的末期,军事失败只是表象,组织、经济能力失灵才是本质。
一个项目问题的解决是分层次的,比如分布式事务问题:
- 可以业务需求、架构层干脆避免分布式事务
- 也可以应用层使用相关框架 + 设计解决
- 也可以干脆使用大容量或分布式数据库,即在数据库层面上解决。
解决问题时,通常并不是单纯解决问题本身就够了,还要符合整个系统一贯的思路。
一个项目 ,若是长时间不管,不精心培育,关键时刻是顶不住的
项目的发展离不开需求的完备性,但需求是一波波的,不是一个线性的过程。
项目需求变化 ==> 实现方式变化 ==> 使用方式变化 对项目的挑战很大,比如k8s为什么会赢?后Kubernetes时代,2019的容器技术生态会发生些什么?不同于一个只能生产资源的集群管理工具,Kubernetes 项目最大的价值,乃在于它从一开始就提倡的声明式 API 和以此为基础“控制器”模式。Kubernetes 项目为使用者提供了宝贵的 API 可扩展能力和良好的 API 编程范式,催生出了一个完全基于 Kubernetes API 构建出来的上层应用服务生态。可以说,正是这个生态的逐步完善与日趋成熟,才确立了 Kubernetes 项目如今在云平台领域牢不可破的领导地位,也间接宣告了竞品方案的边缘化。
敏捷
- 首先是对业务有宏观设计,对整体架构做合理分层。因为不可能所有地方都敏捷,一些更偏应用、更频繁改动的部分需要敏捷,一些基础的模块需要更稳定、更高性能。我们要对业务有宏观的设计,这样可以把不同子系统放到对应的位置上去。如果胡子眉毛一把抓,什么地方都想快,这是错误的,而且也是很难实现的。
- 有了宏观的设计,接下来说具体的实现方法
- 第一是微服务化,拆更小的服务单元,从开发上就可以有利于快速地变更,这些服务单元能够在很多业务系统中灵活组合,以及多人并行开发。微服务是提高开发效率非常重要的一点。
- 第二个是容器化,这个概念我相信在座各位都比较了解。容器对于运维体系来讲有点像集装箱对于货运,可以解决环境部署的问题、隔离的问题、资源分配的问题。容器本身的开销可控,未来还有进一步提高灵活性的空间,以及重组的空间。
- 是不是有这些技术之后就很美好了?我想说的是,从实践来看,问题才刚刚开始。
- 运维、质量和发布体系的问题。
- 存储的易用性问题
- 性能优化
- 高密度计算、数仓与底层硬件探索
- 高密度计算。业务系统有很多类型,有些系统用非常敏捷的方式是好的。另外有些系统比如推荐、搜索、广告、视频理解,这些系统的计算密度很高,服务粒度要稍微大一点。当我们在这些系统上做敏捷的时候,可能要选择用插件化的方式去加速它的开发和迭代,这是一类高密度计算的问题。
- 数仓的问题。字节跳动的核心技术理念是数据驱动和敏捷开发,我们需要全面的数据平台。PS:ab 是推进数仓建设的一个重要抓手。
- 底层硬件的技术探索。比如自研服务器、DPU、AI芯片、数据中心技术等等。
软件设计的中庸之道Big Design Up-Front(类似于瀑布模式)和 No Design是两个极端:2001年,随着敏捷宣言(http://agilemanifesto.org/)的颁布,敏捷思想以迅雷不及掩耳之势,开始席卷整个软件行业。打着敏捷的旗号,大家都低头“冲刺(Sprint)”,期望设计能够在后期迭代中自然涌现,一时间,No Design的思潮甚嚣尘上。然而,事情果真有这么美好吗?期望毕竟是期望,好的软件设计从来没有因为敏捷迭代而自然涌现。相反,Agile压缩了工时,变相的压榨工程师变成了“搬砖工”,软件开发变成了特性工厂(Feature Factory)。BDUF和No Design是两个极端,我们需要”执两取中”,找到一条中间路线。比如说JEDUF(足够的提前设计,Just Enough Design Up Front )。JEDUF是说我们要做设计,但是也拥抱变化,承认信息获取是一个过程,不指望一步到位。
从码农到工匠:既然注定会变化,我们只能放弃强规划的迷思,增加应对不确定性的能力。这正是软件研发模式从大规划、大设计、长周期的瀑布(Waterfall),走向敏捷(Agile)的原因。然而过度敏捷容易滑向另一个极端——无设计,随便造。如何权衡短期实现成本和长期可维护性代价,没有特别量化的指标,主要靠经验,我个人即反对“大设计(Big design)”,也反对“无设计(No design)”,最好是足够的提前设计(JEDUF,Just Enough Design Up Front ),比如能预见到的下两个迭代的需求场景,我会综合纳入当前设计的考虑范围。
无论如何演化,每个项目都有一个内核
笔者曾经负责过两个项目:配置中心和api管理
- 配置中心,一个配置管理系统,app 启动时访问该系统,拉取适配app的所有配置。进而支持运营通过更改配置 来操控 app的行为
- api管理,类似swagger,服务端项目开发完毕后,录入接口,供前端/客户端使用,进而支持apimock等功能,支持前后端并行化开发。
两种思维方式
- ”归纳整理法“。常规的设计系统的流程:研究业界已有系统,提炼抽象,该系统有几个基本概念,每个概念有几种实现方式,然后整理归纳,判断取舍,制定详细设计方案。从这个视角来看,配置中心 和 api管理 是两个完全不一样的系统。”归纳整理法“ 说白了是从众心理,体现在生活上就是这事儿别人做了,所以我也要做。
- “核心扩展法”,说白了是第一性原理在系统设计的体现。配置中心有项目、分组、配置、发布等概念,api 管理有项目、模块、api、mock 等概念。换个表达方式:api管理首先是做接口管理的,然后因为接口不能平铺在那里,所以从组织方式上有模块管理和项目管理。接口是人操作的,所以有权限管理,然后围绕接口有api mock 等。 从这个角度看,api 和 配置中心是一样一样的,url 类似配置中心的一个个配置。
我们为什么要争辩两个思维方式的差异,就是因为常规的”整理归纳“的思维方式,无法帮助你在一些具体的问题上做决策,你最多知道一个具体的特性在你调研的案例里有几个实现了,有几个没实现,这个问题在”核心扩展法“思维下就很好决策,既然项目都有一个内核, 那么项目迭代的过程中,一定要关注对内核的影响。比如当你去看业界主流的api 管理系统实现时,不能被外在的项目公有/私有,是否支持模块 等这些细节所迷惑,它的核心是做api 管理,其它一切都是围绕接口管理进行的。你向别人介绍自己的项目时,也应该先从接口管理入手,这是要点,然后才是其它”支持“功能。
时刻保持优雅,而不是修修补补成大泥球
业绩增长常会稀释人才密度,当业务复杂度往上快速增长时,公司高绩效人才的占比是下降的,两者会出现一个交叉点。当交叉点出现时,你会发现以前的那套流程已经不适用了,混乱和错误开始出现,产品迭代速度会受到严重制约。在这个人才密度上,业务已经变得太过复杂,而系统不可能再以往常的形态运行。
需要完善团队人才梯队,使人才密度的提升超过业务复杂度增长。
学会分析事物的本质业务研发,特别是复杂业务系统的研发,实现产品经理提出的业务需求仅仅是其表象,其真正本质内涵,是使用技术手段将解决某一特定问题的逻辑数字化,利用计算机技术对客观事物做数字化的建模,以尽可能贴近事物本质的方式进行逻辑和数据的运转,从而完成现实和虚拟的映射,解决对应的问题。流量变现业务概论——Linkedmall 流量变现业务初步分析及系统设计概要
项目管理
做产品想“多快好省”都占着,是不可能的,最多只能选两样。因为软件工程的目标就是要构建和维护高质量的软件,“质量”这个因素一般不会妥协。PS:与分布式CAP异曲同工之妙
固定一条边很重要:从时间、成本和范围这三条边中找出来固定的一条或者两条边,再去调整另一条边。
“不可能三角”是道的层面,应用在“术”的层面:
- 老板要压缩工期,则应减少产品范围和加大成本(投入更多人力)。
- 产品经理要临时加需求,则要么延期要么加人。
- 这些年流行的 MVP((minimum viable product,最小化的可行性产品)模式,是一种快速推出产品的模式:一开始只推出最核心的功能,满足用户最核心的需求,然后在用户的使用过程中收集反馈,进一步升级迭代,快速试错。
这个世界上有好多事情,看着像可以任意发挥, 但又不能任意发挥, 那么这个边界在哪里?就到你要摸索每个问题域的本质。
《软件架构设计》:对于项目管理,有一个关键问题要面对:“不确定性”问题。从人的认知来讲,做任何事情,思路都是从一个“朦胧”到逐步“清晰”的过程,项目的进展也是一个从思路、到方案、到落地的细化过程。在这个过程中, 不可避免存在“不确定”,比如需求变化、新技术生疏、核心人员离职、与其它部门协调、历史遗留等,项目管理就是要提前防范各种不确定性。
技术债
陈现麟:技术债务是生产力,合理利用技术债务会大大提高我们的研发效率,提高项目的成功率。就像我们有时候要积极的使用房贷买房一样。技术债务应该是深思熟虑的结果,结合买房贷款来思考。除了本金之外,贷款还会涉及利息的问题,一般会提供非常多家的银行,每家银行之间会有差异,并且还有公积金贷款这样利息更低的贷款产品,我们一般都会选择利率最低的贷款产品或银行。既然技术债务是债务,那么借技术债务也是有利息的,那么我们应该如何选择技术债务的利息,将整体价值最大化呢?一般来说,技术债务影响的范围越大,它的利息就越高,所以,对于技术债务的利息高低,我们可以通过它的影响范围来判断。
- 系统的接口和协议的利息是非常高的,因为系统的接口和协议是对外提供服务的,就导致它的影响范围非常大,并且还会随着接入方的增加,而自动放大技术债务,所以,这样的利息是复利,我们一定要避免。
- 系统架构的技术债务的利息一般也是很高的,因为系统的架构会从全局影响系统的设计,它的影响范围会非常大,并且会随着系统的迭代而增加
- 局部的功能和逻辑之类的实现的利息是比较低的,因为它只会影响到局部的代码实现,比如一个函数的具体实现、写死的配置和策略等影响范围不大的地方,这样的技术债务利息比较低,在我们有需要的时候,可以多借一些。
- 非常边缘的功能和一些尝试型的功能实现的利息是非常低的,因为边缘功能后续的迭代不会很多,它在时间维度上的影响范围是非常小的,而尝试型的功能在后面是有一定的可能性被放弃的。虽然我们希望尝试都成功,但是如果被放弃后,从技术债务的角度来看的话,我们甚至连本金都不需要还。所以,这样的利息债务可以根据需要多借一些。
不可忽视的软件生命周期理论
不可忽视的软件生命周期理论以静态的思维看待软件开发,极有可能最终导致所获得的软件是一个臃肿、易出错的包袱。出现这种状况的原因,是因为没有明白软件是存在生命周期的。软件也象人一样,存在形成、成长、成熟和衰退四大时期(如下图所示)。图中纵座标代表软件对新需求的适应能力,指软件对实现新需求的友好度,背后是概念与概念之间的关系是否清晰、让人对其的认识是否符合直觉与常识,本质是指软件的设计质量。图中的直线也只代表一种趋势,现实中更多地表现为存在波动的曲线。
软件进入成熟期的标志,是其功能实现程度与当初的定位和使用场景契合。进入衰退期是因为业务发展的需要而出现了新的场景,此时软件的概念抽象(又可以称之为“架构”或“主导软件设计”)对于实现新场景下的需求并不友好,导致新开发的代码变成了“贴狗皮膏药”。软件长期处于衰退期的副作用是,软件质量持续变差,开发同学的编码体验不断下降。
理解软件生命周期的另一种视角是,软件工程师对于需求的理解是随着时间逐步加深的,很难出现最初的软件设计能满足业务发展的长期需要,毕竟业务也是一天天变得复杂起来的。换句话说,软件进入衰退期是不可避免的,而技术债也是软件发展的自然产物。走出衰退期的关键是需要让软件进入新一轮的生命周期,而最直接的办法就是“还技术债”,其中包含了重构、或用全新的思路与新技术去解决问题。从小处说来,工程师通过持续重构去还技术债是真正锻炼能力的时候,这个过程会基于个体对业务(或需求)的理解做重新的概念抽象,掌握良好的软件设计能力正是从这些“小处”习得的,也只有具备良好软件设计能力的工程师才有可能驾驭大型软件系统的设计。
软件生命周期理论告诉我们,一个好软件并非一直能保持不变,而是能经得起各种改变。当然,各种改变的背后需要以工程能力做支撑,全面通过单元测试、集成测试、系统测试等手段去保障软件的质量,一旦缺失了这些手段就很难建立起对改变的信心,也最终会趋向于固步自封地不变。
在业务系统中寻求技术含量 自动化 ==> 配置化 ==> UI 化
- 手动变自动
- 配置化。
- 如果业务流程变化多,配置内容就是工作流配置。
- 如果计算逻辑变化多,配置内容就是各种表达式配置。
- 如果上游系统变化多,配置内容就是 ACL 的映射配置。
- UI 化,上下文有关联的配置,在文本中非常难配置,而在 UI 中则非常容易。我们可以将合法配置的前提知识都编写到 UI 的关联弹出逻辑中,或者校验逻辑中。这样用户便可以通过简单的交互,迅速知晓软件配置的游戏规则。
- 平台化,只要用 UI 化的思路做系统,迟早会形成一堆散落的接入系统。平台化是对这些系统进行整合的一个机会,以某个具体的主题,把所有相关的流程聚合在一起。例如 paas 平台,流式计算平台,业务工作流编排平台,文档平台。知识只有沉淀成为平台才能成为公共知识,否则只不过是老员工脑子里的糨糊罢了。
- 中台化,中台建设的最终产出物是 结合了业务 SDK,多租户隔离能力,自动化扩容能力,相应业务逻辑展现为 UI 能力的多套完整的大平台。对架构师的抽象能力,工程师的技术能力,基础设施的运维能力都是有巨大考验的。从业务收益上看,这些带界面的中台可以大大降低工程师与 PM,PD,PXX 交流的门槛,非颠覆性的业务,甚至不需要工程师参与就可以由业务人员罗列出所有修改点自己修改完毕了。
需求分析,架构实现,(新需求,架构改动)* n = 推倒重来
这个过程是一个循环往复的过程,有的产品每年都会推倒重来一次。要解决这个问题,那就需要在每次迭代中,都需要用正确的姿势对不对?
- 自顶向下的架构推导,自顶向下的推导的关键问题在问题定义,如果问题没有被准确的定义,那么自顶向下就无法推导出正确的结果。假设问题被准确的定义了,如何自顶向下推导呢?就是你需要知道猪长什么样,在架构上就是你需要知道这个架构的原来是是什么样子的,解决什么问题的。如果都不知道猪长什么样,那么就无从判断猪是不是适合当宠物了。此处需要有一定的业务领域理解力和领域经验(包含:客户的问题和痛点是什么,怎么分析出来的,当前的架构方案是什么,当前的架构方案是如何解决这个问题的,未来的架构方案如何更好的解决这个问题)。所以当我们不熟悉一个大的业务的时候,我们自顶向下推导架构的难度是极大的,几乎不能完成。
- 自底向上的架构推导 从方法到思维:什么是应用逻辑架构的正确姿势?
重新认识访问者模式:从实践到本质以阿里双十一的各个分会场与功能为例:盒马,饿了么和聚划算分别作为一个分会场参与了双十一的促销,他们都需要提供优惠券,订单和支付等功能。
虽然在用户看来 盒马,饿了么和聚划算是三个不同的应用,但是底层系统可以有两种划分方式:
- 按应用划分:盒马,饿了么,聚划算这个三个系统完全独立,分别实现一遍三个功能点。虽然有重复造轮子的嫌疑,但是能够短平快地支撑创新业务,这可能就是所谓的“拆中台”。
- 按功能划分:将系统分为 优惠券系统,订单系统和支付系统,然后三个应用都使用相同的功能系统,在功能系统内部通过配置或者拓展点的方式处理业务之间的不同。这其实就是所谓的 “中台”,虽然能最大程度上地重用已有技术成果,但是中台的种种限制也会遏制创新业务的发展。 任何一种划分方式都要承受该种方式带来的缺点。