软件架构的定义

一、 软件架构的定义

我们先讨论一下什么是软件架构?

对于软件架构并没有一个标准的定义,但是你和软件工程师谈到架构的时候,他们会知道这些都会是架构的内容。

是不是要分层,如何处理事件,如果划分组件,组件和分层之间如果传递数据和控制信息,数据如何存储,计算如何并发,等等。

我认为架构的本质是一个中心两个基本点中心是要解决一个问题,两个基本点是要解决两个核心资源的问题:人和时间

软件架构的核心是要解决问题,这个问题就是要提供软件需求所定义的功能。围绕这个核心,软件架构就是要能使你的软件更好的提供需求所定义的功能。

任何软件都是由若干人在一定时间内完成的,时间和人是软件开发最重要的资源。其中时间是不可重生的,而开发人员虽然不会说不干就不干,但是就怎么干这个问题,会是影响软件的重要因素。

这里我要澄清一下,我确实是在讨论软件架构而不是项目管理。

首先我们看看第一个基本点人的因素。大部分的软件都是由一个团队来开发的,软件架构设计的一个目的就是能使的团队中所有的人能够有效的合作。

软件架构和项目管理一致的地方就是都要化整为零。对系统分解成为若干子系统,定义每个子系统的指责,系统的边界和系统之间如何协作。

通过这样的定义使得团队中的人可以专注的相对比较清楚的子系统工作上。

另外一个基本点是时间,通常我们开发软件都需要有一个时间约束,软件架构的设计使得软件能够在规定的时间内完成开发。

通过定义子系统,我们可以定义那些组件可以重用,那些可以用开源,那些可以利用团队中已有的知识快速开发。如果没有这个时间限制,软件架构完全可以天马行空,反正有的是时间来换一个玩。

作为一个架构师,我们通常要面对的软件架构相关的问题有:

  1. 和我的软件利益相关的人都是谁?
  2. 软件的目标是什么?
  3. 软件使用什么技术?
  4. 软件有哪些风险?有没有应对方案?
  5. 软件有哪些限制条件?
  6. 如果可以重做,你会做哪些不同的选择?

围绕一个中心,两个基本点,我认为软件架构是:

  • 软件架构是对问题域的划分,把大的问题划分成可管理,可控制,可实现的小问题。
  • 软件架构使得团队可以高效的协同工作,
  • 软件架构可以提升开发的效率

那么,你觉得什么才是软件架构呢?欢迎来和我讨论。

 

(二) 我眼中的架构师

 

之前在公司,有小伙伴在向别人介绍我的时候,经常会有人这么说:“刚哥是我们的architcture”,如果来人是老外,心中一定是一惊,心中暗叹,“这位匪首看上去貌不惊人,难道已经做到了架构和本人天人合一和最高境界了?” 回头,我不免又要唠叨两句,“同学们,没文化,很可怕,我是架构师 architect,不是架构 architcture” 就像我上次跟大家聊的一样,人是架构要解决的核心问题之一,那么说“人即架构”,似乎也是有些道理。

但是你要是硬说架构师就是架构,恐怕你是对架构师有什么误解。

从企业的角度来看,会定义不同的架构师的角色,像什么系统架构师,解决方案架构师,软件架构师等等。

我们要谈论的是软件架构师这个角色。其实很多的软件企业和团队,都没有定义架构师这个角色。

在我早期参与的软件开发团队中,主要的角色无非是产品,开发,测试和管理角色。

后来的敏捷开发就更为简单的分成三个角色Scrum Master, Product Owner, Dev。

在这样环境中,并不是说没有架构师,也不是说不需要架构设计,而是说,架构设计的工作被团队经理,开发等其它的角色所承担。

“一千个人眼中就有一千个哈姆雷特”,每个程序员,每个组织,每个企业也都会有自己心目中的架构师,那么我今天就来聊聊我对架构师这个角色的看法。

我眼中的架构师不一定有颈椎病,不一定秃发,但一定首先是一个程序猿,一个软件工程师,一个码农。

经常看到有小伙伴讨论架构师要不要写代码?我认为这是个伪命题,就像我之前说的,大家根本没有对架构师有一个统一,一致的定义,那么讨论架构师要不要写代码根本就没有意义,有的架构师需要写代码,有的并不需要。

但我眼中的软件架构师一定是一个优秀的程序猿,软件开发是一个实践性非常强的工作,你不能指望通过学习几本二十一天入门类的书籍就能对软件开发有一个深入的认识。

软件开发并不是有人说的艺术,也不是科学,软件开发需要的工匠精神恰恰是来自于大量的实践。

Talk is cheap,show me your code’, 架构师不仅仅是画画架构设计图,做些炫酷的PPT和演讲,虽然这几样都是我非常喜欢和擅长做的工作。

我眼中的架构师是一个技术领导者。

杰拉尔德·温伯格 (Gerald M.Weinberg) 是我最喜欢的软件类书籍的作家,他是软件顾问和问题解决的专家。

他的《成为技术领导者》也是一本值得反复阅读的书籍。

 

 

我们经常会有关于领导和领导力的话题和讨论,那么组织中的领导究竟要干什么,我们不是有经理么,经理不就是领导么?我认为领导和经理的区别在于,领导做正确的事,而经理的主要职责是正确地做事。

做正确的事通常意味着作出正确的选择,这里的难点在于,什么才是正确的?回答这个问题就要上升到哲学的角度了。

墙裂推荐哈佛大学迈克尔·桑德尔教授法学系列课程《公正:该如何做是好?》 一般来说,按照我们之前对软件架构的定义,正确的选择意味着有助于实现软件功能,有助于使得团队可以更好的协同工作,有助于提升开发的效率。

温伯格在《成为技术领导者》中说到 “用乘法而不是加法”,好的架构也都是以倍增的方式来改进软件开发的效率,例如云平台Kubernetes,前端框架react等等。

在我们日常的软件开发过程中,经常会面对着这样那样的诸多选择,好的架构师能够帮助团队选择最适合的那个,也就是我们说的“正确”的决策,这个很难,但是这还不是最难的。

当你做出了你认为是正确的选择的时候,下一步是什么呢?有同学说,那就开始干呗!很多时候,架构师不像是经理,直接管理团队,也就是说架构师并没有最终的决策权。

架构师说,“同志们,我看这样做挺好,咱们就这么搞吧!” 团队成员直接怼过来,“为什么要这么搞吧?我看那么做也挺好么”。

要让一个好的决策落地,架构师需要说服团队,让大家相信这真的是一个正确的决策。

这就像是“狼人杀”游戏中,你作为一个逻辑严密的玩家,通过聆听一轮发言,准确的辨别出所有的狼人,然而你却无力说服队友你是对的。

“狼人杀”这个游戏的精髓就在于,这是一个团队游戏,你怎么认为并不重要,重要的是你的团队是怎么看的。

所以,作为一个优秀的架构师,除了技术之外,你需要有很优秀的逻辑思维和表达能力,良好的人际关系以及不错的口碑。

程序猿是一个很有特色的群体,他们通常都有一个特点,就是非常的“固执”。

说服一个程序猿放弃自己的观点,简直堪比送神州火箭登上月球。

所以永远不要试图证明程序猿错了,也不要证明他的观点不如你的观点。

因为所处的角度不同,我相信自己是正确地的同时并不意味着要证明别人是错误的。

一个好的架构师,作为一个技术领导者的核心能力是影响力。

当然这个影响力是建立在能够真正作出正确的选择的基础上的。

最糟糕的情况是,作为一个领导者,因为能力不足,选择一个错误的方向,然而因为其强大的影响力,就像乔布斯那样拥有扭曲现实的能力,而你又恰恰拥有一个执行能力很强的团队,那么结果只有一个,你会离你的目标越来越远。

我眼中的架构师是技术和产品的之间的桥梁

我们圈子中经常会流传着程序猿和产品经理之间的各种小故事。作为软件圈里的世仇,他们的故事还可以讲述很久很久

 

 

 

 

 

(图片来自https://mp.weixin.qq.com/s/hGIuHzD-9VNsS-JO2z7dWw

我们看到产品和开发作为拥有不同世界观的两个群体,很难理解对方,而我眼中的优秀架构师应该成为产品和开发之间的一个沟通的桥梁,一个能理解两个世界,精通两门语言的的翻译。架构师需要把产品功能翻译成程序猿能够理解的技术语言,同样架构师需要把开发面临的挑战以非技术的语言,转达给产品经理。所以架构师应该同时是产品和开发的好朋友!

我眼中的架构师是一个团队的催化剂

讲一个笑话,我的一个朋友最近加入了一家创业公司,他说他入职的那一天,公司的CEO,CTO,CFO,产品经理,测试,市场,司机,厨子,保安都来欢迎他的入职。

我对他说,“你太有面子,公司看来对你很重视呀,派出这么多人来欢迎你。” 他说,“哪里呀,不就一个人么!”

今天的软件开发对团队的依赖程度非常的高,所以架构师首先是这个团队的一个成员,需要有良好的团队精神。

诺兰大师在他的电影《盗梦空间》中讲述了一个精彩的关于团队合作的故事。

 

 

架构师(筑梦师)无疑是团队中的重要角色,但是一个良好运作的团队,需要各个角色协同工作,离开谁,这个团队都不完美。

好的团队应该是1+1 > 2。公司的意义在于通过把拥有不同的背影,知识,思维模式的人组织在一起,创造出个体无法实现的新的创意,新的产品。

而架构师正是实现新的创意和产品的催化剂,让团队中的每一个人都能发挥出比之前更出色的作用。

我眼中的架构师是一个导师和布道者

软件行业是一个令人激动的行业,技术和产品的以不可思议的速度迅速的发展着,今天的明星技术和明星产品,过不多久就会成为昨日黄花,软件产品的生命周也越来越短,我想了想我参与的软件产品,至今仍然在运行的,仍然在给用户带来价值的,大概是微乎其微的。

所以不断的学习,不断的创新是我们这个行业必须要面对的宿命。

我眼中的架构师应该是一个创新和新技术的倡导者,这里我并不是说新技术一定就是更好的或者更合适的选择,但是我们应该持有开放的心态,积极的去了解这些新技术带来的影响。

作为导师的架构师,应该在团队中创造这样的开放心态和持续学习的文化。

在多年的软件开发生涯中,我和许多优秀的架构师一起工作过,我最后要聊一聊的是我遇到的架构师中的极品。

之前在一家德国公司开发ERP产品,我们团队中的架构师来自德国,我们就称呼他为“老卡”吧。这位“老卡”继承了德国人优秀的哲学思维,如果你跟“老卡”讨论问题,他总是采取“否定一切”的哲学态度,首先说你错了,然后会告诉你为什么你错了。最为致命的是,通常你只能理解他说的你是错的这个观点,而“老卡”所有其它所说的内容,你完全听不懂。一般而言,德国人的英语水平是非常好的,所以并不存在因为语言问题而带来的交流困难。“老卡”喜欢用一些古怪的“大词”或者缩写,结合一些奇怪的逻辑,给对方产生一种深不可测的错觉,我想这就是传说中的架构师的终极形态吧!就像这个故事中的应试者:

面试官:你对电脑懂多少?
应试者(普通青年):懂一点,我戴过电子表,玩过任天堂,房间有一台电视……还有,我看过同学用dos开机,两次…
面试官:下一位!
面试官:你对电脑懂多少?
应试者(2B架构师):嗯,那要看是哪一种电脑了。一般的超次掌上型矽单晶片时脉输出电脑(电子表)比较简单,我小学时候常常使用他的解译编码作业流程(闹铃功能)。 至於多功能虚拟实境模拟器(任天堂)就复杂得多,不过我曾经完整测试过许多静态资料储存单体(只玩卡带破关)。长大後我对于复频道超高频无线多媒体接收仪器(电视)开始产生兴趣,每天晚上都会追踪特定频道的资料(指八点档)。 至于传统的数位电脑,我手下的一位工作伙伴(同学)经常在我的监控之下进行主储存矽单体与磁化资料存取器之间的信号交换(指dos开机)……

后来和印度同事聊天,他们好奇的问,你们都是怎么和“老卡”交流的,他说的你们能听懂么?

我当然不能给中国人丢人吧,就说能!印度同事投来崇敬的目光,在他们眼中,因为无法理解老卡所说的一切,“老卡”俨然成为他们心目中神一样的存在,能和神交流的,也应该不是凡人吧!

所以我要对有志成为架构师的小伙伴提个建议,如果你的技术不扎实,学习能力不佳,沟通有困难,这都不是事。努力学习如何让别人听不懂你所说的话,渐渐的,你就会成为架构师了。

 

(三)软件架构的道与术

秦孝公在位期间致力于恢复秦国的霸业,他因此颁布著名的求贤令, 商鞅3次游说秦孝公,第一次讲的是尧、舜、禹、汤的 帝道。上古时期,百姓安居乐业。秦孝公听着听着睡着了。商鞅离开之后,秦孝公向景监发火,说商鞅自大。景监把这个反馈给商鞅,商鞅没有气馁,请求再给他一次机会。5天后景监给他安排第二次朝见。这一次商鞅讲的是周文王、周武王的 王道,讲的是礼治天下。秦孝公有点兴趣,觉得商鞅可以一起谈论,但没有打算重用商鞅。景监把秦孝公的意思回复给商鞅后,商鞅说:"我已经知道怎么游说他了,请再给我一次机会"。第三次商鞅讲的是春秋五霸的 霸道。秦孝公这次听得津津有味,并几天几夜促膝长谈,不断向商鞅请教,并重用商鞅,才有了后来的商鞅变法,秦国一统天下。

在软件和编程领域有很多书冠之以‘某某之道’,‘某某之美’,‘某某之禅’。软件领域也有自己的一套哲学思想。我今天想聊一聊我对软件架构的哲学思考。这是一个超大的命题,即使我在软件领域摸爬滚打了这么多年,也不敢说我已经窥其一二,今日胡说一通,希望得到大家的指正。

‘道’ 暨道路或者方向,是自然规律,是原则和目的,道路和方向是错的,无论你怎么努力,都很难成功。

‘术’ 是方法,手段和技巧,好的方法和工具可以让你在成功的路上事半功倍,前提是你走在正确的道路上。

‘道’ 是价值观,是主观的东西,也就是你认为什么是正确的事, 不同的人对什么是正确的事有着不同的认识。商鞅的霸道带领秦国统一六国,但是注定不能长久,秦国也未能如始皇的愿,千秋万代。

‘术’ 是方法论,相对比较客观,一个好的方法基本上是可以推广的。

软件架构之‘道’

我个人认为,软件架构之道最核心的问题是解决复杂性的问题。如果说‘道’是方向,那么软件架构之路应该带领码农走向简单。

这里的简单应该包含:

  • 软件分层应该简单,不应该引入不必要的层级
  • 软件的模块应该简单,不应该引入不必要的功能
  • 软件模块之间的关系应该简单,不应该引入不必要的交互和依赖
  • 软件代码应该简单,应该容易理解和阅读
  • 软件构建应该简单,应该容易搭建构建环境
  • 软件测试应该简单,应该容易找出软件中的错误
  • 软件调试应该简单,出错时应该容易定位错误源
  • 软件运维应该简单,应该容易监控和管理
  • 最重要的,软件的功能应该简单,应该容易让用户找到自己想用的功能,并且轻松的使用该功能,达成用户想要的目标。

那么我们想一想,软件发展到今天,我们上面所说的这些有变简单了么?似乎没有。

那么是不是软件架构在背道而驰,变得越来越复杂了呢?我不这么认为。为了理解这个问题,我们来看看软件中的复杂性的元凶,是什么带来了软件的复杂性。

  1. 首先,软件的复杂性来自于功能。我们之前提到,软件架构的中心问题是满足功能要求。随着人们希望软件能够提供越来越多的功能,软件架构的设计必然会随之变得复杂。而这个复杂度的增加和功能之间的关系并不是线性的,而是几何级数甚至更高。因为软件开发是一个动态过程,新加入的功能必然会和已有的功能产生互动,有些是依赖,有些是制约,有些是干扰。例如系统最初有一个功能A,运作良好;当加入功能B的时候,A功能会制约B功能,所以在设计B功能的时候,除了要完成B本身的功能外,还要设计如何和A功能互动或者如何屏蔽A功能对B的限制。当C功能到来的时候,A和B同时要影响C,甚至可能要考虑ABC的联动,这样系统就会越来越复杂了。所以复杂的功能是当前软件复杂性的主要原因。
  2. 其次,软件的复杂性的另一个元凶是人为的。也就是由于开发软件的人和组织因为能力不足,或者因为懒惰,贪婪,傲慢等原罪,人为的使软件变得复杂。

我认为软件发展的大势仍然是向着简单性的方向在前进。因为对功能的要求越来越多,我们现在看到的复杂的软件架构实际支撑了更为复杂的诸多功能,所以从这个角度来看,软件架构实际上是向着简单的趋势发展。

我们所能做的是把复杂性封装在更低的层次。

例如操作系统封装了对计算资源(内存,CPU,进程,文件系统)的使用,AWS云在基础设置infrastructure层次对网络,主机的使用进行封装,而Kubernetes利用容器集群,封装了应用的部署。

操作系统,云和Kubernetes都是很复杂的,但是通过良好的封装和简单的接口,他们给使用者带来了便利

做为架构设计的一个原则,应该尽量把复杂性封装在更低的层次。

 

 

通常在分层架构中,越高的层次一般意味着更多的代码和更多的使用。当一个问题发生的时候,一般总是表现在最高层处(UI或者应用层),然而对这个问题的处理,可以在各个层次解决。

例如为了提高访问效率,我们可以在UI或者数据层加入缓存,数据层的缓存可以支持所有的UI用户,而UI层的缓存只能针对使用该类型的UI的用户,例如web端和移动端的不用UI各自要实现自己的缓存。

当然实际中,所有的层次都可以利用自己的缓存来解决问题,但是从解决问题的效率来看,在低层次上解决问题的效率要高于更高的层次。所以我们应该尽可能的把复杂问题的解决放在更低的层次上。

但是我们之前也提到过人是软件架构的一个基本点之一,分层的架构往往也意味着分层的组织。

当UI的团队试图解决性能问题的时候,他们往往希望在组织可控的范围内解决问题。

这并不是因为他们不明白在数据层解决问题的优势,而是因为要和另一个组织,数据库部门去沟通,带来的额外成本比自己加一个缓存可能还要高。

做正确的事是有代价的。(参见康威定律

爱因斯坦说: “Simple,but not simpler”。

建筑大师路德维希·密斯·凡德罗说:“Less is more。”

奥卡姆说:“如无必要,勿增实体”。

老子说:“大道至简”。

Everything should be made as simple as possible, but not simpler.
–Albert Einstein
爱因斯坦这句话表面看很通俗:事情应该尽可能的简单,而不是更简单。
要把复杂的事情尽可能的用简单的方式解决,但不能投机取巧!以一味的捷径为目的!过于简单!凡事都有一个度!不可做得太过!

 

软件架构之道在于找到设计的平衡点,使得架构足够简单,但是能够满足需要。 (关于奥卡姆剃刀,请大家参考我之前的动图,用可视化来讲故事

除了简单性作为道之根本,软件架构设计还有一些常见的通用原则,我认为这些原则都是和降低系统复杂度一致的:

  • KISS原则

Keep it simple and stupid 是我们之前的简单性原则的一种说法。我想说的是简单未必愚蠢,很多时候大智若愚。

  • 最小代价(努力)原则

光总是走最短路径,人也一样。程序猿的惰性与生俱来,我们总是选择最容易行走的路径。

这也是为什么我们应该尽可能在一开始的时候,作出正确的选择,因为一旦这个架构设计出现,后面的人很有可能不愿意为了更好的架构而改进,而是遵循已有的设计。

这个和简单性原则一致,如果我们不能在一开始作出正确的选择,因为最小代价原则,系统必然会走向复杂。

  • 最小意外原则

以前读过一本讲UX设计的书《点石成金》,英文名叫《Dont make me think》。和这个原则一致,UX设计应该自然,符合用户的常识和使用习惯。

如果用户需要通过思考才能理解如何交互,那么一定是设计出了问题。同样的,架构设计也一样,好的设计应该避免意外,遵守通用的规范和习惯,代码也是。

这些意外其实是软件架构和设计中的复杂因素。最小意外也就意味着尽可能的简单。

  • DRY原则

重复是软件的原罪之一,“Dont repeat yourself” 告诉我们应该尽可能的消灭重复和冗余。重复使的软件的阅读,修改,测试变得复杂,消灭重复,是使软件变得简单的手段之一。

 

软件架构之‘术’

我们再来聊聊软件架构的‘术’。在软件和软件架构设计领域,有很多方法和工具,我们来看看其中最常见的一些。我们可以把他们归为“术”。这些和软件架构都有着直接或者间接的关系。

数据结构和算法

数据结构和算法是软件编程领域里最重要的方法。

数据结构,是抽象的表示数据的方式;

算法,则是计算的一系列有效、通用的步骤。

数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。

算法是为求解一个问题需要遵循的、被清楚指定的简单指令的集合。

算法与数据结构是程序设计中相辅相成的两个方面,是计算机学科的重要基石。

运用数据结构和算法,可以有效的解决编程中遇到的一些常见的复杂问题。

每一个程序猿从一开始学习编程就接触数据结构和算法。

有人说,程序等于数据结构加算法。这有一定的道理,但是显然不够全面。

数据结构和算法作为编程的基础方法,是各大软件厂商招聘考核的标杆,不管你是程序猿还是架构师,都需要投入精力于此,数据结构和算法是软件和架构设计的基础

面向对象和设计模式

仅有数据结构和算法还不足以应对复杂的软件开发的需要。面向对象的设计成为了软件开发领域里最为流行的思想。

面向对象是一种对现实世界理解和抽象的方法,利用的是隐喻(metaphor)的手段。

隐喻其实是我们在软件设计中常用的一种手段,为了便于理解,我们把现实世界中的概念用软件中的概念的来模拟,这样做的好处是便于我们去思考,因为我们的设计是基于我们对现实世界的理解,所以可以重用我们现现实世界积累的成功经验。这样做的缺点是,软件世界有自己的特点,完全套用现实世界的偏见可能并非最为有效。现在虽然对面向对象的程序设计仍处于统治地位,但是它的声音已经渐渐变弱。

与之对应的有面向过程,函数式编程,面向切面(Aspect Oriented)等思想。

设计模式(Design pattern)起源于《设计模式:可复用面向对象软件的基础》一书,提出和总结了对于一些常见软件设计问题的标准解决方案,称为软件设计模式。

该书作者后以“四人帮”著称。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。

这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

软件设计模式和面向对象的设计都基于一些常见的原则:

  • 单一职责原则 Single Responsibility
    一个代码组件(例如类或函数)应该只执行单一的预设的任务。
  • 开放封闭原则 Open Close
    程序里的实体项(类,模块,函数等)应该对扩展行为开放,对修改行为关闭。换句话说,不要写允许别人修改的类,应该写能让人们扩展的类
  • 里氏替换原则 Liskov Substitution
    里氏替换原则的内容可以描述为: “派生类(子类)对象可以在程式中代替其基类(超类)对象。”
  • 接口隔离原则 Interface Segregation
    指明客户(client)应该不依赖于它不使用的方法。接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口(role interfaces)。接口隔离原则(ISP)的目的是系统解开耦合,从而容易重构,更改和重新部署。
  • 依赖倒置原则 Dependency Inversion
    程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

以上的五个原则构成了著名的SOLID,除此之外,还有一个著名的高内聚低耦合原则,是指具有相似功能的代码应该放在同一个代码组件里。一个代码片段(代码块,函数,类等)应该最小化它对其它代码的依赖。这个目标通过尽可能少的使用共享变量来实现。“低耦合是一个计算机系统结构合理、设计优秀的标志,把它与高聚合特征联合起来,会对可读性和可维护性等重要目标的实现具有重要的意义。”该原则和我们之前提到的把复杂性封装在更低的层次上是一致的,对复杂性的封装就是高内聚。

模块化和分层

模块化和分层是架构设计里最基本的方法。

 

如上图的一个典型的软件架构图中,不同的模块位于不同的层级,某些模块会跨一些层级。模块化和分层利用的是分而治之的方法,把复杂的软件系统划分为可控制,可管理的小单元。也即是说,它仍然是为了解决复杂性的问题。分层和模块化不是总能降低复杂度,过多的层次和冗余的模块会使系统变得更为复杂。

微服务和SOA

随着越来越多的软件应用走向云端,云原生的设计越来越多的被提起。微服务随之大行其道。Martin Fowler对微服务的定义是“微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间相互协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务和服务之间采用轻量级的通信机制相互沟通(通常是基于HTTP的Restful API).每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构"

 

 

和之前的模块化和分层相比较,分层更多是关注在垂直方向上的责任划分,而微服务增加了水平方向的扩张,并实现去中心化的网络架构。关于去中心化的网络架构推荐阅读凯文·凯利的《失控》

优点

  1. 提升开发交流,每个服务足够内聚,足够小,代码容易理解;
  2. 服务独立测试、部署、升级、发布;
  3. 按需定制的DFX,资源利用率,每个服务可以各自进行x扩展和z扩展,而且,每个服务可以根据自己的需要部署到合适的硬件服务器上;每个服务按
  4. 需要选择HA的模式,选择接受服务的实例个数;
  5. 容易扩大开发团队,可以针对每个服务(service)组件开发团队;
  6. 提高容错性(fault isolation),一个服务的内存泄露并不会让整个系统瘫痪;
  7. 新技术的应用,系统不会被长期限制在某个技术栈上;

缺点

  1. 没有银弹,微服务提高了系统的复杂度;
  2. 开发人员要处理分布式系统的复杂性;
  3. 服务之间的分布式通信问题;
  4. 服务的注册与发现问题;
  5. 服务之间的分布式事务问题;
  6. 数据隔离再来的报表处理问题;
  7. 服务之间的分布式一致性问题;
  8. 服务管理的复杂性,服务的编排;
  9. 不同服务实例的管理。

我们看到,微服务在某种程度上提高了系统的复杂度,天下没有免费的午餐,作为一个架构师,需要理解微服务带来的好处是否能够抵消复杂性提升的代价。

当今的微服务大多基于容器设计。(关于微服务中容器设计的原则,请参考我的博客基于容器应用设计的原则,模式和反模式

无服务设计 (Serverless)

Serverless 架构是指大量依赖第三方服务(也叫做后端即服务,即“BaaS”)或暂存容器中运行的自定义代码(函数即服务,即“FaaS”)的应用程序,函数是无服务器架构中抽象语言运行时的最小单位。在这种架构中,我们并不看重运行一个函数需要多少 CPU 或 RAM 或任何其他资源,而是更看重运行函数所需的时间,我们也只为这些函数的运行时间付费。

 

 

无服务的设计带来的优点有:

  • 降低运营成本:
    Serverless是非常简单的外包解决方案。它可以让您委托服务提供商管理服务器、数据库和应用程序甚至逻辑,否则您就不得不自己来维护。由于这个服务使用者的数量会非常庞大,于是就会产生规模经济效应。在降低成本上包含了两个方面,即基础设施的成本和人员(运营/开发)的成本。
  • 降低开发成本:
    IaaS和PaaS存在的前提是,服务器和操作系统管理可以商品化。Serverless作为另一种服务的结果是整个应用程序组件被商品化。
  • 扩展能力:
    Serverless架构一个显而易见的优点即“横向扩展是完全自动的、有弹性的、且由服务提供者所管理”。从基本的基础设施方面受益最大的好处是,您只需支付您所需要的计算能力。
  • 更简单的管理:
    Serverless架构明显比其他架构更简单。更少的组件,就意味着您的管理开销会更少。
  • “绿色”的计算:
    按照《福布斯》杂志的统计,在商业和企业数据中心的典型服务器仅提供5%~15%的平均最大处理能力的输出。这无疑是一种资源的巨大浪费。随着Serverless架构的出现,让服务提供商提供我们的计算能力最大限度满足实时需求。这将使我们更有效地利用计算资源。

当然无服务设计也有一些限制,它更适用于大量,频繁而简单的,无状态的操作,如果服务之间的关系比较复杂,状态比较多,调用时间比较长,无服务设计并不是非常适合。

软件架构设计还有很多方法,这里不可能一一讨论,例如领域驱动的设计(DDD),重构技术,敏捷(更多是软件工程,但是和架构也会有密切地关系),希望以后是时间再和大家分享,讨论。

行走于软件江湖

如果软件行业是个江湖,那么我们程序猿就是行走于江湖的武林人士。每个人要做的就是找寻“道”,研习“术”,磨练“器”。

“道”是价值观,是你的江湖理想,是你的追求。就像《神雕侠侣》时期守襄阳,信奉“侠之大者,为国为民”的郭靖,。

“术”是你的内功心法和拳艺招数。数据结构和算法就像是内功,帮助你提高对战的效率。而各种其它的方法就像是招数。你可以像是学了独孤九剑的令狐冲,仅凭招数就可以杀敌无数,但是要成为武林盟主,内功修养也是不可或缺。

“器”是工具,各种语言,IDE,可以划分到“器”,“工欲善其事,必先利其器”。各位大侠行走江湖,免不了要选几样趁手的兵器,有人喜欢剑走偏锋,有人喜欢暴力砍杀。什么,你说你喜欢九齿钉耙?我只能赞你一声神仙威武!(参考如果编程语言是一种武器。)

江湖苦修非一日之功,希望各位大侠在软件江湖早日得道,成为武林至尊或者一方豪杰。

(四) :软件中的质量属性

开发高质量的软件是一件极具挑战的工作。其中一个重要的原因就是对于“质量”的定义各不相同,变化莫测。而软件架构的设计主要就是围绕满足功能要求和非功能要求,其中的非功能要求就是软件的各种质量属性。

杰拉尔德温伯格在他的四部曲巨作《质量软件管理》的第一卷第一章中就谈到了什么是质量以及质量的重要性。温伯格在书中讲了一个很有趣的故事。某软件企业每年都会根据所开发软件的质量对开发团队进行奖励,质量好的团队将会获得相应的嘉奖。可是如何评价软件的质量是一个令人头疼问题,于是他们采用了量化指标,根据用户反馈的defect的数量来定。某款软件质量超群,自推出至市场以来,只收到了一个defect。于是开发相应软件的团队获得了年度质量大奖!可是这款软件真的是一款高质量的软件么?看看这个defect的内容吧:“该软件无法安装!”

为了了解软件的质量是否满足要求,我们必须定义软件的质量属性(Quality Attributes)。同时质量属性也是影响软件架构的重要因素。软件架构主要由需求决定,需求有功能性的和非功能性的,其中非功能性的需求主要就是指质量属性,即各种“性”(“-ilities“)。wikipedia上列出了大概80种不同的质量属性,下面我们讨论一下其中最常见的属性。

 

可用性(Availability

可用性是指系统正常工作的时间所占的比例。可用性会遇到系统错误,恶意攻击,高负载等问题的影响。
当年在一家存储公司开发管理软件,"HA"(High Availability)是一个经常被提及的性能属性。"DU"(Data Unavailable)和"DL"(Data Lost)都是非常严重的可用性问题。
可用性面临的主要问题有:

    • 物理层失效:比如数据库服务器宕机,停电, 网络欠费被中国电信断网
    • 恶意攻击:例如DOS(Deny of Service)攻击
    • 软件的设计问题或BUG:比如错误的资源控制锁导致某个资源长期被占用,各种core dump, out of memery, out of stack。
    • 升级或日常维护

针对这些问题,为了增加可用性,需要考虑

    • 如何设计故障转移(failover),一般可以使用冗余来消除系统中的单点故障。可以是各种冷热备份,分布式系统。
    • 如何设计在线升级。我当年在那家存储公司的一个主要责任就是做在线升级,因为存储设备有两个同时工作的单元构成,所以升级的过程简单说就是先升级第一个单元,然后再升级第二个单元。听上去非常简单,然而实际的升级过程非常复杂,在升级之前,会做非常多的健康检查,比如检查两个单元是不是都在正常工作,有没有坏的磁盘,存储设备的版本是不是满足要求,等等等等。特别是由于存储设备的软硬件型号复杂,还要考虑各种不同的运行环境,各种软件BUG,健康检查非常非常的复杂。当然大多数情况下,用户可以跳过健康检查,然而由此引发的升级失败,后果自负!
    • 如何设计异常处理。异常处理是一个很大的话题,为了支持高可用性,我们应该如何处理异常呢?举个例子,一个网站要处理用户的订单,然而由于数据库服务器的故障,虽然前端的服务一切正常,可是订单无法处理。你会怎么处理这个数据库服务器异常的情况呢?大多数的程序员会在捕获异常的时候写日志,把异常状记录下来,然后在客户端的UI上显示一段谁也看不懂的异常代码。这样的异常处理其实跟没做差不多,甚至更糟。那么把异常代码翻译成用户可以看懂的语言是不是会好一点呢?也许会,如果你告诉用户因为你的数据库故障,请明天再来,可想而知用户会多么失望!好的异常处理是把用户的订单请求记录下来,发给人工处理,或者等待数据库恢复后自动处理,并告诉用户订单已经处理,并有可能迟延,请求得到用户的谅解。
    • 如何应对不稳定的网络连接。

 

灵活性 (Flexibility

灵活性是指系统是否能够很容易的适应环境和需求的变化。
例如现在需求是返回10以内的所有质数。我们可以使用以下程序:

function prime(){
    var result = [2,3,5,7];
    return result;
}


这段程序非常好,性能也非常高。然而非常的不具备灵活性,通过对需求的分析我们似乎可以大胆的预见10是一个非常有可能会改变的需求,于是提高灵活性的方式就是把10变成可变参数:

function prime(range){
    var result = [];
    var i,k;
    for(i=2; i<=range; i++){
      result.push(i);
    }
    for(i=0; i<result.length; i++){
      for(k=i+1; k<result.length; k++){
        if(result[k]%result[i]==0){
          result.splice(k,1);
        }
      }
    }
    return result;
}



我们看到第二段代码为了增加灵活性,代码变的更复杂,运行时间变长。当然第一段代码中的质数根本就没有经过计算验证,完全是我自己计算出来的,因为10以内的质数这样简单的运算根本不需要计算机。
在软件开发中有哪些问题会引起灵活性下降呢?

    • 由于各种原因而造成的数量惊人的代码
    • 过于复杂的代码
    • 不断重复的代码

糟糕的软件中这三点往往是一起出现的。

软件系统通常可以通过以分层,组件化等方式来提高灵活性。我在实践中的原则是”用简单构造复杂“。软件系统本身是非常复杂的,然而构建软件系统的基本单元却应该是非常简单的。例如计算机的基本组成单元是门电路,每一个门电路都非常简单,然而计算机系统却是如此的复杂和灵活。凯文凯利在他的《失控》一书中,也有同样的观点。

构建灵活软件系统的关键在于找到那个简单单元的边界,每一个单元应该足够的简单,但是不能够过于简单,“Simple,but not Simpler!”

概念一致性 (Conceptual Integrity

我们很少在软件设计的时候谈论概念一致性,也许我们认为概念一致是一个共同的假定,然而实际上,在软件开发的过程中,往往会出现很多概念不一致的情况。
概念一致性的问题主要表现在以下的方面:

    • 在一个模块的设计中混杂不同的问题域
    • 不同的组织或者团队负责系统中的同一个功能
    • 没有统一的代码规范
    • 为了满足后向兼容,系统中存在新旧两套不同的代码栈

我们可以看出,这些问题大多是管理的问题。我个人认为,为了实现概念一致性,在软件的设计过程中应该尽可能少的引入新的概念。软件设计的过程是一个抽象的过程,我们把复杂的软件系统抽象为一个个的层,问题域,过程,模块,服务,接口,这些都是非常必要的。但是这些东西都应该是越少越好,能在一两层解决的问题,绝不要划分成四五层,能提供一个API接口,绝不要给三个。人类能够同时掌握的概念是有限的,大部分人可能也就三四个把,当你设计的系统中有五六个或者七八个陌生的概念需要同时掌握的时候,对你的团队中其它需要使用你的设计的开发人员来说绝对是一个巨大的挑战。大部分人不会费力气去搞懂你设计的高大上的新概念。他们很有可能会设计出一套对他们自己更容易理解的并行的方案来解决同样的问题。

互操作性 (Interoperability

互操作性指的是系统内或者系统之间不同的组件可以有效地进行信息交换,通常是以服务(Service)的形式来进行的。互操作性的关键因素包括通信协议,接口定义,数据格式的定义等等,而标准化是实现互操作性的重要手段。

实现互操作性的主要挑战有以下这些方面:

  • 系统内部或者和已有的旧系统(legacy system)之间的数据定义不一致
  • 系统的边界模糊,模块之间耦合严重,导致数据冗余
  • 缺乏标准,或者各方对标准的实现和认识不一致

我现在所在的商务智能团队的总架构师(Chief Architect)就一直在部门间推动对数据文档统一格式标准的定义和实现。这本身对于我们产品内部的互操作性是非常有必要的,然而BI的团队分布在各个大洲(主要是德国,法国,加拿大,中国和印度),每个部门对各自产品优先级的认识不一致,在加上对旧系统兼容性的要求,这项工作的进展非常非常的缓慢。BI的各个产品仍然很难互操作。

我之前开发过通信网管,当时做的产品是统一网管平台,就是把各个厂商(华为,中兴,朗讯等等)的电信设备统一的管理起来。当时已经有了相当成熟的电信网管理标准(TMF,ISO等标准)和技术标准(Q3,Corba)。然而理解的不同,厂商对标准的实现千奇百怪,所以实际上需要给每一个厂商定制不同的接口适配器。我当时就在负责一些这样的接口开发。

面向服务的架构(SOA)曾经是一个非常热门的词汇,现在似乎不怎么提起了。我司当年曾经大张旗鼓的宣传SOA。其实这样的架构能够解决的一个主要的问题就在能够把企业内部各种已有系统通过暴露标准服务接口的方式有效的协调起来。

为了实现互操作性,我们一般需要

  • 定义标准的数据格式和语义
  • 定义标准的服务接口,并使用基于服务的架构
  • 设计高内聚,低耦合的模块以获得最大的灵活性可重用性

可维护性 (Maintainability

可维护性有两个不同的角度,一个是指从软件用户和运维人员的角度,另一个是从软件开发人员的角度。

从用户和运维人员的角度,软件的可维护性是指软件是不是容易安装,升级,打补丁,有了问题是不是容易修复,能不能很容易的获得支持。

从开发人员的角度,软件的可维护性是指软件的架构是不是清楚简单,代码是不是容易阅读,有了问题是不是容易定位错误的原因,有没有可以提供帮助的文档,等等。

软件系统可维护性的主要问题有:

  • 模块之间紧耦合导致无法或很难对单独的模块进行修改,替换和升级
  • 在高层直接使用底层协议和接口,导致无法替换物理设备实体
  • 没有有效的分层和责任的划分,导致一个肿大的模块以及巨大的代码出现在同一个文件甚至函数中。
  • 没有帮助和设计文档
  • 为了兼容旧系统而不得不同时存在两个以上的复杂的代码栈甚至技术不同的实现

知道了问题,改进的建议是非常明显的,我要讨论的是一个关于代码可读性的有趣话题。

很多人都认为,代码是写给人看的,它碰巧也能被计算机读懂。所以我们应该像写文学作品那样写代码。

碰巧我也非常的赞同这样的观点。然而,最近的一篇文章提到另一种观点,代码不是文学作品,我们不是阅读而是评审。

这篇文章也许能够帮助你改善代码评审。不管代码是不是文学作品,写出容易阅读的代码对于提高软件的可维护性的好处是不言而喻的。


性能(Performance

性能也许是软件开发中最被重视的质量属性,也是最特殊的一个,从它的英文名字中不以(-ility)为后缀,我们可以看到他的特殊性。

我们通常以系统执行某操作所需要的响应时间(latency)或者在某单位时间所能完成的任务的数量(throughput)来定义性能指标。

性能和其它的质量属性的相关性很高,有一些会对性能产生正面影响,有一些则是负面的。

记得我当年参加一个关于C++性能优化的培训,有一道算法,要求大家试着用最快的方式实现。

因为C++中的指针操作按数组索引访问要快,于是当时最快的一个解决方案是用了一大推复杂指针访问来实现算法。然而这样的代码很难维护,而且容易出错。所以为提高性能就牺牲了可维护性。
一般而言,计算机提供了许多的资源,包括CPU,内存,硬盘等等,提高性能的核心就是充分利用这些资源。要保证对资源的使用是正确和有效的。

通常提高性能的考虑包括:

  • 利用缓存(空间换时间)
  • 设计高效的资源共享,多线程,多进程,锁
  • 异步
  • 减少模块见得信息传递
  • 使接口设计传递最小所需的信息
  • 增加系统的可伸缩性,是系统能够有效的部署在分布式的资源上

另外我们还需要考虑另一个性能,就是程序员实现功能的性能。随着软件的发展,现在的程序员可以更高效的实现功能需求。

一方面编程语言和方法在不断进步,另一方面大量的可重复使用的组件,服务,开源的库使得想在实现同样的功能的时间和需要的开发人员的数量比以前极大的缩小了。

whatsapp以区区55人的团队开发出价值190亿美元,拥有4.5亿用户的软件产品,这在以前是难以想象的。所以软件架构设计者应该把软件的开发效率看成是更重要的性能指标。

 

可重用性 (Reusability

老板一般都非常重视可重用性,因为如果把软件代码看成产品,那么如果该产品如果只能用一次就扔掉,那他显然是很不开心的,因为这是一笔很糟糕的投入。

在我的软件开发生涯这么多年以来,我体验的软件的可重用性都不是很高,也许是我大多在大型的软件企业服务有关。

大企业的特点就是团队非常多,产品非常丰富,老板经常更换。

每一个新的老板上台后,面临一大堆功能技术各异的系统都非常的不happy,于是整合在所难免,如何重用已有的系统来实现一个新的,大一统的新产品成了重中之重。

然而这并不比找到宇宙中的终极大一统理论更简单。

最终的整合结果往往并不能有效的重用已有的系统,当老板因为各种原因离开时,我们会发现,对新的老板来说,整合的任务变得更加艰巨了,因为,又有一个新的系统需要整合了。

提高可重用性的一个最主要的原则就是避免重复,“Don't repeat yourself!”我想大家应该非常熟悉了,这里就不多说了。

在成熟度不高的开发团队中(很不幸,我们大多数人都处在这样的团队中),对代码的重用很难,其实只有人才是最可靠的可重用组件,人离开了,所有的可重用性也就跟着离开了。

 

伸缩性(Scalability)

伸缩性要求软件系统能够跟着所需处理的工作量相应的伸缩。例如如果计算机是多CPU多核心的,软件是否能够相应的利用到这些计算资源。

另一个方面就是软件是不是能够部署到分布式的网络,有效的利用网络中的每一个节点的资源。

有两个方向的伸缩,垂直和水平。

在垂直方向的伸缩(scale up)是指提高单节点的处理能力,比如提高CPU主频和内核数,增大内存,增大磁盘容量等等。SAP的HANA就是一个典型的垂直方向的伸缩。

在水平方向的伸缩(scale out)通常是指通过并发和分布的方式来增加节点以提高处理能力。Hadoop就是一个很好地水平伸缩的例子。

设计高伸缩性的软件时,我们可以考虑

  • 避免有状态的服务或组件
  • 使用配置文件决定组件的的部署和关系
  • 考虑支持数据的分区
  • 避免统一层的责任跨越不同的物理实体

在云时代,软件的伸缩性越发重要。

 

可测试性(Testability)

可测试性顾名思义就是指软件是否容易测试。

什么样的软件是不可测试的呢?举个例子来说,我们曾经开发过一个数据可视化的组件,就是把数据以图表的形式展现出来。

有一种数据可视化的类型使用力导向的算法(force-directed)把数据以网络拓扑图的形式展现出来,该算法使用一些随机的参数来模拟节点的初始位置,并通过迭代计算生成最终layout的结果。

也就是说每次的layout结果都是不一样的。测试团队对这样的算法很不满意,他们认为这样的实现是无法测试的,因为当时我们的测试主要以比对图形为基础,也就是生成一个正确的图形为基准,每次测试都会把输出的图形和基准图形进行比较,如果不一致则认为出错或者要修改基准。随机算法虽然从功能上讲并没有错误,但是在这样的测试方法下是无法满足可测试性的要求的。最后,开发团队修改了算法,使得每次的初始位置未固定位置来解决这个问题。

David Catlett提出了一个SOCK模型可以有效地帮助我们了解可测试性的要素

  • Simple
    代码越简单越容易测试。
  • Observable
    软件系统应该是可观测,无法观测也就无法衡量
  • Control
    软件系统应该是可以控制的,尽可能多的把控制权暴露给测试模块。
  • Knowledge
    测试人员或者模块需要更多地理解被测试模块,理解的越多也就越容易测试(白盒测试)

当我们设计软件的时候,需要定义哪些质量属性是我们希望实现的,切记,质量属性并非越多越好。一般来说找到最重要的几个来构建软件就好了,而且"鱼和熊掌不能得兼",各个质量属性之间有可能是互相矛盾或者互相影响的。分布式数据库中的CAP理论就是一个典型的例子。对于一个分布式的计算系统可不能同时满足一致性(Consistency),可用性(Availability),分区容忍性(Partition Tolerance)。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值