1.敬畏风险
软件世界既是虚拟世界,同时也是客观世界的一部分,进行高可靠系统设计的第一步需要团队成员达成共识,尊重客观世界的运行规律,这些客观规律包含:
承认世界的不确定性,小概率可能造成大影响,风险无处不在,这就是我们经常说的黑天鹅事件。突发事件虽无法避免的,但我们应尽可能避免问题的发生,在设计时Design for failure! 尽可能考虑可能出现的问题,比如网络专线抖动,机房故障,流量过载情况等等。当故障发生时,及时回溯和总结,避免再次发生。
识别连续性假设陷阱,归纳法是有缺陷的,归纳法成立的前提是认为未来和过去是一样的,过去的发展规律一定适合于现在,但很可能不是这样。
成为世界是不完美的,同样也没有完美的设计,提供有损服务也是一种选择,他可能更能提高系统的可用性。
敬畏风险、具有风险识别意识,海南法则说每一起严重事故的背后,必然有29次轻微事故和300起未遂先兆以及1000起事故隐患。
不心存侥幸心理,如果事情有变坏的可能,不管这种可能性有多小,它总会发生。
了解客观规律,对我们构建高可靠系统有何知道意义呢?构建高可靠系统的本质是学会降低不确定性带来的风险,包括识别风险和应对风险2方面。
2.什么是可靠性
可靠性,从字面意义上理解就是可用、靠谱。
在生活中,如果说一个人可靠,是说这个人能够把事情办成,能够达到我们的预期,所以可靠性是和目标挂沟的。
同样,在计算机领域,我们说可靠也需要和目标挂钩,软件设计有几个通用标准:首先是程序正确无bug,为此我们需要提前识别可能存在的边界、安全问题,保证程序具有更好的健壮性。其次,可用是计算机软件(系统)可靠的第二个目标,通常业界用4个9,5个9来衡量,表达公式为 系统可用性 = MTTF / (MTTF + MTTR),MTTF是系统平均无故障运行时间,MTTR是系统平均修复时间。为此我们需要竭尽所能提高系统无故障运行时间,降低故障恢复时间。为了保证系统可用性,一般需要一定的容灾策略。再次,可靠性通常还与性能关系紧密,对交互系统(网站)来说可靠性意味着响应快,低延迟,而对离线计算系统可靠性意味着高吞吐量,在算法领域,可靠性意味着更低的空间消耗,更小的执行复杂度。最后,针对数据系统(不单指数据库,业务数据也是数据,所以提供业务数据的系统也是数据系统),可靠性意味着需要提供事务,在某些情况下,可靠性与事务是同一个名词。
事务,即一致性,通常用ACID来描述事务,即原子性、一致性、隔离性和持久性。原子性是说一个事务要不什么都不做,要做都做完,所有操作不可分割,是一个整体。隔离性讲的是在并发场景,读写事务是相互隔离的,互相感知不到对方的存在(这里不深入隔离级别领域)。持久性讲的是事务操作完毕持久生效,这样一个事务才算真正生效了。最后说下一致性,笔者认为前面讲的原子性、隔离性、持久性是用来考核一件事是否满足事务,而一致性是目标,原子性讲的是相互关联的数据前后一致,隔离性考虑的是在并发场景,每个事务看到的数据前后一致。
总结:可靠性意味着可用、靠谱,它与目标挂钩;在计算机领域可靠性衡量标准是程序正确、系统可用、性能好(低延迟、高吞吐,或者更低的资源消耗、更小的时间复杂度),在某些情况下它代表事务(即需提供原子性、一致性、隔离性和持久性)。
3.可靠性加固
了解了风险无处不在,小概率可能导致大问题,且知道软件可靠性的衡量标准,那么我们来缕一缕软件开发运维中可能遇到的风险以及如何应对。
3.1.正确性
程序是人编写的,是人编写的程序就意味着它可能出错,认识到这个风险,保证软件的正确性是第一要务,通常做法是制定一套严格的研发规范与流程,包括:
• 制定严格的研发流程 编码流程包含编码->自测->code review ->提测->测试(测试流水线,alpha测试,beta测试)->备机发布、产品验收->上线发布->日常运维->问题定位等步骤。
• 制定文档规范并进行评审,包含规范的需求文档和技术设计文档,需求文档除了业务本身一般需考虑权限问题和安全风险,设计文档除了技术方案本身,需提前识别风险点,比如是否需做异步化、部署方案怎样是否存在单点故障,是否需要做隔离、限流保护等等。
• 制定编码规范,并针对需求或项目组织code review:编码规范是为了统一思想,保证程序正确、可维护,所以需要严格按照编码规范要求编程。
• 制定严格的测试流程:研发自测、自动化对比测试、Beta测试保证软件功能正确,高可靠运行。
• 制定严格的发布流程:Pipeline、设置静默期、备机发布、分批发布、灰度发布等等。
以上是软件研发到发布过程中严格的流程与规范。在日常运维中也需要这样一套规范来应对风险,这些流程包含:
• 日常建设:核心业务指标监控、全链路压测、限流预案、降级预案、熔断机制、弹性伸缩方案。
• 事前预警诊断:核心指标预警、核心链路分级预警、排查问题SOP自动化。
• 故障演练:比如双十一淘宝京东等大厂一般都会进行一定的故障演练,保证系统抗风险能力平稳运行。
• 事故处理:制定止损手册和排查修复宝典、每周安排值班,实时监控告警、系统有一定容灾策略,要求能够实时发现问题,故障发生时系统可以更有弹性,有一定容灾策略。制定线上应急流程:快速定位故障并快速恢复。
• 事后总结:根因分析(5WHY分析法)、事故定级与损失核算、完善研发流程与系统架构,不再重复已发生的问题。
3.2.可用性设计
可用性通常用n个9进行衡量。为了保证n各9,攻城狮(相比程序猿,可用性可能更多是工程问题,所以这里讲攻城狮可能更贴切)用尽全力、使出了浑身解数。接口的可用性公式为:可用性 = 正确返回的数量 / 接口请求的总数。可用性可以理解为在故障情况下的存活能力。对服务端来说,可用性意味着接口(或服务)正常提供服务,正确返回,为了保障可用性,要求系统有一定的容错机制,下面将一一阐述。
【冗余术】
副本即冗余,是为了防范单点故障导致服务不可用的风险**。是设计一套可靠性系统优先能够考虑的。**这些思想包含:
• 复制:复制即副本、备份,也有叫异地容灾,双活,多活等用语,都是一个概念,是解决单点问题的常见思想,对于个人博客,可用性要求并不高,可能一台服务器提供服务就够了,可是对于大型网站来说,单点是绝对不允许的。复制按照地理空间维度可划分有:同机房互备、同城互备(Amazon叫AZ)、同区域互备、跨区域互备甚至跨洲互备。针对服务层,需要做到N+1,存储层一主多从,在该架构下下,需要考虑主节点宕机时重现选举主节点问题,通常需要进行一轮或多伦投票选举过程。
• EC:在存储系统使用较多,也是一种容错技术,解决副本方案空间利用率不足的问题,大致做法是将数据分成N块,块分为数据库和校验块,允许在丢失2-3块数据时通过其他数据库或校验快进行恢复。
【分区】
数据规模化后可能出现系统负载和系统性能急剧下降问题。通常会对数据按照业务进行合理分区(比如分库分表),这样做好处是:1.可以改善系统性能;2.可以改善局部影响全局的情况出现,也是可靠性加固的常见手段。
【防雪崩术】
在极端环境下(比如大促)系统可能需要面临严峻的考验,这时大家非常关注可用性,需要防止出现雪崩连锁反应,提高系统在故障时的存活能力,常见的措施有:
• 熔断机制:在股票市场,熔断这个词大家都不陌生,是指当股指波幅达到某个点后,交易所为控制风险采取的暂停交易措施。相应的,服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。熔断模式一般都是基于策略的自动触发,一般指的框架级的保护措施。
• 服务降级:服务降级,比较好理解,当整体资源快不够了(业务场景是指除核心链路以外的其他链路),忍痛将某些服务先关掉,待渡过难关,再开启回来。服务降级与业务有关,一般涉及到降级开关。
• 舱壁隔离:确保任何局部问题不影响全局,系统进行业务拆分,垂直划分业务系统,业务模块做到热点隔离(如秒杀单独隔离)、进程隔离、进程内按业务划分核心业务和非核心业务,线程隔离、熔断隔离等。在部署上做到集群隔离、容器隔离、JVM租户隔离等等。
• 限流保护:限流模式,常用于下游服务容量有限,但又怕出现突发流量猛增(如恶意爬虫,节假日大促等)而导致下游服务因压力过大而拒绝服务的场景。常见的限流模式有控制并发和控制速率,一个是限制并发的数量,一个是限制并发访问的速率。针对爬虫场景进行黑名单封杀,针对恶意灌水场景限制用户单位时间的行为,封禁恶意用户。
• 超时与重试:超时避免了一个阻塞不至于导致线程无限期等待、长期占用关键资源导致、最终资源枯竭拒绝服务的情况出现。重试是为了尽可能保证请求成功。
【其他可用性保证】
上述所说的是容错机制,除了副本、容错机制,梳理业务核心链路梳理、在大促期间做到弹性扩容(弹性扩容要求服务无状态。不存在本地缓存、本地差异配置)、系统间解耦等手段也是至关重要的。
• 核心链路梳理:业务核心链路可靠性是业务系统需要优先重点保证的,非核心链路接口调用可采用异步方式或熔断方式做到异步化和服务降级。
• 弹性伸缩:指的是可以根据流量弹性伸缩系统服务能力,让你的系统更有弹性。
• 解耦:非核心业务,比如消息通知可以通过消息队列进行解耦,不要因为此类业务影响和核心业务的可用性。
• 负载均衡:负载均衡是为了防止局部服务压力过大,分担服务器压力,常见的负载均衡策略有随机、轮询、最少活跃数、哈希负载均衡以及各种加权叠加后的负载均衡策略。
• 接口幂等。
3.3.可靠性巡检
以上讲的更多是如何设计一个高可靠的系统,但我们还可以从运维角度发现系统可能存在的不平稳运行风险,这些手段包含:
• 慢查询巡检;
• 大KEY巡检;
• 系统性能指标巡检;
• 系统可用性指标巡检;
• 系统异常日志告警;
3.4.高可用工具
针对线上故障,按时间维度可分为预防、检测、处理、复盘几个阶段。
预防阶段:全链路压测工具、故障演练工具、风险巡检、自动化回归测试
检测阶段:监控系统、服务治理系统、反爬系统、全链路跟踪系统。
处理阶段:自动部署系统、弹性扩容系统、配置(开关)系统;
复盘阶段:COE复盘系统
4.关于性能
响应时间和吞吐量也是衡量系统可靠性的重要指标。
低延迟
降低延迟最有效的解决思路无非以下几种:
• 异步(并行)化:防止所有线程同步阻塞,同步阻塞可能来自一次IO,他可能是一次磁盘IO,也可能是网络IO。同步阻塞不仅增加了响应时间,而且会导致CPU资源的浪费。
• 缓存:缓存即数据本地化,就近获取,缓存思想使用很广,浏览器缓存、CDN、本地缓存、分布式缓存等等。
• 批量:将多次IO转变成一次IO,通过调用批量接口达成目标。
• 程序优化:比如算法优化、查询优化等。
高吞吐
离线任务对吞吐量要求较高,常见优化策略有:
• 控制数据规模在最小范围;
• 尽量减少shuffler动作,减少数据移动;
5.关于事务
事务在数据库领域谈论较多,但其实很多在线业务系统(比如电商领域)对事务要求是比较高的,一般认为,关系型数据库(比如mysql)可以实现强一致性,而现在多数互联网公司多数采用面向服务架构,如何在服务化架构下保证事务一直都是热点话题,多数业务系统提供最终一致性保证,其实说白了他是一种非常弱的一致性(笔者觉得根本就不算事务),常见的解决思路是,常见的模式(这里只举最常见的2个)有:
• 可靠消息模式:在该模式下会用到消息队列这种数据结构来达成一致性目标;
• 定时补偿模式:区分哪些是可靠的、哪些是不可靠的,对于不可靠的事先保证本地事务,再通过定时补偿机制来达到目标。实际上很多消息队列也是借鉴的该模式;
总结
高可用系统构建更多在人,程序员需要严格自律,准守研发规范流程,且具备识别风险和应对风险的能力。在设计阶段,可通过冗余术、失效转移术、分区策略、防雪崩术(熔断、舱壁隔离、限流保护、超时重试)以及负载均衡、解耦、接口幂等等手段做可靠性加固设计。在日常工作中定期做可靠性巡检、时不时做做故障演练,这样能够及时发现潜在的风险,且故障真的发生时可以做到不慌。
The end.
转载请注明来源,否则严禁转载。原文链接