21 20 | 高可用设计,让产品没有后顾之忧
你好,我是乔新亮。这一讲,我想和你聊聊,关于高可用设计的那些事儿。
一提起高可用设计,很多同学立刻就会想到“冗余设计”、“故障转移”等关键词。确实,在大部分与高可用相关的分享里,这两个词往往会被重点强调。
所谓“冗余设计”,是指要通过集群来替代单点服务,做好冗余备份。单点架构是高可用的大敌,“把鸡蛋放在不同的篮子里”是高可用最朴实、最有效的设计思路之一;“故障转移”则是为了缩短故障时间,保证故障发生时,业务可以快速恢复。
如果你对高可用设计了解得更多,可能还会给出如 “CAP 理论”、“异地多活”、“双机架构”一样的更多概念,并且详细解释应用方法。
像这样面对问题,能立刻在大脑知识地图中检索并给出应对举措或方案,是一个优秀的特质,能帮助你通过许多高难度面试。但不知你有没有想过,为何自己在面试中对答如流,在实际工作中却仍然会遇见许多高可用难题呢?为什么还是无法主导一家大型企业整体架构的高可用设计呢?
问题或许是多方面的,比如企业现阶段的发展状况特殊,缺乏实战机会,导致个人资历不够;或者相关部门不太配合,组织人员能力差,代码 Bug 太多,让你一想就气不打一处来……
以上原因都有可能,也都客观存在于很多公司。但我认为,最重要的原因是,缺乏对高可用设计自顶向下的、比较通透的理解和认知,因而在实践中常常迷失在技术细节里,难以窥见架构的全貌。
那么这一讲,我们就来尝试一下,看能否通过一些简单的梳理,建立对高可用设计的完整认知。
解剖高可用设计
在架构设计部分,我们讲了:粗略地说,想做好架构设计,第一步是将一个 IT 系统从应用层级至底层基础设施,全部拆解为一个个应用模块,我们也可以称之为“元素”或“组件”;第二步是保证各个模块间不能孤立存在,还要做好充分的协作,协作通过应用模块对外暴露的“服务”来承载,我们也可以称之为“连接”。
所以说,应用模块和服务,或者叫做元素和连接,共同组成了所谓的架构。那么,要实现架构的高可用,就意味着实现所有元素、连接的高可用。
至此,其实我们已经得出了一点非常重要的认知,再重复一遍:真正的高可用,是指实现所有元素、所有连接的高可用。只要一个元素或一个连接没有做高可用设计,都意味着风险的存在。
比如,公司要求每天必须按时上班,只要迟到就罚钱,那么你如何保证自己的“出勤系统”是高可用的呢?一定是全链条做高可用设计。闹钟至少有两个——互为主备、要保证衣物都在、要为堵车准备预案、要保证高峰期能挤上电梯……你甚至得保证身体健康,不然可能因为拉肚子而迟到。
要注意,在这个例子里。闹钟可能不响、堵车可能会发生,没问题,只要在老板眼里,你永远按时打卡就行,只要整个系统是高可用的就好。更准确地说,所谓高可用,是要保证“业务的连续性”,即在用户眼里,业务永远是正常(或基本正常)对外提供服务的。
怎么样,是不是有点难?是的,在实际工作中,大部分企业的架构设计没有做到高可用,原因可能只有两个:
- 相关负责人压根儿就没考虑高可用设计;
- 做全套高可用的代价太大了,钱不够用,时间不够用。
先前有位同学在专栏下方留言说,乔老师传达的理念就是“trade-off”。我一看,禁不住为他鼓掌喝彩——说得太对了。在企业层面,很多难题不是“简答题”,而是“选择题”。所以在专业成长这一章,我们会先讲“架构决策”,再讲其他内容。
其实人生也如此,职业生涯也如此,如果都有唯一的正确答案,那太简单了。很多教程回答的都是“唯一正确答案”,听着很有道理,实际做的时候,发现根本不是这么一回事。
作为成年人,我们必须得清醒地认识到,人生这道选择题,根本没有标准答案,你能做的只有在不完美中寻找完美,不断trade-off。
做好决策,在很多情况下,并不意味着风险就消失了 —— 风险始终存在。根据墨菲定律,如果事情有变坏的可能,无论概率有多小,它一定会发生。那么假设你我就是企业架构的总负责人,现在要保证企业整体业务的连续性,应该怎么办呢?
有一个很通用的办法,叫做“冗余设计”(我们在文章开头提过了)。所谓“集群”、“分布式”,这些名词大体上都是在描绘“冗余设计”的概念。
但冗余设计也不是万能的,它只能解决底层物理机器,也就是某一个实例的不可用问题。如果是代码逻辑出现了问题,冗余设计就失效了 —— 一个恶性 Bug 在生产环境爆发,后果是所有集群都会遭殃。
没钱做 100% 的高可用设计,又想尽量提供系统的抗风险能力。作为架构负责人,应该怎么办呢?
这里你可以停下一分钟,略微思考一下。
第一个解决方案是,在风险爆发、系统出现问题的情况下,对外提供“降级服务”。也就是说,当团队没有时间去设计、测试并确保当前组件高可用时,如果因为未知原因,导致组件出现故障,我们需要提供一个逻辑相对简单,并且一定可用的服务替代原来的服务实现,实现服务对外的高可用。
以电商系统的物流时效产品为例,正常情况下,该系统需要通知用户已购商品的到达日期。比如,邻近区域当日送达、跨省隔日送达、偏远地区三日送达,等等。具体的物流时效和用户和仓库的距离、仓库作业能力、车辆配送能力很多因素都有关。
但如果因为一个 Bug 或一个配置文件修改错误,导致物流时效产品崩溃了,用户在四级页、购物车,订单确认页调用该服务时失败,怎么办呢?
一个解决办法是:由服务器端技术人员写一段程序,保证任何用户查询物流情况,都显示 3 天送达。出问题的时候,使用此服务进行替换。然后,全体技术人员努力恢复服务,系统恢复后替换回正常的物流时效服务(如果有兴趣,这里你可以想想,为什么这段代码应该是服务端技术人员来写?答案藏在架构设计一节)。
看到这里,你可能就笑了:老乔你这是欺骗用户呢,这还叫什么高可用?
可这就是“降级服务”,实打实的高可用保障手段。在用户眼里,业务是连续的,只是可靠性降低了。其实对于架构师而言,高可用和高可靠应该是两个不同的概念,只是很多人将其混为一谈。
在最理想的情况下,我们既保证高可用,也保证高可靠;但出现问题时,我们优先保证高可用,其次保证高可靠。这是另一点关键认知。
其实在不同领域里,有很多类似的做法。比如在流媒体领域,当用户观看直播出现严重卡顿时,很多企业的第一反应不是查服务器 Log,而是为用户自动降码率。因为比起画质降低,卡的看不了显然会让用户更痛苦。
如果“降级服务”还解决不了问题,应该怎么办?答案是提供“熔断服务”,让出现 Bug 的模块从系统中“熔断”,虽然用户会看到物流系统报错,但整个业务依然是正常响应的,不会被一个系统的 Bug 拖死。
现在我们总结一下:高可用意味着对系统全部元素、连接都进行高可用设计,在物理实例层面主要表现为冗余和集群设计,在代码逻辑层面,方法则多种多样。当你的资源和精力不足以实现全链路高可用时,提供“降级服务”和“熔断服务”,优先保证高可用,其次保证高可靠。
企业里面有些核心服务是不能降级的,对于这类服务,就一定要通过研发流程管理,确保服务的高可用、高可靠。一名合格的技术管理者,要能够识别核心服务,并引导团队重点关注。
高可用,不只是个“设计问题”
在我的职业生涯中,有两次生产事故让我印象最为深刻。
一次是机房停电,一次是知名开源软件 ZooKeeper 出现严重 Bug(几年前的一个版本 Bug,连接数超过阈值就会停止服务,不知道现在是不是修复了)。这两次事故发生时,我基本都处于焦头烂额的状态,甚至几天几夜不能睡觉。它们一个属于底层物理实例出现了问题,一个属于代码逻辑出现了问题。
对于你而言,我觉得代码逻辑导致的系统故障可能更为常见,机房停电、爆炸、地震毕竟发生几率比较小。
如果将“放大镜”对准代码逻辑导致的系统“不可用”问题,我们就会发现高可用设计真正的敌人是“变化”。
设想一下,如果生产环境不发生变化 —— 不发布新版本、不修改配置文件、不修改数据库脚本,系统大概率会一直保持正常(你可能会说,老乔,服务器压力激增也会导致问题。前面我们讲了,这属于架构设计中的“流控”设计,通过容量规划设计,流量控制问题很容易解决)。
此时,生产环境发布新版本就会成为一个影响巨大的变量,极有可能对系统的可用性造成挑战。
所以,研发管理水平的高低,决定了你在版本发布方面的成功率和信心。
单就版本发布问题来说,你需要关注研发管理的三个关键点:
- 记录系统的任何一次发布和变化,包括发布系统/组件、发布时间等;确保自己可以随时定位任何一个时间段内的任何元素及任何发布动作,包括但不限于代码、配置文件、SQL 脚本、设备参数修改等;
- 发布时不影响业务;
- 保证任何发布都可以回滚。尤其当一个大版本的发布时,能否精确识别回滚单元,并做到秒级回滚。
只要做到以上三点,哪怕你在上午十点发布新版本,又有什么关系?根本就不必等到半夜嘛。
当然,总是回退也不是办法。作为一个研发组织,我们还可以从另外一个角度,提高系统的抗风险能力。这里,我要先问你两个问题,如果有时间的话,请你思考一下:
- 由代码逻辑导致的系统风险,是如何进入生产环境的?
- 生产环境出现严重故障,是不是毫无征兆地发生的?
首先回答第一个问题,风险是经由开发环境、SIT 环境、压测环境、PRE 环境,进入生产环境的。所以我们要做的,是严格检查各个环境下的异常,研发管理规范,应该为代码版本进入下一个环境设置准入标准,对于任何异常,都有负责人进行修正。如果异常通过了评估,审核者要对其负责。
第二个问题,答案当然是否定的。就像人在生病前,会在各项生理指标上有所表现。系统 Bug 在导致生产故障前,也往往会有各类异常,我们要做好监控并正式的处理掉它。
这里我想说点题外话,在我们这个技术行业,每年都有猝死的情况发生,每次看到这种消息,我都觉得很遗憾。大家要关注自己的身体健康,关注身体给出的各种警告,爱自己、照顾好自己,才能爱家人。只要关注自己的健康、想办法,猝死大概率不会发生的。
回归正题,在极少数情况下,一个严重 Bug 会藏在生产环境里,始终没有触发。但当它被触发时,可能就会导致生产环境“暴毙”。前面我讲的关于 ZooKeeper 的例子就是这样,到连接数超过阈值后,系统突然就挂了,所有人措手不及。
对于这种情况,一旦碰到了,某种程度上就要认命。我一直讲,人生,运气也很重要,非常重要。如果已经尽全力了,还是出现了问题,要学会接受自己,承认自己的运气不好,将这当作是成功路上的经验、教训,这才是真正的成长性思维。拒绝不完美,渴望成长,当然很棒,但很多心理问题的源头也在这里,要学会接受自己。
所以,开源软件其实是存在一定风险的。但开源软件依然代表了软件研发行业的一种主要潮流,毕竟你免费用了人家的软件那么久,有些许风险不是很正常吗?所以我依然允许团队向生产系统引入开源代码,只是从那次事故以后,我要求:引入开源代码的技术人员,必须通读并掌握其代码。
还有许多研发管理方面的注意事项,包括代码 Review 文化、DevOps 等,我们就不再展开细说了。关键在于要注意,真正的、为业务负责的高可用设计,不是画框图就能解决的,它是一个面向 IT 组织的整体设计。
结语
到这里,我们这一讲的内容就接近尾声了。
高可用设计,意味着“Design For Failure”,最重要的是让我们做产品没有后顾之忧。如果后院天天起火,研发团队每天胆战心惊,也就无从谈起“将产品打磨至卓越”了。所以,做好高可用,一定程度上就是在实践“慢就是快”的认知理念。
如果你有仔细揣摩以上内容,或许会发现:虽然我们或许不能保证所有服务高可靠,但我们是可以保证所有服务高可用的。其关键点在于,面向所有的元素和连接,都要做设计。任何没有被设计过的元素和连接,往往都是不可靠的。
任何一个要达到的目标,都要被量化;任何一个量化的目标,都是一个契约,要有契约精神;任何一个契约,都要进行设计。没有设计的内容,怎么可能如你所愿呢?何况,什么才是你的“所愿”呢?
从这个角度来看,做一名卓越的架构师,真是个苦差事,但也充满了乐趣。
你可能也会想,老乔,那我学的 CAP 理论、异地多活还有用吗?是不是都白学了?
这些当然是有用的,但它们都有具体的使用场景限制。比如对于 CAP 理论来说,在分布式基础设施层的应用有些用,在应用层就受限了,许多人是学完了就忘了,设计架构时该怎么干还怎么干;至于异地多活,已经超过了国内大部分企业的现阶段的业务需要。而且我认为,异地多活最大的优势在于,对架构可扩展性和可维护性的提升,而不仅仅是高可用。
学习,尤其是学习技术和管理知识,其实要有“功利心”。学完了一定要去实践,这样才能真正化为己用。我总是跟你讲:持续学习、学以致用、终身成长,有太多同学就是没做到学以致用,才导致“学了也白学”的结果出现。
希望你在以后的学习生活中,能始终做到学以致用,保持高速的成长,终身成长。
我们下一讲再见!