系统设计经验谈

性能(performance)设计非常重要,对于服务器端实时交易系统来说系统性能的重要性不言而喻,对客户端软件来说性能好的软件也会获得良好的用户体验,从而给用户留下高质量软件的良好印象。因此在进行架构设计中性能设计非常重要。

      但架构设计实际是一个平衡设计,在可用性、可扩展性、可维护性、可靠性、高性能等之间做个妥协选择。这些非功能性的需求再加上复杂的功能性需求,同时还要考虑到项目管理上tight schedule, low cost, perfect effect的三角难题约束,有时需求还不是很明确,vision不是很清楚,这种情况下系统架构设计真是一门艺术。(可参考笔者另外一篇文章《也谈系统设计的一些原则》)

      单就性能设计来说,在架构设计初期就一定要把系统性能考虑在内,否则等开发完成以后测试发现性能不好就比较难办,通常要花费较长的时间来诊断性能瓶颈,找到提升的办法,甚至要改变架构,伤筋动骨,往往造成项目延期。所以性能设计首先要有明确的性能目标,根据用户和软件本身的性能要求来设计,合适的就是最好的。其次,要有适当的度量标准和量化的性能指标。最后,要有相应的设计策略,具体的测试方法。

      根据我的经验,影响系统性能主要瓶颈在I/O,包括数据库,socket,网络通信,文件等,例如频繁查询数据库并返回大量结果集,频繁操作大文件等,这些昂贵的操作会占用大量的CPU时间。拿系统响应和服务一个事务来说,有几个Round trip,要通过哪几层I/O,如何合理的分配这些I/O的调用,降低不必要的I/O,都是进行系统性能设计要考虑的。而有些性能问题在初期并不会表现出来,但当拿到实际上线环境下,存在多用户并发、大数据量的情况下就会暴露出严重的问题。所以性能设计时一定要考虑到I/O,同步,并发,资源争用,以及大数据量等因素。通常,I/O操作、网络响应、差的算法、数据库、以及其他的低效的资源使用都会导致低劣的性能。

具体可用的设计策略有:

l        缓存以及缓存层(caching layer)

    在数据层和应用层之间增加数据缓存层,提供全局数据服务。可以大大减少数据库往返次数。与读取数据库和读取大文件(如XML文件)比,读取内存的速度无疑要快的多。所以对经常要访问的数据进行缓存是非常好的实践方法。因为现在系统往往内存很大,可以充分利用大内存,而共享内存更能实现数据并发访问。

l        多线程(multi-threading)

    现在基本上大部分软件实现多线程或多进程,多线程对单CPU系统还只是顺序利用CPU时间和改善用户体验,多CPU系统才是真正的并行。要注意的是多线程不要争抢访问同一资源而导致部分串行操作,要做到真正的并行操作多线程并不容易。另外,在多线程间同步一个庞大的资源,过多创建线程又没有实现线程池也会导致系统性能下降。

l        负载平衡(load balancing)

    物理上增加地位对等的集群服务器(Cluster),通过负载分配算法分配相应服务器来相应客户端请求。很多系统支持负载均衡,Windows server2003 IIS就支持负载均衡服务,其他如WebLogic, WebSphere也有集群版本支持负载均衡。当然你也可以自己实现负载分配算法。

l        数据库优化(database optimization)

   如果应用程序使用了数据库,可以采取许多步骤来消除访问和写入数据时的瓶颈:

Ø         标识潜在的索引,但不要创建过多的索引。

Ø         如果使用 SQL Server,则使用 SQL Server 的事件探查器和索引优化向导。

Ø         监视处理器的使用;理想范围是:75-80% 处理器时间。

Ø         使用查询分析器分析查询计划以优化查询。

Ø         使用存储过程优化性能。

Ø         标准化写入的大量数据 —写入较少的数据。

Ø         取消标准化读取的大量数据 —读取较少的数据。

l        文件系统优化

     有时候系统性能不好,但当你关闭写log的功能,性能一下子提高很多。因为频繁的打开关闭大log文件时I/O开销非常大,同样记录log到数据库也一样。所以,release版尽量减少写log,或干脆移到裸设备上。

      频繁打开关闭文件对系统性能下降程度是惊人的,可以通过一些变通办法来减少文件的频繁操作。

      例如,原来的缓存持久化实现是保存在XML文件,每次要获得一个配置项,都打开XML文件,通过XPath拿到这个配置项的值,这样效率不高,而且容易把这个XML文件lock住;改进的方法是:通过比较XML文件的修改时间(System.IO.File.GetLastWriteTime)判断是否要再次打开文件,大大提高了效率;另一个可以改进的方法是:启动时读取所有配置到一个静态的HashTable,每次要获得一个配置项都从内存HashTable获取,在最后或适当的时候持久化到XML。



l        代码性能设计

             在编程实现上,代码性能设计也很重要,一些昂贵的操作会占用大量的资源和CPU时间。例如,字符串相加没用StringBuilder, 频繁创建对象,差劲的排序或递归算法,过多的装箱拆箱,过多的使用反射(Reflection),频繁new HashTable或大的数组,用异常(Catch Exception)用做正常的逻辑,使用复杂的正则表达式,等等。具体可以参考《Effective C++》《Effective C#》等书籍。

l  语言的选择

   另外,语言选择也很重要。比如相对于Java, C#, C++, 大多数OLTP系统用C语言效率高的多,因为在所有的高级程序设计语言中,C程序设计语言的运行效率是公认的。再比如我们熟悉的一些框架,框架本身是C#或是Java的,但其核心独立模块是C++封装的,这样可以达到最佳的性能。所以对于一些特定的业务需求目标和数据的具体情况,对于核心的模块或算法,可以用特定的语言来实现以获得更好的效率。

l  应用层   

    比如应用层和数据库的API,在.Net中就有就有DataReader、DataSet和IList等的选择以及转换等,这个根据具体情况而定;还有就是大家常采用的数据的格式化和压缩,以及采用分页,减少传输的数据量;是否可以把一部分处理逻辑放在客户端呢,减少服务端的工作量。界面端也是有很多针对性能优化的考虑,例如绘图,控件重绘都是非常耗资源的,各控件的数据加载和数据绑定性能也各不相同,尽量采用惰性加载,异步加载;初始化和启动速度等都是需要考虑和优化的。








在进行系统设计时,不仅要考虑软件的功能性需求,还要考虑非功能性需求,比如软件的性能(Performance)、可扩展性(Scalability),系统的稳定性(Reliability)、部署(Deployment)和更新(Upgrade),可维护性(Maintainability),版本的管理,系统的安全(Security),界面的友好程度可用性(Usability, User experience)等。要想覆盖所有需求,实现一个简单而优秀的系统,可谓艰难。

 

大道至简,合适最好

   什么是优秀的系统设计? 这个问题颇有争议,但几乎每个软件工程师和架构师都追求优秀的系统设计。当然,系统设计并不代表结果,系统设计只是架构师或者带头程序员的工作,优秀的系统设计必须经由良好的项目管理和团队努力,经过分析需求、设计、开发、测试、分发、维护,以及迭代或重构的过程。中间哪个环节出了问题,再好的设计都将功亏一篑。

 

可能每个人都对自己设计的系统很自信很满意,但“实践是检验真理的唯一标准”。如果一个系统设计经过实践证明,大家(指客户或用户)公认为优秀的系统,那就是一个优秀的系统设计。

大道至简,适合的就是最好的。其实设计并没有那么严重,适合的就是最好的,简单最好。软件也是一种服务,这个系统设计出来就是为了服务一些用户还没有被满足的需求,如果你能够恰好满足了这些没有被满足的需求,而且能以比较低的代价提供这种服务,那这就是最好的系统。因为系统设计的来源是商业需求,而商业追求利益最大化。你的软件和服务必须比别人功能更加先进,更加好用,对变化的商业 需求反应更加灵活,推出或者升级的速度更快,开发和维护成本更低,才能证明这个系统设计的优秀性。所以系统简单,不能说明你的系统不优秀,说不定设计者有化繁为简的过人能力;系统复杂,功能繁多,也不能说明系统优秀。

技 术人员常常犯的错误是技术至上,技术第一,不计成本的去设计和开发无比先进和灵活的系统,不计风险的去采用最新的没经过实用的新技术。所以作为架构师,不仅仅需要精通技术,更需要良好的沟通协调,去了解业务和客户真正的需求,真正站在客户利益角度和最终用户利益角度思考问题和设计系统,在各种选择中做出权 衡。

 

极限编程人士的一个响亮的口号是“You aren't going to need it”。这其中包含的核心意义就是不要为了考虑程序的可扩展性,把目前不需要的功能加入到软件中来。不要过度设计。抓住重点,合适就好。比如根据二八原则,80%的用户只会使用20%的功能,而这20%的功能就是客户最关注的最需要的功能,也就是软件或服务的“卖点“,系统设计时必须集中精力和充分考虑到这部分需求。如果把精力放在某些花哨的功能上,既不 重要,也没必要,那就是过度设计。要想避免过度设计,我觉得可以遵循敏捷开发方式来做。尽可能的简单设计,当满足不了时,重构;保证产品是可运行的,不断的加入新的特征;产品经常性的提交给客户使用。

 

稳定压倒一切

   标题党的风格,但这个口号在社会上如此,系统设计上也是如此。系统的稳定性压倒一切,即使牺牲性能,也要确保系统的稳定。即使扩展性不强,也要保持系统稳定。即使用户界面不友好,也不要系统不稳定。

为什么用户这么关注稳定性?由于人们日常的工作对系统依赖程度越来越多,因此系统必须可靠。举个例子,我想我们都玩过CPU超频吧,频率高了确实挺爽,但如果一天死机几次,正在干的活全部丢失,你心里还会放心踏实的工作吗?同样的道理,一个网站如果不稳定,经常宕掉,用户的信息和辛辛苦苦写的文章搞丢失,还会有人上这个网站吗?所以稳定性是系统设计最重要的方面。

 但实施起来比说起来困难,有时候为了实现系统灵活的扩展性,导致系统架构不稳定;也有时候为了实现高性能,导致系统不稳定。如何取舍和平衡,不同的需求,不 同的架构师会做出不同的抉择。但个人认为稳定性放第一位,再先进灵活的框架,再牛的功能,如果不够稳定,坚决不要没有充分测试匆忙上线。

 

系统稳定性如果获得?除了大量充分的测试以外,在设计上必须有针对可靠性的设计,基本原则是在尽量保证各服务可靠的基础之上,通过一个健壮的体系结构来确保系统能够在硬件和软件出错的情况下依然平稳的运行。比如采用统一面向方面的框架,统一的异常处理、错误隔离、报警、容错和恢复机制,考虑系统的冗余度,尽可能地避免单点故障,尽可能地保证一个进程故障都不会引起系统的瘫痪,同时也允许系统对部分服务做升级和维护而不影响系统继续提供核心的服务等等。总之,系统可靠性是系统在给定的时间间隔及给定的环境条件下,按设计要求,成功地运行程序的概率。成功地运行不仅要保证系统能正确地运行,满足功能需求,还要求当系统出现意外故障时能够尽快恢复正常运行,数据不受破坏。

 

可扩展性,灵活性

   毋庸置疑,一个先进的系统设计具有很强的可扩展性和灵活性,因为现在的商业需求变化迅速,如果每次小小变化都导致大量系统改动的话,这样的体系结构无疑是失败的。所以架构师必须去主动迎接未来可能的变化,设计灵活的可扩展的架构。但具体系统的可扩展性做到哪个程度,以及和其他设计指标的权衡利弊如何取舍是个问题。

 

比如具体灵活到哪个程度的问题,是要实现对各个组件的无依赖性,是要实现对具体数据库(File/XML/SQL Server/Oracle/DB2)的无依赖性,还是要实现对具体操作系统(Windows, Linux, OS2, AIX)的无依赖性?灵活程度不一样,设计要求和设计方法不一样。

 

架构设计必须尽量封装可能的变化,例如在业务流程发生有限的变化时(比如每个业务模块本身的业务逻辑没有变的情况下),能够比较方便地修改系统程序模块或组件间的调用关系而实现新的需求;如果这种调用关系被设计成存储在配置库的数据字典里,则连程序代码都不用修改,只需修改数据字典里的模块或组件调用规则即可。这就是按需设计,不是过度设计。

 

设计指标的权衡利弊如何取舍的问题也很明显,比如降低依赖性常用的方法是封装变化(Wrapper),分层设计,但会带来系统的性能下降,尤其是大数据量的情况下;引入缓存机制吧又会增加系统复杂性,降低系统稳定性等。因为过于复杂的设计既造成系统不稳定的隐患,又影响性能。

 

这方面的讨论很多,个人认为可扩展性和灵活性设计是架构设计中非常重要的方面,但不能过度。稳定性第一,接下来是灵活性第二,还是性能第二灵活性第三的问题,要具体看需求的情况了。如果是一个实时大数据量大用户在线服务系统,应该在性能的前提下实现灵活;如果是一个其他的业务系统,灵活性优先于性能。具体情况要具体权衡利弊。

 

高性能

 

在进行性能设计时,首先要与客户充分地沟通,了解客户的性能需求,不管它是清晰的还是暗含的。不要等到项目后期才发现其实数据量是60万而不是10万。所以预先了解项目的性能指标,获取与性能相关的数据,从而预先评估架构的性能指标非常重要。

其他具体的性能设计方法可以参考我另外一篇文章:《架构设计之性能设计经验》

 

可用性,用户友好性

   设计以人为本,到头来还是给人用的。产品经理、客户和最终用户很注重界面,他们不知道你的系统有多么先进,只看界面是不是美观,界面友好,标准,操作流畅,有良好的用户体验(User experience)。界面做好了,客户满意,你就成功了一半;否则内部系统再先进,客户都会认为这个系统非常糟糕,用户体验非常不好。因此,可用性设计在这个Web2.0的时代尤其重要。

针对可用性的设计应该由架构师、用户体验设计师共同完成。可用性设计不等于界面设计。界面设计是静态的,而可用性设计是以用户为中心的交互设计(Interaction Design),更关注用户的行为和体验。交互设计需要研究角色模型,用户行为和上下文,数据整合和呈现方式等(Persona,goal,scenario)。一个成功的可用性设计/用户体验设计需要一个或多个跨学科的设计师,倾听和收集用户对系统使用的需求、体验或不满,并进行艺术化的设计。

例如一个失败的网站,内容繁杂,让客户无所适从;搜索了一个内容,出来一堆东西,用户想在这里面进一步搜索,却找不到这个功能。而良好的网站设计,用户操作非常流畅,流程很容易让用户理解。简洁明了,没有乱七八糟的东西干扰用户。因为后者仔细研究过用户交互模型,知道大多数用户的操作习惯和困难,而网站内容是根据清晰的流程设计的。数据的呈现方式也是经过整合的,不是所有数据都呈现给用户,而是呈现对用户有用的数据。

 

可维护性,可管理性

 

可维护性包括代码的可理解性,可测试性,可修改性和系统的可移植性。如果一个系统的可维护性从最初没有得到很好的重视,当系统面临重大的设计改动时,会发现几乎无法入手,最简单的方法是彻底推翻重写,于是造成大量的资源浪费。

 

具体来说,软件系统可维护性差的原因有:

1.       过于僵硬: 加入一个新性能,不仅仅意味着建造一个独立的模块,而且因为这个新性能会波及很多其他的模块,最好变成跨越几个模块的改动。

2.       过于脆弱: 对一个地方的修改,往往会导致看上去没什么关系的另外一个地方发生故障。尽管在修改之前,设计师会尽力预测可能的故障点,当是修改完成之前,系统的原始设计师们甚至都无法预测到可能会波及的地方。

3.       复用率低: 每当程序员发现一段代码、函数、模块所做的事情是可以在新的模块、或者新系统中使用的是,他们总是发现,这些已有的代码依赖于一堆其他的东西,以至于很难将它们分开。最好他们发现最好的办法就是不去“碰”这些已有的东西,而是重新写自己的代码。他们可能会使用源代码拷贝的办法,以最原始的复用方式,节省一些时间。

4.       黏度过高: 有的时候,一个改动可以以保存原始设计意图和原始设计框架的方式进行,也可以以破坏原始意图和框架的方式进行。一个系统设计,如果总是使得第二种办法比第一种办法容易,就叫黏度过高。

5.       系统过于复杂:系统过于复杂和庞大,结构不尽合理,设计文档缺乏或没有更新,系统年龄大时间久远。

 

可维护性设计应该采用灵活架构,采用复用的设计方法,尽量减少相互之间的依赖项,尽量采用成熟的工业应用级的产品和框架,采用代码审查机制等。传统软件工程用可理解性、可测试性和可修改性来衡量软件的可维护性,CASE软件工程则以考察可重用性来衡量可维护性。可维护性最直接的体现是良好的软件结构和完整正确的文档体系。维护应在文档级以上展开,应从软件结构出发,即以重构为核心。可重用性是可维护性的基本属性,最大限度地重用现存软件是软件维护方法学的重要思想原则。

 

运行可管理性,包括运维对系统软硬件各个部分的监控,以便于控制系统运行、监视系统状态、错误处理。为了实现上述目标,系统应该尽量采用参数化的可配置的设计,模块间通信应当尽可能简单,同时建立合理详尽的系统运行日志,系统通过自动审计运行日志和动态跟踪(dynamic tracing facility),了解系统运行状态、监控资源的使用和配置、进行有效的错误处理


你可能感兴趣的:(系统设计经验谈)