前言
当讨论高可用时,那么必然有与之对应的低可用甚至不可用,但无论是哪种可用描述,其中都暗含了一个大众共识,即不存在永久稳定运行的系统程序。
事实上,几十年前图灵也论证过类似的问题,称为“停机问题”,具体的描述是:能否为A计算机编程,使得程序才能在有限时间内推断出计算机B是否会停止运行?图灵使用了十分简洁但严谨的反证法论证了停机问题,具体论证方式在这里就不赘述了,最终的结论就是不存在这种程序,也就是说,很不幸,我们无法一种可计算的方式解决停机问题。
所以无论是经验共识还是逻辑论证,我们不得不面对一个现实,即世界上不存在能够时刻保持稳定的系统程序。
基于这个现实,有无数的天才和应用实践试图尽可能的降低机器不可用的概率。所以我想当下谈论到高可用时,就是在不断试错的路上交流经验,但幸运的是,在我们之前有无数天才和实践经验供我们学习参考。
一、高可用
高可用这一概念是伴随着计算机系统的发展和需求的增长逐渐形成。随着分布式系统的发展和互联网的普及,需求与技术也在不断演进和增长,高可用性已经成为现代软件系统架构中不可或缺的重要组成部分。
对于高可用的定义有很多种描述,但都是围绕一个主题,即抵御不确定性,Distributed systems中定义可用性为系统处于正常运行状态的时间比例,如果用户无法访问系统,则称该系统不可用。
如果高可用的问题治理以时间维度划分,可以大致分为一下4个阶段:分为:事前(故障发生以前)、事发(故障发生到系统或人感知到故障)、事中(故障发生到故障处理这段时间)、事后(故障结束之后)。按照上述分类,不同的阶段可以对应不同的技巧
1.事前:副本、隔离、配额、提前预案、探知
2.事发:监控、报警
3.事中:降级、回滚、应急预案,failXXX系列
4.事后:复盘、思考、技改
相比于其他高可用原则而言,隔离原则是十分通用且易懂的,作为保障高可用系统的基石,因为隔离原则最大的作用就是应对为系统提高抵御各种“黑天鹅事件”的保障原则。
二、隔离原则(Isolation Principle)
2.1、定义
隔离原则作为一个抽象的指导原则,并不属于某一个具体的研究方向或领域,而是一个跨领域的设计概念。"隔离"字面意义已经描述的很充分,犹如舱壁隔离( Bulkhead Isolation )。
如果要形象的比喻隔离原则在保证系统高可用中的作用,我想可以描述为一艘航行在大海上的巨轮与底层船舱的关系,当风平浪静时,巨轮安稳前行;一旦遭遇海底暗礁、碰撞等严重灾害是,船舱之间的隔离与坚固程度就是巨轮稳定前行的生命保证。
具体来说,隔离原则在系统设计中采取一系列方法,将系统组件、服务、资源和数据等隔离,尽可能减少相互影响,从而使得系统在对抗各种不确定性时,可以更好的将系统风险分散,提高系统整体可用性。
2.2、实现
虽然隔离原则十分易懂和重要,但它只是一种抽象的原则或设计方式,并不是一种严格的理论框架,实践过程中,我们很难通过量化的方式说系统已经完全做到了充分隔离。
作为系统设计的一部分,在微服务架构(Microservices Architecture)、数据库系统(Database Systems)、网络安全与隔离(Network Security and Isolation)、服务网格(Service Mesh)、应用程序隔离(Application Isolation)、环境隔离(Environment Isolation)、虚拟化技术(Virtualization Technology)等领域都可以称为隔离原则的具体实现。
上述描述有些过于抽象,以日常开发所能接触到的实际应用为例,如服务拆分、color网关、jsf对应着微服务架构领域;事务隔离、数据库分区是数据库系统的具体体现;jdos、编排部署则是容器技术实现;租户隔离、垂直机房是环境隔离方向应用。
仔细分析下来,隔离原则指导下的技术产物在日常开发中无处不在,甚至于让我们有了一种技术本身就该如此的错觉,使用了最新的技术似乎就能保证系统的高可用。但现实经验告诉我们,维持系统的高可用没有这么简单。
事实上,我们可以带着隔离原则重新审视一下日常的开发过程,重新感受隔离原则在现有技术架构下有哪些体现。
2.3 经验与原则
对于一个抽象的原则而言,在工程实践中往往会被总结为一系列的规则建议。隔离原则在实际的开发设计中,隔离原则通常存在以下的实现方式和建议,我们可以从简单的并发编程开始,感受隔离原则的具象化体现。
•线程隔离
日常开发中,我们总会遇到机器数量受限场景,通常的做法就是并发编程,简单的通过几个线程交互完成对某项工作的处理。但当工作比较复杂时,我们一般会选择自定义线程池,或者自己造轮子、引入外部开源组件等。
之所以我们使用线程池,实际上就是想实现多个线程的独立运行,以一当百。这种也称为线程隔离指的是线程池隔离,核心业务线程与非核心业务线程隔离,一个请求出现问题不会影响到其他线程池。如Netty的主从多线程,Dubbo的Connection Ordered Dispatcher的线程模型(图片来源于网络)
但当我们的工作或需求复杂时,增加线程或线程池往往不是最明智的选择,毕竟线程之间的通信过程以来的共享变量与环境耦合过于严重。此时我们通常会考虑将多个线程封装到某个进程中,利用多个线程来时间复杂操作。
•进程隔离
比如把项目拆分成一个一个的子项目,互相物理隔离,或使用命名空间、资源控制以及其他进程隔离技术进行隔离。如前后端分离项目、容器隔离。(图片来源于网络)
但无论我们的程序设计的如何优雅,总有一个绕不开的致命问题,机器才是程序的实体,严重依赖硬件资源就是致命的问题。所以大家往往会选择升级软件架构,比如采用微服务架构,将程序与硬件资源进一步结构
•集群隔离
集群部署与更新时我们目前日常开发中必不可少的流程之一,将应用部署到多个容器,使用集群隔离开不同服务,使互相不影响,可以看作是进程隔离的进一步升级。(图片来源于网络)
集群隔离示例图
但这种方式受限于机器的部署环境,毕竟在地球上,一只海鸥扇动翅膀就可能能够永远改变天气变化。
•机房隔离
更进一步,我们通常会把机器分不同的机房进行部署,如我们常用的廊坊与汇天机房。
从程序到机器,从机器到空间,虽然技术日新月异,我们仍然需要依赖这种朴素的方式抵御风险。如果较真会发现,我们仍然可以继续增大隔离性,但我们先到此为止,我们可以再简单的将视角从物理层面转换到数据流量维度。
•读写隔离
互联网项目中大多是读多写少,我们常规的处理方式就是读写分离,一方面避免读取逻辑对写入逻辑的干扰,同时扩展读的能力,提高性能,提高可用性。在这种架构下,核心的出发点就是数据与操作之间隔离,只是已通过不同技术手段完成。实际上,分库分表、冷热隔离等,都是具体的实现方式。(图片来源于网络)
•热点隔离
当我们用同样的方式审视业务或流量入口时,仍然可以总结出一些规律,如将热点业务独立成系统或服务进行隔离,如秒杀,抢购。
3.3、业务实践
上述讨论的都是围绕现有开发技术与经验的探究,可以发现隔离原则确实无处不在,但无处不在并不等于一定存在,比如我们持续迭代更新的业务系统。
1、垂直机房改造
对于系统的可用性风险事故,给我印象最深的就是在22年刚入职时,就经历过一次严重的线上事故,总的来说就是某个应用的机房挂了,且是机房硬件问题,导致研发人员束手无策,整个交易服务链路完全熔断。事实上,在后续的断网演练与混沌工程中,偶尔也会发现类似的问题。这类“黑天鹅事件”对系统的影响是致命的,所以历经长时间的治理,我想jd的研发同学所负责的系统都应该已经完成垂直机房改造了。
时至今日,再也没有发生过类似的问题了,这应该就是隔离原则最简单最直接的体现了。
背景
跨机房容灾即某机房完全断网后业务不受影响,降低机房故障影响,提升跨机房容灾能力。
技术方案
1)JSF垂直调用改造:服务端提供不同机房不同别名,调用方配置同机房别名调用;
2)NP挂载跨机房多VIP:跨机房多VIP 且 与负载均衡/服务容器IP所在同机房;
3)JIMDB读垂直调用改造:廊坊应用容器读廊坊jimdb节点;汇天应用容器读汇天jimdb节点;不能采用读随机方式。
4)ES双机房主备集群;
5)应用跨多机房对等部署;
6)直接对接color上注册的JSF服务修改成注册HTTP服务。如果JSF直接对接合辙,按机房垂直调用原则在合辙配置不同服务别名即可。
2、es双集群改造
当我们用同样的方式来审视我们的应用系统,我们同样会发现一些风险。比如积理订单的es双集群,对于积理订单es而言,存储着交易链路的订单信息,提供外部系统订单列表信息查询的基础服务,一向是重中之重,以至于我们在es之上的建立了一个专门提供es查询和写入应用服务。
订单es在之前就是双集群,每个集群都是一主两从, 日常也有着均分流量配置,完备的降级切流预案。但致命的问题就在于,双集群都在某个机房之中。是的,类似于垂直机房改造前的风险,虽然从来没有发生过,但这种风险存在是不可接受的。
所以在去年双十一,我们启动了es双集群改造工作,目前已经是两套独立机房的稳定集群提供数据服务。
背景
积理订单系统作为黄金链路中的重要0级系统,其核心流程强依赖的ES为单机房单集群,容灾能力弱,恢复能力差;因此对积理订单ES系统做双集群改造及多种容灾恢复策略,以提升系统可用性。
技术方案
上半部分是我们为了兼容老数据所做的一些切流逻辑,下半部分则是垂直机房改造之后的效果。
在之前,积理订单es应用会接受来自积理订单中间件异步事件消息,通过不同消息轨迹分别写入到不同集群之中,使用消息体中的版本号作为数据最终一致性的保障。
我们仍然沿用这种写逻辑,重新在廊坊和汇天独立申请了两套es集群服务。
而针对读逻辑,我们进行了垂直机房与集群调用的改造,保留了之前的百分比流量配置,同时增加了垂直互备调用,防止某一机房网络抖动或事故导致垂直链路的可用性降低,增加了垂直互备调用逻辑。
如果有同学感兴趣详细的设计思路和实现细节,可以查看我们组童鞋编写的这篇治理文章:ES高可用-双集群改造
容灾读策略使用 | ||
---|---|---|
策略选择 | 场景 | 备注 |
百分比模式 | 单边机房性能受限 | 调低受限机房百分比 |
百分比模式 | 单边机房掉线 | 全量切到正常机房 |
垂直调用互备模式 | 日常场景 | 减少毛刺抖动带来的超时影响 |
垂直调用 | 高性能场景 | 就近调用、快速响应 |
3、流量隔离 - 分组
上面聊到的两个案例实际上都是空间层面的隔离,可以发现我们的系统在严格的审视下,总能发现一些问题,同样的,我们也可以切换视角到应用流量上。
积理订单作为全渠道黄金链路中的重要0级系统,即支持了线上业态,同事也承接着大量的线下业务流量,
但都是使用相同的代码部署。一方面,日常需求的迭代更新中,很难保证代码持续的可靠,这一点,前文已经有所描述。一旦某次线上或者线下业务需求的改造发生问题,也会导致整体链路不可用。更重要的是,线上与线下业态在流量峰值、及时性和业务模式上都存在很大区别,
所以这种场景下,针对流量的隔离就显得十分重要。
背景
线下POS场景的订单交易流程要求极高的可用性与及时性,为了防止线上流量影响线下门店的即时消费,需要将流量进行区分,针对线下场景提供专用容器服务。除了积理订单以外,快退、交易结算等都存在线上与线下分组隔离。
技术方案
4、数据隔离 - 冷热数据归档
最后,我想以一个房间里的大象作为结尾。当我们在机房、应用、流量角度考虑系统的高可用时,通常会想,系统为什么之前不这么做。事实上,我们的技术总是在进步的,而研发系统的架构也是在不断实际的,有些问题可能也只有随着业务的不断丰富才会暴露出来的,软件系统如同随着业务更新不断生长的。
这个数据隔离的应用是订单ums,这个应用实际上提供的功能十分简单,甚至于写入方只有订单系统内部几个系统写入,保存着订单全生命周期中的跟踪信息,全称是订单全流程跟踪信息。对外提供者简单的查询服务。甚至于很久都没有需求改造,持续稳定的提供的服务,从没有发生过线上问题和告警。
但去年的双十一我们终于发现了这头房间里的大象,之所以发现,是因为他已经拥有了1亿条订单跟踪数据,让我们不得不心惊胆战的完成归档改造。
背景
1、 1000000000+条数据,数据量大
2、 日常调用频繁,QPS-50/ms
因此设计做归档的处理,做冷热隔离、热库表只保留90天内的订单信息。
技术方案
三、结语
通过以上对日常开发与业务实践的讨论,我们会发现,隔离原则虽然无处不在,但并不是一定存在的;系统能稳定运行,但不会一直稳定运行。我想这也是研发同学的价值所在:持续的关注系统,持续的审视系统,持续的优化系统。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。