软件系统稳定性设计的秘密


软件系统稳定性设计的秘密

文章插图
 
何谓系统稳定性?
控制系统理论认为:系统受到某种干扰而偏离正常状态,当干扰消除,如果系统的扰动能逐渐收敛并最终恢复正常状态,则系统是稳定的,反之,系统偏离越来越大,则是不稳定的,所以,稳定性是系统抗干扰和返回平衡状态的能力 。
对于经典的传递函数的软件系统,一般我们讲的稳定指的是BIBO稳定,即有界输入有界输出稳定 。一个系统如果对任意有界输入得到有界输出,它就是BIBO稳定的 。一句话,稳定的系统对于各种输入需要有符合预期的输出 。
随着软件复杂性越来越高,稳定性的保障越来越难,随着服务规模越来越大,稳定性的重要性越来越高 。阿里行癫把稳定性比喻成木桶的底板,如果稳定性出问题,则滴水不留,所以,工程师在设计和开发软件的时候,要坚持底板思维 。
但我们的软件需求和计划很少考虑非功能部分,然而软件的结构和实现却有非常大的比重服务于此,这也许是软件项目计划经常延期的重要原因 。
如何保障稳定性?
虽然理论上没有绝对稳定的系统,但我们依然可以有所作为,使我们设计和开发的系统在生产环境接近稳定运行 。
从大的方面讲,稳定性保障,可以分成3个部分:
制度纪律
编码规范、代码提交门禁
Code Review
【软件系统稳定性设计的秘密】静态代码扫描,动态代码分析
Unit Test、压测
灰度发布、Rollback、应急预案
监控
复盘、故障树分析
思想之道
保持简单、降低复杂度
不(零)信任、面向失败设计
实践之术
冗余设计(数据、计算、带宽冗余)
快速恢复设计(无状态设计)
容错、灾备
熔断、隔离
限流
有损服务
错误重试策略,避免流量风暴
去关键路径、去中心化、避免单点故障
负载均衡(load balance)
避免惊群效应
看门狗设计
安全编码
制度纪律
通过制度去规范操作和行为,通过纪律去约束大家在框架内活动,被证明是保障稳定减少出错行之有效的方式 。
纪律是关键,只有持之以恒的遵守制度,才能避免方法和规定沦为空谈 。
但制度和纪律只是划出质量底线,只能解决大多数稳定性问题,难以发现一些隐匿的问题,需要配合思想之道和实践之术,才能持续改进软件质量,从而更全面的保障稳定性 。
思想之道
道是大的层面,它具有全局性的指导意义,我从众多的指导思想里,挑选最重要的两点:保持简单和不信任/面向失败设计,展开来讲 。
1. 保持简单
复杂是稳定性的天敌,保持简单即保持稳定 。单一职责,功能清晰就是践行保持简单 。
把简单的东西搞复杂很容易,而化繁为简则堪称化腐朽为神奇 。所以保持简单并不是低要求,它需要你透过表象洞悉事物本质,用最直接最土味的方式解决问题,做技术的同学有一个奇怪的癖好,喜欢把自己最近琢磨的东西用到项目中,不然总有锦衣夜行的感觉 。
我的建议是“学深用浅” 。引入复杂性,一方面要权衡收益,另一方面要警惕损伤,要理解项目开发很多时候是团队合作,任何复杂性的引入都会对合作者提出更高要求,严以律人是危险的,低门槛才是符合人性的 。
2. 不信任设计、面向失败设计
不信任设计又叫零信任设计,和面向失败的设计有相似之处,其本质都是防御性编程思想 。
不信任设计思想假设系统依赖的上下游都不可靠,假设周围都是坏人,假设攻击无处不在 。
网络服务需要对客户端请求参数做严格验证,不仅检查合法性,也要验证NaN 。游戏开发有一句名言:假设客户端的数据都是假的 。
进程内的函数调用大多时候很安全,会有可预期的结果,但如果跨进程调用(RPC)的可靠性则会低很多,有可能超时,有可能丢包,有可能失败,调用者必须意识并处理好各种异常情况,是重试?如果重试的话重试多少次?重试之间的间隔应该怎么确定?请求的上下文怎么保存和恢复?
我们要正确理解不信任设计的内涵,避免用力过猛,警惕借面向失败设计之名行无效编程之实,比如已经对客户端请求数据做了严格校验,在服务器处理过程中,重复检验,比如已经对接口入参判空,在内部调用过程中重复判断 。这会降低代码浓度,混入大量无效代码,损伤可读性和执行效率,本质上是违背“保持简单”原则的 。


推荐阅读