之前在谈谈架构设计的目的 这篇文章中说过,架构设计的目的就是为了解决软件系统复杂度带来的问题。

但是究竟复杂度有哪些呢?所以今天借此说说软件复杂度的六个来源:

1.高性能;

2.高可用;

3.可扩展性;

4.低成本;

5.安全;

6.规模;

一、高性能

对性能孜孜不倦的追求是整个人类技术不断发展的根本驱动力。例如计算机,从电子管计算机到晶体管计算机再到集成电路计算机,运算性能从每秒几次提升到每秒几亿次。但伴随着性能越来越高,相应的方法和系统复杂度也是越来越高。现代的计算机CPU集成了几亿颗晶体管,逻辑复杂度和制造复杂度相比最初的晶体管计算机,根本不可同日而语。

软件系统也存在同样的现象,比如以淘宝为例,最早期间可能最多支撑几千人的访问,但是,随着用户群体的增长和业务规模的扩大,已经远远不止几千人,这时对于性能方面就要求很高,总不可能,用户买个东西点击搜索或者查看商品详情出现卡顿现象吧。

软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。

(1)单机复杂度

以我公司最早项目来说,刚开始一个服务器对一个项目,那个时候还好,很少出现服务器宕机现象,但是当一台机器上运行好几个tomcat,而且好几个tomcat上都分别放置web项目,那个时候就是一台服务器,测试和线上都在一个服务器,最后总是频繁宕机,虽说,当时靠着shell脚本时刻监控着,发现宕机,就自动重启。但是那也无济于事,后来改变了下,将所有的项目集中在一个tomcat上,将这个tomcat的优化程度做到最高,关于tomcat优化,可以参考我的这篇博客:Jmeter之tomcat性能测试+性能改进措施

然后在这个基础上再做了nginx动静分离和负载均衡。也许有人问什么是动静分离?

简单的说,就是由Nginx处理静态资源(img,css,js等之类),Tomcat处理动态请求(比如接口或者jsp之类的),Nginx作为高性能Web服务器,自然对于静态资源的处理效率高的多。

这个负载均衡做的有点名不符实,仅仅只是在一个tomcat上。不过在做了将tomcat优化配置和Nginx相关优化配置和动静分离等后,发现网站的性能变的高了,关于网站性能测试可以参考这篇文章:网站在线性能测试分享

这个性能不仅仅指的是网页性能,同时还包含并发,目前并发仍然不是特别高。

上面我只是提提最初的到后来改变,但是一个服务器上随着后面我们慢慢有了私服和jenkins,还有装上了redis和phpmyadmin等,每个软件都会有对应的进程运行者,这也会占用很多内存。最后虽然勉强运行起来,但是时不时还是回到了卡顿,最后我只能将一些不必要的去掉(也许有人说,再买个服务器呗,反正现在的阿里服务器也很便宜,但是呢,经理说了,目前这个服务器只是测试服务器,以后正式上线再买一个好的)。

有点说偏了,最后提一下,单机复杂度的因素主要是进程和线程,,比如进程之间通信、多进程、多线程、多线程并发等。

(2)集群的复杂度

集群的复杂度主要体现在这么几个方面?

a.任务分配

比如当服务器由一台变成十台甚至上百台时,如何保障对应的请求分发获得对应的响应呢(web的本质基本上还是遵循HTTP协议,同学们,有时间还是得好好回顾回顾一下HTTP,关于HTTP相关的,我觉得朋友们可以参考我的这篇文章:谈谈HTTP)?

b.任务分解

通过任务分配的方式,我们能够突破单台机器处理性能的瓶颈,通过增加更多的机器来满足业务的性能需求,但如果业务本身也越来越复杂,单纯只通过任务分配的方式来扩展性能,收益会越来越低。为此,为了解决这个问题,于是就有了任务分解。

任务分解,在此你可以理解,如果一个项目有上百个子系统,可以将子系统分类并放在不同的服务器上,以此达到一个系统只办一件事(系统只办一件事总比系统办好几件事情效率要高的)。

考虑到依赖性,如果好几个系统杂糅一起,不做好合理的业务分离,最后可能演变成这样,修改了A系统,还得修改B系统,修改了B系统,还得修改C系统,这是一件非常麻烦的事情。相信,无论是项目经理还是开发的小伙伴们都不愿意看到这种现象的出现。另外还有一点需要强调的是,分解后,模块变小,有助于更好的发现问题。

针对高性能,摘抄李运华先生提出的一个问题,问题如下:

你所在的业务体系中,高性能的系统采用的是哪种方式?目前是否有改进和提升的空间?

我对此的回答如下:

我们目前开发出的三个系统,由于目前没有完全上线为客户服务,对于性能方面要求不是特别高。

不过要问,采取的是上面哪种方式,目前仅仅只是Nginx负载均衡+动静分离,还仅仅只停留在单体应用上面。也没有做一些主从复制、读写分离之类的。目前用不到。

如果要说未来扩展方面,目前,根据我们经理的预测,我谈谈我的想法,我觉得我们将来更偏向于集群+任务分解方面。原因很简单,复杂问题简单化。不过在此情况下,一定会遵循前面提到的架构设计三原则,对此有兴趣的朋友,可以读读:架构设计三原则

目前的改进和提升空间,我觉得目前的改进和提升空间不在于架构方面,而在于规范方面。很多规矩都不是特别成型,显得有点散,不过目前相比之前,还是有很大的进步,为此我也感到高兴,我很害怕团队退步,毕竟一个团队的氛围对于一个人影响还是比较大的,不过我觉得当团队规模逐渐扩大时,这个问题,一定会迎刃而解的,目前,只所以这样,也考虑到一个重要的因素,那就是人员的流动性(众所周知,对于创业团队,特别是有一段时间,如果团队中有人离职,将会给这个团队带来不少的损失)。

二、高可用

什么是高可用?

系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一(摘自维基百科)

维基百科说的有点太官方了,没有百度百科这么直接。百度百科是怎么说的呢?如下所示:

“高可用性”(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性。

关键字在于“可用”,以诺基亚手机来说,诺基亚手机质量确实不错,用的好几年觉得还是那么的好用,说明诺基亚的可用性还是不错的(虽说功能并没有目前哦苹果、安卓手机那么强大,但是它的质量是没的说的)。

另外再说一点,高性能和高可用是有根本的区别的:

高性能增加机器的目的在于“扩展”处理性能;

高可用增加机器的目的在于“冗余”处理单元。

通过冗余增强可用性,但同时也带来复杂性。

(1)计算高可用

这里的“计算”,指的是业务的逻辑处理。计算有一个特点就是无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的,所以将计算从一台服务器迁移到另外一台服务器,对业务并没有什么影响。

可以以单机架构和双机架构对比,

单机架构:客户端-服务端

双机架构:客户端-任务分配器-服务端(多了这个中间层,不要小看这个中间层,目前的宽带上网之所以能够确保用户上网隐私不被侵犯,很大原因是因为我们是通过路由器来上网,笔记本电脑-路由器-外网,我们只需连接路由器即可,由路由器连接外网上网,对于外网而言,它只知道是路由器,而不知道是我们)。

(2)存储高可用

对于需要存储数据的系统来说,整个系统的高可用设计关键点和难点在于“存储高可用”。存储与计算机相比,有一个本质上的区别:将数据从一台机器搬到另外一台机器,需要经过线路进行传输。线路传输的速度是毫秒级,同一机房内部能够做到几毫秒;分布在不同地方的机房,传输耗时需要几十甚至上百毫秒。

虽然毫秒对于人来说几乎没有什么感觉,或者是说完全就没有什么感觉,但是对于高可用系统而言,就是本质上的不同。按照“数据+逻辑=业务”的这个公式来套,数据不一致,即使逻辑一直,但是最终的业务表现就会不一样,没有达到预计的效果。

(3)高可用业务决策

无论是计算高可用还是存储高可用,其基础都是“状态决策”,即系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动保证高可用。如果状态决策本身是有问题的,那么后续的处理和行动都是没有意义的。另外有一点要强调一下,状态决策本质上就不可能做到完全正确。

a.独裁式

天下之大,唯我独尊。所有的都要听我的。比如,以集中式举例,SVN是一种集中式的版本控制系统,如果其中央仓库出了问题,会影响整个项目团队的研发。关于SVN和Git的对比分析,可以参考我的这篇文章:SVN和Git的比较

b.协商式

常见的表现就是主备,可以联系到生活实际,男女交往,有不少男性朋友或者不少女性朋友沦为备胎。也许这个例子有些不恰当,但是往往也可以说明一点,当主机出现问题时,不能正常提供服务,备机替代主机保障正常服务,不受宕机的影响(联系到服务器)。

c.民主式

这个民主式我一时看不太明白,不过我认为前两种是比较常见的(a,b) 。

摘自李运华先生提出的一个问题,问题如下:

高性能和高可用是系统的核心复杂度,你认为哪个会更复杂一些?理由是什么?

我的回答如下:

我觉得不管是高性能还是高可用,前提是确保业务正常服务于客户。

理由如下:要说复杂度,我认为还是取决于业务,比如有的业务它并不需要高性能,比如特定公司的人群定制化使用的办公软件,只需要保障客户使用的时候不会出现一些功能方面的错误或者是业务方面的bug。

三、可扩展

可扩展性指的是应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无需整个系统重构或者重建。

回忆当初我们的PMS系统,为了实现某个功能,牵其一必动其余,那段心酸加班的苦日子,至今让我难以忘记。当然了,也许当初的考虑没有那么周到,写的代码也不够严谨规范,当然了,最重要的就是对业务不了解熟悉(犯了没有完全理解需求就直接开敲这样的错误,我想每个工作年限不长的人或者工作年限小于两年的都可能犯这样的错误)。

记得当初我还有一个这样的想法,幻想着将20多种设计模式全部应用到代码中,只要应用到代码上,代码质量、项目质量都会提高。不过,幸运的是我并没有那么做。

原因有这么几点?

(1)我自己对设计模式而言,仅仅只是知道它的名字,不知道它在实际的应用场景和怎么写的;

(2)由于(1)缘故,在我心中有这么个理念,没有把握的事情绝不干(虽然生活中干了不少没有把握的事情,比如曾经对某某一见钟情,就对其表白,最后的结果只能是以失败告终,如果结果对方同意,说明了两点,第一点,的确情投意合;第二点,这是一个陷阱)

(3)当时手里项目太多,由于之前系统遗留的问题,不停的解决问题,当然了,当解决以后,面对这么臃肿的项目,我只想说四个字(四个字是什么我就不说了,有些不文明),不过的确面对一个很蛋疼的项目,要重构的话,不仅仅是时间问题,更重要是勇气,当然了,还有必不可少的筹备。

扯了一些相关又无关的话,下面进入正题说说。

首先有一个问题,什么才算是一个可扩展性良好的系统?可扩展性良好的系统具备哪些条件?

其实这两个问题可以理解为一个问题,不过我觉得还是分开说好一些,因为更全面。

针对第一个问题,我给出的回答是:

可扩展性良好的系统,用一句简单粗暴的话来说就是,面对产品经理变态的需求,你不需要内心暴打产品经理一百遍,不需要加班加点。一个字,就是“干”。这个“干”,不是之前痛苦的“干”,而是快乐的“干”。正常来说,之所以程序员对于产品经理抱着仇视的态度,关键不在于需求怎么样,而在于,实现这个需要需要改很多地方,改完很多地方,有可能又会出现新的问题,这才是最要命的。快乐的实现,在于将需求吃透的前提下,不需要改完这个改那个,只需在此基础上扩展就能达到这个目的。

我想如此,便是一个可扩展性良好的系统。

针对第二个问题,我给出的回答是:

一般在设计一个可扩展性良好的系统,需要具备这么两个基本条件:

(1)正确预测变化;

(2)完美封装变化;

关于(1),软件系统与硬件系统或者建筑相比,有一个很大的差异:软件系统在发布后还可以不断地修改和演进,这就意味着不断有新的需求需要实现。如果新的需求能够不需要改动代码或者是少改动代码就可以实现,肯定是皆大欢喜的,否则来一个需要就要求系统大改一次,成本会非常高,程序员心里也不爽(改来该去),产品经理也不爽(做的那么慢),老板更不爽(那么多人就只能干这点事情,要你们何用)。因此作为架构师,我们总是试图去预测这些变化,然后设计完美的方案来应对,当下一次需求真正来临时,架构师可以自豪地说:这个我当时就已经预测到了,架构能够完美地支持,只需几个小时就可以了(在团队成员眼里是多么牛逼的存在)。

有一句名言叫做:理想很丰满,现实很骨感。

在IT界中有一句谚语叫做:唯一不变的是变化。

这句话值得细细品味,比如如果架构师在每个设计方案都要考虑可扩展性,那么架构师会不堪重负,有一句话叫做,鱼和熊掌不可兼得。

另外关于预测,预测本身就暗示了不可能每次预测都是正确的(如果每次预测都是正确的,你可以去做预言家了)。

关于预测变化的复杂性,主要有这么几个体现:

a.不能每个设计点都考虑可扩展性;

b.不能不完全考虑可扩展性;

c.所有的预测都存在出错的可能性;

对于架构师来说,如何把握预测的程度和提升预测结果的准确性,是一件很复杂的事情,而且没有通用的标准可以简单套用,更多的是靠自己的经验、直觉,所以架构评审时,经常会出现两个设计师对某个判断争的面红耳赤的情况,原因在于没有明确标准,不同的人理解和判断有偏差,而最终又只能选择一个判断。

关于(2),为了应对变化,通常将变化封装起来。

常见的方案有这么几个?

a.将“变化”封装在一个变化层,将不变封装在一个独立的稳定层;

b.提炼出一个抽象层和一个实现层;

关于a,可以用一句话来概括“不同的人办理不同的事情”,比如在社会生活中有形形色色的人,有的人善于阿谀奉承,有的人踏踏实实,阿谀奉承的人总有一天会因为某些事情而做了嫁衣,而踏踏实实的人却不受到半点影响,这个社会需要实干家。在写代码中可以反映出,一个函数只办一件事情,变化层应对那些时常变化的,稳定层就待在它的稳定层,这样也解耦,不会有影响变化层有问题而还要改稳定层,不会因为稳定层有问题而改变化层。

关于b,对于抽象层和实现层,对于Java开发很常见,比如接口和实现类就是一个很好的体现。

来自李运华先生的思考题:

你在具体代码中使用过哪些可扩展的技术?最终的效果如何?

针对这个问题,我对此的回答是:

可扩展的技术倒没有使用太多,主要是从代码上扩展,比如从简单方面做起,每次Controller都要打印对应的日志,我让AbstractController继承log4j日志,这样每次Controller需要打印日志不用每次在类里面声明一下。还有就是将Controller代码简化,比如之前Controller太过繁杂,以至于业务逻辑全面在Controller里面,其实最好还是在service中将其写清楚,Controller方面能简化则简化。当然了,还有就是统一代码规范,这里我参考了《阿里巴巴的Java开发手册》,同时也自定义一些特定组件。

最终的效果是,代码耦合性降低,代码的可读性提高,可扩展性提高很多。效果虽然没有达到我预想的那样,但是已经进步了不少。这也是一件让人高兴的事情。

小结:

这两天由于合作方的需要编写了大量文档,这篇文章预计应该昨天就发表的,但是还没有写完。今天仍然没有写完,因为还有低成本、安全、规模等三个方面还没有讲到,我准备留到下次再讲,我觉得高可用、高性能、可扩展性等三个方面,大家可以仔细读读,多多思考。一定会有你想不到的收获。

12-01 05:56