企业架构设计原则之品质均衡性(一)
一、高可用
高可用要求系统在满足服务水平协议的范围内,保持随时可以被访问被使用。任何系统的稳定运行,都依赖或多或少的其他资源,譬如数据库服务、缓存服务、网络、存储等,甚至系统自身的处理逻辑是否健壮也会影响能够正常提供服务。
影响系统可用性的因素主要有以下几类。
(1)基础设施可用性
如上文所述,IT 系统的正常运行离不开基础设施的支持。就好比一台有线电视要想正常工作,也需要电力的供应,同时有线网络也必须流畅。IT 系统对基础设施的依赖一般大体可分为计算资源、存储资源、网络资源、电力资源等。计算资源侧重承载系统的服务器(可以是硬件服务器,也可以是虚拟机)。服务器自身也会因为设备故障、老化、温度过高等原因而宕机。而存储资源一般是指硬盘以及网络存储设备等,存储设备的空间也是有限的,一旦空间耗尽而没有及时扩容,也会引发宕机。网络波动性更加不可控。
为了提升基础设施的可用性,行业通常做法主要有以下几点。其一,通过冗余的方式,让多个同类似的设备同时提供服务,一旦其中一部分不可用,剩余可用部分可以继续提供服务,从而保证服务的连续性。其二,通过完善的全方位监控,及时预警,及时介入,尽可能提前化解。其三,定期巡检和保养,确保设施处于健康的工作状态。
(2)依赖其他服务的可用性
系统的正常运行,并能够提供期望的功能,很多时候还离不开其他服务。这些服务一部分是企业自身建设能力之外的,因此不得不依赖,譬如大家最显而易见的支付功能,必须依赖第三方支付渠道或银行渠道来完成。另外一部分服务,企业自身具备建设能力,但是处于易维护、可复用等架构理念,往往会拆分为多个更细粒度的服务,并通过服务之间的调用来编排成完整的业务流程。那么,服务拆分的力度越细微,理论上一个功能所依赖的服务数量就越多,可用性也会随之而下降。
假设系统 A 依赖 B、C、D、E 四个服务。而 B、C、D、E 四个服务的可用性指标是 99%,那么 A 的可用性约等于 99%*99%*99%*99%=96.10%。因此,无论是服务提供者,还是服务消费者,意识到任何服务都无法做到 100%的可用性,并基于此推行相关的补充加固机制,对于重要的功能和流程,是必不可少的步骤。
3) 系统自身稳健性
除了基础设施和依赖服务会影响系统可用性,系统自身的健壮性也是至关重要的。譬如对于循环的退出条件控制是否考虑周全,对于大对象是否及时释放,当下载大批量数据的时候,有没有做分页处理……
系统自身稳健性这一块,还有一个点是大部分技术同学比较容易忽视,但是效果却又比较严重的,那就是对于超时的控制。按照最佳实践,一般涉及跨网络调用其他资源都需要设置合理的超时时间,而且一般建议超时时间控制在 15 秒以内,最长不超过 60 秒。而现实中,因为开发同学接口性能、应该异步处理的场景被设计为同步处理导致设计不合理等诸多问题,往往会发现 15 秒的超时时间经常不够用,处于简单省事,很多技术同学会很大方的直接加大超时时间,甚至达到几个小时。这在正常情况下不会出现什么问题,可是一旦引用的服务出现问题,导致大量请求被阻塞,因为超时时间过程,请求不会自动断开,就可能会引起系统的崩溃。
一名优秀的技术人员,往往不仅仅盯着功能是否实现,还会考虑许多异常情况,只有在充分考虑了这些异常情况并做了相应的化解,系统才具备反脆弱性,才能够稳坐钓鱼台。
那么,一个系统可用性该如何衡量呢?在 IT 圈有一个专业名词叫 SLA,即服务等级协议,用来标识服务的可用性级别。SLA 的计算公式是:服务可正常工作的时间/全部时间,默认时间周期是一年。服务可正常工作的时间=全年工作时间总和-(一年内出现故障导致不可用的时间+维护时间)。举一个例子,假设有一个系统 A,在过去的一年中,因为种种原因出现 3 次事故,每次事故持续时间为 4 小时,此外,每个月会进行一次版本更新,其间服务会停止 1 小时,那么系统 A 的可用性 SLA=(365*24-3*4-12*1)/365*24=99.73%。
系统的可用性 SLA 是越高越好吗?自然也不是,取决于系统对服务连续性的要求级别。假设系统主要客户是内部员工,而员工一般只在工作时间使用,其他时间基本不使用,那么该系统的可用性 SLA 就没必要维持那么高。譬如每次更新的时间可以更长,这样准备更充分,同时也不需要做在线不停机更新,毕竟没有人使用,停机也不会有损失,反而会让方案变得简单低成本,何乐而不为呢?其次,要维持高可用性在较高的水平,对于基础设施的会提出更高的要求,包括更多的冗余,这也是直接的经济成本。
二、高可靠
说到可靠,相信所有人都不会陌生,无论是学校老师教导,还是家长提醒,抑或是职场培训,都或多或少会谈到一个话题,那就是如何做一个可靠的人,如何让经手的每一件事情变得确切可靠。可靠是一项优秀的人格品质。
回到系统建设上,也不例外。
试想想过去很多的财务工作,为了保证财务计算的准确无误,往往同一份工作需要安排若干名人员进行处理,有的负责记录,有的负责计算,有的负责核对,有的负责最终的审核,还有的负责总账的审查。如果细致地分工,后一个步骤都需要对前一个步骤进行检查确认,甚至需要增加额外的岗位利用其他的手段进行验算,目的就是为了保障所有账目的可靠。来到数字化时代,很多的财务工作都被系统所取代了,但是对于账目的准确性却一点也没放松。如果你的系统不够可靠,计算的数据大部分都是对的,但是存在少数情况下会出现偏差,作为财务人员,你敢放心地使用这些系统吗?你会觉得系统是在简化帮助他们吗?
所以,对于一个可靠的系统,他的计算结果一定是符合预期的,一定是正确的,使用者对他的处理结果是有充分的信任的。
但是,如何才能设计高可靠的系统呢?
技术方案自身的严谨性、周全性自然是必需的,但除此之外,还需要考虑多方面的检查、复核,只有复核无误之后,才能进行保存。还记得每次数学考试的时候,当题目做完之后,交卷之前,都会要求自己对每一道题目换一个计算方法重新计算一遍,只有当两个方法的计算结果保持一致,才认为正确。数学上这个步骤好像叫“验算”过程。同理,在系统建设时,越是重要的功能和步骤,验算过程也是严格要求。
三、稳定性
与可用性和可靠性看起来似乎差不多,但是侧重点略有不同。可用性强调的是系统可以持续提供服务,而可靠性要求系统提供的服务结果是正确的,即正确的输入一定给出正确的处理结果。稳定性则强调系统提供的服务,在处理速度上保持相对一致,在有限的范围内进行波动。如果用数学专业化说来说,稳定性的一个指标是服务的响应时间的方差尽可能较小。假设存在两个等价服务,构建相同的配置和相同的参数分别请求服务 A 和服务 B,假设服务 A 的响应时间数组是[100ms,300ms,280ms,50ms,80ms],而服务 A 的响应时间数组是[220ms,240ms,190ms,180ms,205ms]。单纯从算术平均数来看,服务 A 的平均响应时间是 162ms,而服务 B 的平均响应时间是 207ms,似乎服务 A 的服务水平优于服务 B。但是二者从标准偏差来看,则明显服务 B(标准偏差等于 24)优于服务 A(标准偏差等于 118),说明服务 B 的稳定性好过服务 A。因此,在平均响应时间均可接收的范围内,优先选择服务 B 可能是更加明智的选择。
四、高性能
作为 IT 从业人员,高性能的具体内涵或许没有来得及深究,但是肯定对这个词如雷贯耳,不会陌生。那么什么是性能呢?我们将范围框定在 IT 领域,那么性能是指完成一项工作所需要消耗的时间和软硬件资源。高性能则表示处理某一项工作所耗费的时间少,或者所占用的资源更低,或者两者兼而有之。
那么如何衡量一个系统的性能高低呢?
首先,看处理某项工作的物理资源消耗。最典型的物理资源有应用服务器的 CUP 和内存,数据库服务器的 CPU 和内存。假设客户使用某一项功能,监控立马发现对应服务这项请求的应用服务器 CPU 利用率提升了 30%,内存使用率提升了 40%;而类似场景下另外一个功能,多个客户使用,CPU 毫无波动,内存占用也是非常稳定,如果要评价这两个功能的性能,想必答案不言自喻。系统对资源的占用越大,意味着相同硬件配置下,所能服务的客户越少,也就意味着需要投入更多的资源和成本才能满足对客户的服务要求。业界常用 TPS/QPS/IOPS 等指标来描述一定配置下系统的处理能力。TPS/QPS/IOPS 越高,意味着性能越是强劲;反之,则需要考虑是否还有优化提升的空间。
其次,看响应时间。在资源占用相差无几的情况下,谁的响应时间越短,表示客户体验越好,同时也可能表示系统能处理更多的请求,也就是 TPS/QPS 可能更高。
那么常见的提高系统性能的手段有哪些呢?
其一,选择性能卓越的框架或类库,是高性能的基石。应用程序开发很多时候是基于现有的框架之上进行业务构建,因此应用程序的性能很大程度取决了框架的性能。譬如在网络通信领域,目前广为流传 Netty 框架,就是因为其出色的性能而被大家所青睐,从而被广泛集成在各个系统或框架之内,成为网络通信的“内核”而存在。
其二,遵循常见的设计原则和最佳实践,可以提升系统的处理能力。譬如利用异步原则实现业务解耦,可以缩短响应时间;利用快速失败原则,将准入判断放在入口处,实现最快失败,节约没有必要的业务处理;对于经常访问的数据进行缓存,可以大大提升处理性能;将零散的操作进行归类合并,形成批处理,对于数据库的访问操作,这个方法非常管用;
其三,控制锁粒度,实现更高的并发和并行。锁的粒度越大,系统能同时处理的请求就越少,性能自然也就越低下。
其四,减少跨网络的数据传输和请求。网络请求历来都是非常耗时的动作,因此,如果可以减少网络请求的次数,或者减少往来数据体积,可以提升系统的处理性能。
其五,利用池化技术,减少频繁的系统资源申请和分配。常见的池化技术有连接池、线程池、内存池等。这些池都有一个共同特点,即对应的资源是有限资源,且分配较为消费系统 CPU 或内存,同时会频繁地使用。因此采用池化技术,可以避免频繁的申请和释放所带来的额外消耗。
最后,要想提出高性能的 IT 系统,对技术的掌握必须从使用层面深入到原理层面,捋清楚实现原理,才能更好地加以利用,否则只是道听途说,缺乏真知灼见,容易陷入人云亦云的混沌状态。
五、易测试
相比于稳定性和可靠性,易测试在架构品质考量上算是二等公民了。说他是二等公民主要是两点依据,其一,很少会有架构师第一时间考虑方案的易测试性,其二,当易测试性不那么容易实现,或者在大的约束环境下,需要做出取舍的时候,易测试性往往又逆袭为第一梯队被考虑放弃的对象。
那么,什么是易测试性呢?所谓易测试性是指设计的系统在开发完之后并转交给质量保障团队时,质量保障团队能够很方便地对系统功能和品质进行验证。可能很多从业者会生出一丝丝疑惑,质量保障团队对系统进行测试和验证很难吗?不就是直接通过界面输入数据,点击按钮,再检查执行结果跟预期是否一致吗?持有这一类观点的从业人员不在少数,尤其是对于刚入行的开发人员更是如此。其实持有这一类观点的从业人员,可能还有一个特征,那就是开发好的功能自己几乎不测试,只要编译能通过就直接转手给了质量保障团队,至于功能不能正常运行他们自己心底是没有底气的。
那么为什么需要考虑易测试性呢?
首先,并不是所有的功能都有界面可以操作。很多应用开发人员,习惯了具备界面的应用系统,却忽略了一个重要问题,那就是很多系统可能并不具备可视化界面,譬如定时任务,又比如 API,又比如需要加密/解密的场景。当产品本身没有界面时,对于开发人员可能问题不大,但是对于质量保障人员却无疑增加工作难度,毕竟很多质量保障人员并非开发出身,需要他们写代码完成测试工作不是不行,只是比较困难。
其次,涉及与外部系统交互的场景,质量保障人员难以模拟。譬如系统依赖第三方的接口(譬如银行账户充值),而这些接口因为敏感性或权限隔离或其他原因,无法直接调用,只有上到生产环境才能集成。对于质量保障人员来说,这个功能又是重要流程上的一环,必须验证,而且必须重复多次以充分验证。一个比较好的办法就是参考对方的接口文档,写一个模拟程序进行替代。类似的场景还有对其他系统的回调接口进行测试。
其次,对于系统处理的中间数据,质量保障团队无法直接获取。譬如对于性能要求极高的场景,我们需要统计系统的处理效率,包括时间消耗、CPU 增长情况、多线程的利用情况等等,这部分数据既无法通过界面直接输入,也没有从数据库中萃取,只能通过开发人员在开发过程中进行埋点打印输出,再保存到数据库、日志甚至监控系统,这样质量保障团队才能够进行验证。
其次,对于异常场景的控制,需要借助技术手段。无论是方案设计,还是技术实现,对于异常场景都是必须考虑并处理的。然而,却存在一个不友好的问题,即异常场景并非那么容易复现。譬如网络抖动带来的远程调用超时问题,又比如请求的第三方接口不稳定问题。这些场景正常情况下几乎难以遇到,如果指望正常的手段进行验证,不知道要等到猴年马月才能跑完所有测试用例。这时候就需要人为地控制。譬如验证超时场景时,可以通过控制台输入一个超时时间,系统接收到这个超时信号,主动睡眠,通过这种方式模拟第三方的超时。
最后,提升部分场景下的测试效率。假设系统有一个功能是验证同时段多人上传系统的稳定性。虽然这个功能通过人为的方式可以验证一部分,如组织多名质量保障人员同时发起上传操作,但是协调成本较高,如果要重复较长时间,则更加难以持续。如果开发一个读取本都目录再多线程并发上传的功能,则可以大大简化质量保障人员的验证工作,而且还可以重复利用。
总之,易测试性不是一个可选项,对于特殊场景下,他至关重要,除非你不想验证这些场景下系统的表现。
六、易运维
易运维是指系统上线后,运维团队能够很轻松地对系统进行维护,及时发现问题,快速修复。
易运维并不是一件顺其自然的事情,相反,他需要设计之初考虑到运维功能的强大压力以及运维周期的长期性并进行友好的设计。一般的软件系统生命周期分布中,开发只占三成,剩余七成都是运维阶段,因为运维周期不仅很长,成本也是很高昂的。我们经常会遇到这样的场景,客户打电话给客户经理,上报说系统某个功能无法正常运行,已经耽误他们正常办公了,希望尽快解决一下。客户经理收到后一方面安抚好客户,另一方面转告给运维团队,并强调客户在线等,希望火速解决,否则客户投诉事小,直接渠道竞争对手那里就难以挽回了。收到故障上报的运维团队于真真催促声和紧张的气氛下,原本清晰的头脑瞬间成了浆糊,一团乱麻。硬着头皮,开始远程登录到服务器,查看有没有错误日志,要么直接查询数据库,看看数据状态……然后再反馈给运营,让联系客户再重新操作一遍,以便排查,虽然客户经理满心不情愿但是为了尽快灭火也不得不厚着脸皮联系客户,近乎哀求的口吻请求客户重新操作一遍……运维团队一遍看日志,一遍对照代码,猜测可能出问题的故障点,如此经过漫长的 2~3 个小时后,运维团队终于找到了原因,可以通过修复数据来解决,但是生产数据存储的是加密的数据,需要先把生产数据导出到本地,再编写代码加密,再通过走工单的方式执行到生产环境,于是又经过了 2 个小时,问题暂时告一段落。客户气鼓鼓地抱怨一个下午啥都没干成,运营满心不悦,埋怨运维团队定位和解决问题过于拖沓冗长,并且晚于客户发现问题,非常被动,且显得不够专业。运维团队精疲力尽,但是又无可奈何,只能心里问候对应功能的开发。
熟悉的场景,熟悉的结果。
如果你不希望你的系统在运维阶段到处鸡飞狗跳,那么请为你的功能设计足够友好的运维特性。
首先,日志的输出尽可能齐全,关键信息一定要输出。试想想假设你是运维团队,客户说遇到了问题,但是系统日志却寥寥无几,你的内心是不是很无助?其次,有了日志,乍一看满心欢喜,仔细分析才发现关键业务信息都没有,有日志跟没日志好不到哪里去,你又该作何感想?
其次,预备好故障检测、处理的工具或功能。一旦面临问题,在压力下,人很容易变得手足无措,因为建议在设计之初,就开发好可能出现的问题,以及对应的检测功能,并修复功能。利用这些预设的工具,可以很边界且准确的查找出故障点,并及时修复,减少事故的影响程度。
最后,把运维人员也当作是系统的用户之一来对待,为他们提供便于开展工作的功能,这样的付出总会有回报的。
版权声明: 本文为 InfoQ 作者【凌晞】的原创文章。
原文链接:【http://xie.infoq.cn/article/dafbe686b484f1102322d273e】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论