您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

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

时间:2020-07-13 15:01:52  来源:  作者:
软件系统稳定性设计的秘密

 

何谓系统稳定性?

控制系统理论认为:系统受到某种干扰而偏离正常状态,当干扰消除,如果系统的扰动能逐渐收敛并最终恢复正常状态,则系统是稳定的,反之,系统偏离越来越大,则是不稳定的,所以,稳定性是系统抗干扰和返回平衡状态的能力。

对于经典的传递函数的软件系统,一般我们讲的稳定指的是BIBO稳定,即有界输入有界输出稳定。一个系统如果对任意有界输入得到有界输出,它就是BIBO稳定的。一句话,稳定的系统对于各种输入需要有符合预期的输出。

随着软件复杂性越来越高,稳定性的保障越来越难,随着服务规模越来越大,稳定性的重要性越来越高。阿里行癫把稳定性比喻成木桶的底板,如果稳定性出问题,则滴水不留,所以,工程师在设计和开发软件的时候,要坚持底板思维。

但我们的软件需求和计划很少考虑非功能部分,然而软件的结构和实现却有非常大的比重服务于此,这也许是软件项目计划经常延期的重要原因。

如何保障稳定性?

虽然理论上没有绝对稳定的系统,但我们依然可以有所作为,使我们设计和开发的系统在生产环境接近稳定运行。

从大的方面讲,稳定性保障,可以分成3个部分:

制度纪律

编码规范、代码提交门禁

Code Review

静态代码扫描,动态代码分析

Unit Test、压测

灰度发布、Rollback、应急预案

监控

复盘、故障树分析

思想之道

保持简单、降低复杂度

不(零)信任、面向失败设计

实践之术

冗余设计(数据、计算、带宽冗余)

快速恢复设计(无状态设计)

容错、灾备

熔断、隔离

限流

有损服务

错误重试策略,避免流量风暴

去关键路径、去中心化、避免单点故障

负载均衡(load balance)

避免惊群效应

看门狗设计

安全编码

制度纪律

通过制度去规范操作和行为,通过纪律去约束大家在框架内活动,被证明是保障稳定减少出错行之有效的方式。

纪律是关键,只有持之以恒的遵守制度,才能避免方法和规定沦为空谈。

但制度和纪律只是划出质量底线,只能解决大多数稳定性问题,难以发现一些隐匿的问题,需要配合思想之道和实践之术,才能持续改进软件质量,从而更全面的保障稳定性。

思想之道

道是大的层面,它具有全局性的指导意义,我从众多的指导思想里,挑选最重要的两点:保持简单和不信任/面向失败设计,展开来讲。

1. 保持简单

复杂是稳定性的天敌,保持简单即保持稳定。单一职责,功能清晰就是践行保持简单。

把简单的东西搞复杂很容易,而化繁为简则堪称化腐朽为神奇。所以保持简单并不是低要求,它需要你透过表象洞悉事物本质,用最直接最土味的方式解决问题,做技术的同学有一个奇怪的癖好,喜欢把自己最近琢磨的东西用到项目中,不然总有锦衣夜行的感觉。

我的建议是“学深用浅”。引入复杂性,一方面要权衡收益,另一方面要警惕损伤,要理解项目开发很多时候是团队合作,任何复杂性的引入都会对合作者提出更高要求,严以律人是危险的,低门槛才是符合人性的。

2. 不信任设计、面向失败设计

不信任设计又叫零信任设计,和面向失败的设计有相似之处,其本质都是防御性编程思想。

不信任设计思想假设系统依赖的上下游都不可靠,假设周围都是坏人,假设攻击无处不在。

网络服务需要对客户端请求参数做严格验证,不仅检查合法性,也要验证NaN。游戏开发有一句名言:假设客户端的数据都是假的。

进程内的函数调用大多时候很安全,会有可预期的结果,但如果跨进程调用(RPC)的可靠性则会低很多,有可能超时,有可能丢包,有可能失败,调用者必须意识并处理好各种异常情况,是重试?如果重试的话重试多少次?重试之间的间隔应该怎么确定?请求的上下文怎么保存和恢复?

我们要正确理解不信任设计的内涵,避免用力过猛,警惕借面向失败设计之名行无效编程之实,比如已经对客户端请求数据做了严格校验,在服务器处理过程中,重复检验,比如已经对接口入参判空,在内部调用过程中重复判断。这会降低代码浓度,混入大量无效代码,损伤可读性和执行效率,本质上是违背“保持简单”原则的。

实践之术

术是局部层面,它是实践经验,牵扯方方面面,难以尽数枚举。

如果以文章写作类比软件开发,谋篇布局相当于设计层面,设计层面要致广远,遣词造句相当于实现层面,实现层面要尽精微。

所谓千里之堤溃于蚁穴,防微杜渐尤其重要。

1. 冗余设计

冗余设计指留出安全余量,冗余包括数据冗余、计算冗余、带宽冗余。

数据冗余指一份数据多个副本,一主多备。

计算冗余,比如服务实例的QPS极限是10K,但实际上我们会按5K跑,这样,即使出现流量超速增长,我们依然有反应时间。

2. 快速恢复设计(无状态设计)

互联网服务很多都是无状态设计,服务实例只是逻辑的盒子,后面跟着分布式一致性数据库,这样能极大简化设计,即使实例挂了,客户可以很容易迁移到其他服务实例执行,而有状态设计则要复杂难搞得多。

3. 容错、灾备

容错指我们的系统要有一定的错误容忍能力,这意味错误发生,我们要能查错、检错、避错、甚至改错,只要可能,我们就要吞咽错误。

灾备这个大家耳熟能详,主从设计,异地备灾,目标都是为了应对各种极限情况。

4. 熔断、隔离

熔断机制不止软件设计独有,股市也有,我甚至怀疑软件的熔断机制是从股市学来的。

隔离本质上就是说如果故障发生了,如果故障发生,而又不能吞咽,那也应该隔离避免错误传播扩散,千方百计缩小影响范围,相当于感染新冠要被隔离起来。容器化等技术为隔离提供良好能力支撑。

5. 限流

系统设计要做好资源耗尽、资源不够用的情况,如果服务请求超过服务能力,那就应该限流,这应该作为一种配置,或者自动执行的策略。

这个跟地铁限流差不多,处理不了,那就排队。

6. 有损服务

有损服务我印象中最先是腾讯提出来的,指如果出现服务能力不够,不能为所有客户提供服务的异常情况,那系统应该确保已有客户的服务请求得到满足,而不能让新增客户拉已有客户一起死。

有损的意义就是有损失,有损伤的意思,已有客户不受干扰,新增客户沦为代价,这不也是没办法的办法嘛。

7. 错误重试策略,避免流量风暴

如果设计一个ToC服务,在客户大规模断连的情况下,客户会重连,重连失败再连,如果重连尝试的频率不控制好,正常客户端重连有可能演变成对服务器的大规模攻击,打爆一台服务器,又去灭另一台,这太吓人了。

可以参考kernel TCP的重连策略,有最大尝试次数,而且重试间隔是逐渐拉大的。

8. 去关键路径、去中心化、避免单点故障

企业不要关键先生,关键先生会成为瓶颈,软件也不能把宝压到一个地方,去中心化去集中式,没什么难理解的。

9. 负载均衡

load balance其实就是分担压力,LB要避免倾斜,有多种LB算法,比如RR,比如一致性hash,各有利弊,有兴趣可以研究下。

LB不仅限于服务,进程内的多线程可能也会需要考虑这个问题。

10. 避免惊群效应

一只鸟被惊扰起飞,然后一群鸟全部受惊起飞,画面感是不是很强?有点破窗效应的味道,可以参考Nginx对惊群效应的处理策略。

11. 看门狗和心跳机制

可以参考kernel的watch dog,其实就是看护机制,检测错误并努力掰过来。

12. 安全编码

安全编码是一个职业程序员的基本要求,安全编码规则很多,很细节的一些规矩。这个可能跟语言相关,如果是C++相关的可以参考:C++的门门道道

C相关的规则要少一些,我顺手列举一些。

  • 比如要注意初始化。
  • 比如全局变量不要有构造顺序的依赖。
  • 比如慎用强转,强转等于接管了编译帮你做的类型检查。
  • 比如理解线程安全函数,理解可重入的概念,理解信号机制。
  • 比如要避免死锁,理解ABBA锁理解自死锁。
  • 比如要谨防资源泄漏。
  • 比如处理好内存分配失败的情况,理解野/悬垂指针。
  • 比如要处理好边界,防止越界,溢出。
  • 比如内存拷贝要避免内存重叠,理解memmove的用途。
  • 比如理解递归的低效和栈的大小限制,避免爆栈。
  • 比如建议使用STD安全版本函数(_s+n)版本。
  • 比如了解unsigned < 0导致死循环的情况。
  • 比如了解浮点数跟0比较的问题。
  • 比如理解整型数据溢出和反转。
  • 比如不要返回临时变量的引用或者指针,理解栈帧动态伸缩的原理。
  • 比如理解做好把关检查的必要性,包括系统把关和模块把关。

小结

最后来读段经典:《系统化思维导论》一书中引用冯诺依曼的话写道:如果你观察一些自动装置,不论它们是人类设计的还是自然界本来就存在的,你通常会发现,它们的结构很大程度上受控于它们可能失效的方式,以及针对失效所采取的防御性措施(多少有些效果),说它们能预防失效有点夸张,它们不是能预防失效的,只是被设计成试图达到这种状态,这样至少大部分失效都不会是毁灭性的。所以,根本谈不上消除失效,或完全消除失效带来的影响。我们能尝试的只是设计一种自动装置,在大部分失效发生时仍能继续工作,这种装置减轻了失效的后果,而不是治愈失效,大部分人造的和自然界存在的自动装置,其内部原理都是如此。

本文转载自微信公众号「 码砖杂役」



Tags:软件系统   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
> An on-call developer&#39;s worst nightmare (red indicates errors) 深入探讨如何通过缓存,作业化,队列分离等解决平台的扩展性,稳定性和性能问题。一天处理超过$ 20,000,0...【详细内容】
2020-07-30  Tags: 软件系统  点击:(50)  评论:(0)  加入收藏
何谓系统稳定性?控制系统理论认为:系统受到某种干扰而偏离正常状态,当干扰消除,如果系统的扰动能逐渐收敛并最终恢复正常状态,则系统是稳定的,反之,系统偏离越来越大,则是不稳定的...【详细内容】
2020-07-13  Tags: 软件系统  点击:(46)  评论:(0)  加入收藏
▌简易百科推荐
为了构建高并发、高可用的系统架构,压测、容量预估必不可少,在发现系统瓶颈后,需要有针对性地扩容、优化。结合楼主的经验和知识,本文做一个简单的总结,欢迎探讨。1、QPS保障目标...【详细内容】
2021-12-27  大数据架构师    Tags:架构   点击:(5)  评论:(0)  加入收藏
前言 单片机开发中,我们往往首先接触裸机系统,然后到RTOS,那么它们的软件架构是什么?这是我们开发人员必须认真考虑的问题。在实际项目中,首先选择软件架构是非常重要的,接下来我...【详细内容】
2021-12-23  正点原子原子哥    Tags:架构   点击:(7)  评论:(0)  加入收藏
现有数据架构难以支撑现代化应用的实现。 随着云计算产业的快速崛起,带动着各行各业开始自己的基于云的业务创新和信息架构现代化,云计算的可靠性、灵活性、按需计费的高性价...【详细内容】
2021-12-22    CSDN  Tags:数据架构   点击:(10)  评论:(0)  加入收藏
▶ 企业级项目结构封装释义 如果你刚毕业,作为Java新手程序员进入一家企业,拿到代码之后,你有什么感觉呢?如果你没有听过多模块、分布式这类的概念,那么多半会傻眼。为什么一个项...【详细内容】
2021-12-20  蜗牛学苑    Tags:微服务   点击:(9)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  青锋爱编程    Tags:后台架构   点击:(21)  评论:(0)  加入收藏
在了解连接池之前,我们需要对长、短链接建立初步认识。我们都知道,网络通信大部分都是基于TCP/IP协议,数据传输之前,双方通过“三次握手”建立连接,当数据传输完成之后,又通过“四次挥手”释放连接,以下是“三次握手”与“四...【详细内容】
2021-12-14  架构即人生    Tags:连接池   点击:(17)  评论:(0)  加入收藏
随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商...【详细内容】
2021-12-08  架构驿站    Tags:分布式系统   点击:(23)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  大数据架构师    Tags:Netty   点击:(17)  评论:(0)  加入收藏
前面谈过很多关于数字化转型,云原生,微服务方面的文章。虽然自己一直做大集团的SOA集成平台咨询规划和建设项目,但是当前传统企业数字化转型,国产化和自主可控,云原生,微服务是不...【详细内容】
2021-12-06  人月聊IT    Tags:架构   点击:(23)  评论:(0)  加入收藏
微服务看似是完美的解决方案。从理论上来说,微服务提高了开发速度,而且还可以单独扩展应用的某个部分。但实际上,微服务带有一定的隐形成本。我认为,没有亲自动手构建微服务的经历,就无法真正了解其复杂性。...【详细内容】
2021-11-26  GreekDataGuy  CSDN  Tags:单体应用   点击:(35)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条