高可用
可用性级别 | 系统可用性% | 宕机时间/年 | 宕机时间/月 | 宕机时间/周 | 宕机时间/天 |
---|---|---|---|---|---|
不可用 | 90% | 36.5 天 | 73 小时 | 16.8 小时 | 144 分钟 |
基本可用 | 99% | 87.6 小时 | 7.3 小时 | 1.68 小时 | 14.4 分钟 |
较高可用 | 99.9% | 8.76 小时 | 43.8 分钟 | 10.1 分钟 | 1.44 分钟 |
高可用 | 99.99% | 52.56 分钟 | 4.38 分钟 | 1.01 秒 | 8.64 秒 |
极高可用 | 99.999% | 5.26 分钟 | 26.28 秒 | 6.06 秒 | 0.86 秒 |
大厂一般要求 4 个 9,其他要求严苛的业务要达到五个九以上
架构的主要分层
- 接入层:主要由 F5 硬件或 LVS 软件来承载所有的流量入口
- 反向代理层:Nginx,主要负责根据 url 来分发流量,限流等
- 网关:主要负责流控,风控,协议转换等
- 站点层:主要负责调用基本服务来装配数据并返回给客户端
- 基础 service:其实与站点层都属于微服务,是平级关系,只不过基础 service 属于基础设施,能被上层的各个业务层 server 调用而已
- 存储层:也就是 DB,如 MySQL,Oracle 等
- 中间件:ZK,ES,Redis,MQ 等,主要起到加速访问数据等功能
高可用需要每一层都实现,解构一下各层怎么实现的呢?
-
接入层&反向代理层
LVS以主备的形式对外提供服务,keepalived 通过心跳检测机制检测健康情况,并完成自动故障转移
-
[网关」,「站点层」,「基础服务层]
注册中心通过服务发现,服务下线实现故障转移(也是心跳检测)
-
中间件
-
Zookeeper
Follower 与 Leader 用心跳机制,故障则选举
ZAB 协议,常用的还有 Paxos,Raft 等协议算法,用在 Leader 选举上,也就是是在分布式架构中,这些协议算法承担了“第三者”也就是仲裁者的作用,以承担故障的自动转移
-
Redis
主从模式,Cluster 集群模式
- 主从模式一主多从引入哨兵(哨兵集群)Raft
- 分片集群模式 将数据分片,多个主节点分担写的压力,每个节点储存部分数据。需要对每个节点做高可用(主从模式)
-
ES
ES 中,数据是以分片(Shard)的形式存在的
-
一、何谓系统稳定性?
“BIBO稳定”,即有界输入有界输出稳定。一个系统如果对任意有界输入得到有界输出,它就是BIBO稳定的。一句话,稳定的系统对于各种有效输入需要有符合预期的输出,它应该像不倒翁一样。
二、如何保障稳定性?
虽然理论上没有绝对稳定的系统,但我们依然可以有所作为,使我们设计和开发的系统在生产环境接近稳定运行。
从大的方面讲,稳定性保障,可以分成3个部分:
制度纪律
- 编码规范、代码提交门禁
- Code Review(人肉)
- 静态代码扫描,动态代码分析(工具)
- Unit Test、压测
- 灰度发布、Rollback、应急预案
- 监控
- 复盘、故障树分析
思想之道
- 保持简单
- 不(零)信任(面向失败)设计
实践之术
- 冗余设计(数据、计算、带宽冗余)
- 无状态设计(快速恢复)
- 容错、灾备
- 隔离(防止雪崩)
- 过载保护(限流、熔断、有损服务)
- 失败重试策略,避免流量风暴
- 去关键路径、去中心化、避免单点故障
- 负载均衡(Load Balance)
- 降级
- 看门狗设计
- 安全编码
三、制度纪律
通过制度去规范操作和行为,通过纪律去约束大家在框架内活动,被证明是保障稳定的第一道防线,也是减少出错行之有效的方式。
纪律是关键,只有持之以恒的遵守制度,才能避免方法和规定沦为空谈。
但制度和纪律只是划出质量底线,只能解决大多数稳定性问题,难以发现一些隐匿的问题,需要配合思想之道和实践之术,持续改进软件质量,才能全面保障稳定性。
四、思想之道
道是大的层面,它具有全局性的指导意义,我从众多的指导思想里,挑选最重要的两点:保持简单和不信任(面向失败)设计,重点展开。
1.保持简单
复杂是稳定性的天敌,保持简单即保持稳定。单一职责,功能清晰即是践行保持简单。
把简单的东西搞复杂很容易,而化繁为简则堪称化腐朽为神奇。所以保持简单并不是低要求,它需要你透过表象洞悉事物本质,用最直接最土味的方式解决问题,做技术的同学有一个奇怪的癖好,喜欢把自己最近琢磨的东西用到项目中,不然总有锦衣夜行的感觉。
我的建议是“学深用浅”。引入复杂性,一方面要权衡收益,另一方面要警惕危害,要理解项目开发很多时候是团队合作,任何复杂性的引入都会对合作者提出更高要求,严以律人是危险的,低门槛才是符合人性的。
2.不信任设计、面向失败设计
不信任设计又叫零信任设计,和面向失败的设计有相似之处,其本质都是防御性编程思想。
不信任设计思想假设依赖的上下游都不靠谱,假设周围都是坏人,假设攻击无处不在。
内部不可信,外部不可信。
措施:边界控制 + 身份认证 + 核查校验
网络服务需要对客户端请求参数做严格验证,不仅检查合法性,也要验证NaN。游戏开发有一句名言:假设客户端的数据都是假的。
进程内的函数调用大多时候很安全,会有可预期的结果,但如果跨进程调用(RPC)的可靠性则会低很多,可能超时,可能丢包,也可能失败,调用者必须意识并处理好各种异常情况,是重试?如果重试的话重试多少次?重试之间的间隔应该怎么确定?请求的上下文怎么保存和恢复?
我们要正确理解不信任设计的内涵,避免用力过猛,警惕借面向失败设计之名行无效编程之实,比如已经对客户端请求数据做了严格校验,在服务器处理过程中,重复检验,比如已经对接口入参判空,在内部调用过程中重复判断。这会混入无效代码,降低代码浓度,损伤可读性和执行效率,除了一点心理安慰并不起实际作用,本质上是违背“保持简单”原则的。
plus:谷歌的零信任架构的本质是以身份为基石的动态可信访问控制。
五、实践之术
术是局部层面,它是实践经验,牵扯方方面面,难以尽数枚举。
如果以文章写作类比软件开发,谋篇布局相当于设计层面,设计层面要致广远,遣词造句相当于实现层面,实现层面要尽精微。
所谓千里之堤溃于蚁穴,防微杜渐需要一以贯之的执行。
1.冗余设计
冗余设计指留出安全余量,不满负荷跑,冗余包括数据冗余、计算冗余、带宽冗余。
数据冗余指一份数据多个副本,一主多备。
计算冗余,比如服务实例的QPS极限是10K,但实际上我们会按5K跑,这样,即使出现请求超速增长,依然有反应时间。
2.无状态设计(快速恢复设计)
互联网服务很多都是无状态设计,服务实例只是逻辑的盒子,后面跟着分布式一致性数据库,这样能极大简化设计,即使实例挂了,客户可以很容易迁移到其他服务实例执行,而有状态设计则要复杂难搞得多。
游戏服务器经常有共享存储的设计,把玩家需要存盘的数据放在SHM,这样即使游戏服务器crash,也能快速恢复,不丢数据。
3.容错、灾备
容错指我们的系统要有一定的错误容忍能力,这意味错误发生,我们要能查错、检错、避错、甚至改错,只要可能,我们就要吞咽错误,而不传播错误。
灾备这个大家耳熟能详,主从设计,异地备灾,目标都是为了应对各种极限情况,实现服务高可用。
4.隔离
隔离就是说如果故障发生了,而又不能吞咽,那应该隔离避免错误传播扩散,避免雪崩,千方百计缩小影响范围,提高系统整体可用率,就像感染新冠要被隔离起来一个道理。
隔离设计并非软件行业独创,它是借鉴于造船行业的船舱设计。
隔离又分两种:
-
按功能/服务隔离,缺点:代码跨多个模块会变复杂,会异步,会影响性能。
-
按用户隔离,多租户架构(VIP,用户等级,用户IP),独立性越高,资源利用率越低。
功能隔离和用户隔离并非互斥,可结合起来使用,实施隔离需要兼顾安全性、效率、资源利用率,模块颗粒度。
另外,容器化等技术为隔离提供良好能力支撑。
5.过载保护
系统过载 = 系统处理能力 < 服务请求量。通常可以用QPS、请求处理的平均时延等去评估系统处理,系统瓶颈指系统中最先接近极限的资源,一般系统资源包括CPU,内存,IO。
过载保护方案分为发生前的过载预防,发生时的过载处置,发生后的过载恢复。
过载预防包括压测,监控,LB,前端预防等,过载恢复一般指假死,宕机后的服务重启,状态恢复等,而过载处置包括:
-
弹性扩容(架构上支持水平扩容时优先考虑的措施)
-
熔断
熔断机制不止软件设计独有,股市也有,我甚至怀疑软件的熔断机制是从股市学来的。
-
限流
系统设计要做好资源耗尽、资源不够用的情况,如果服务请求超过服务能力,那就应该限流,这应该作为一种配置,或者自动执行的策略。这个跟地铁限流差不多,处理不了,那就排队。常见的限流算法有:计数器、漏桶和令牌桶算法。
-
有损服务
有损服务,我印象中最先是腾讯前CTO张志东提出来的,指如果出现服务能力不够,不能为所有客户所有业务提供服务的异常情况,那系统该有所取舍,保大弃小,尽可能保重要业务,减少损失。有损的意义就是有损失,有损伤的意思,它是一种思维方式,是退而求其次,是不得已而为之。
-
Tengine
Tengine的sysguard模块提供了系统过载保护功能:当系统的内存、CPU、负载或RT等指标达到设定的阈值时,请求会被跳转到指定URL,具体配置参考官网;
-
Sentinel
Sentinel提供了限流、熔断、降级和系统过载保护等功能,而且在阿里内部经历了多处大促活动,稳定性和性能是有保障的,是应用稳定性保障的利器之一;
6.失败重试策略,避免流量风暴
如果设计一个ToC服务,在客户大规模断连的情况下,客户会重连,重连失败再连,如果重连尝试的频率不控制好,正常客户端重连有可能演变成对服务器的大规模攻击,打爆一台服务器,又去灭另一台,这太吓人了。
重试退避功能用于计算延迟的算法,在退避阶段,从第一次到最后一次重试的延迟时间计算方法(1)线性的(2)算术的(3)几何的(4)指数的
可以用指数回退方式重试,也可以参考kernel TCP的重连策略,有最大尝试次数,而且重试间隔是逐渐拉大的,避免流量风暴。
7.去关键路径、去中心化、避免单点故障
企业不要关键先生,关键先生会成为瓶颈,软件也不能把宝压到一个地方,去中心化去集中式,就是降低风险。
去中心化在某种程度上来说,可谓是的互联网世界的共产主义化,区块链即是去中心化思想的最好体现。
8.负载均衡
load balance其实就是分担压力,LB要避免倾斜,比如对username做负载均衡,可能c开头的用户远比v开头的用户多,从而导致倾斜。
有多种LB算法,比如RR,比如一致性hash,各有利弊,有兴趣可以研究下。
LB不仅限于服务,进程内的多线程可能也会需要考虑这个问题。
9.降级
降级可以是自动降级或人工降级,可以是读服务降级或写服务降级。
降级处理方案:
- 返回默认值:比如库存调用超时,返回默认现货
- 返回兜底数据:通常是异常或错误发生时最后适用的数据,比如预备的静态页面、默认的数据
- 返回缓存数据
- 返回空值
- 放弃调用
降级发生场景
- 读降级:多级缓存模式下,如接入层缓存–>应用层缓存–>分布式缓存(Redis Clusters)–>RPC / DB,可由远及近的顺序降级读缓存,这种方式适用于对读一致性要求不高的场景。另外,也可走静态页,或屏蔽读
- 写降级:只更新Cache,之后异步扣减库存到DB,保证最终一致性即可。
10.看门狗和心跳机制
可以参考kernel的watch dog,其实就是看护机制,检测错误并努力掰过来。
11.安全编码
安全编码是一个职业程序员的基本要求,安全编码规则很多,很细节的一些规矩。随便列举一些
- 比如理解线程安全相关用法
- 比如要避免死锁
- 比如要谨防资源泄漏。
- 比如要处理好边界,防止越界,溢出。
- 比如理解递归的低效和栈的大小限制,避免爆栈。
- 比如理解整型数据溢出和反转。
- 比如理解做好把关检查的必要性,包括系统把关和模块把关。
12.自动故障转移
近实时的故障转移才是高可用的主要意义
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战