服务可用性指南

一 单点故障


单点故障概念

单点故障是指系统中一旦失效,就会让整个系统无法运作的部件。

从架构上来说是一种典型的系统性风险,因为如果系统存在单点,即使系统的其它部分做得再完备,也无法降低单点故障造成的破坏性,因此单点故障出现必然导致整体故障。无论是应用服务器还是底层网络、存储,应该尽量避免出现单点故障。

如何防止单点故障

  1. 遵循高可用架构设计,做好软硬件冗余架构,在故障出现时能够及时切换
  2. 定期对单点进行巡检、迭代优化
  3. 通过故障注入来发现系统中潜在的单点故障

二 负载均衡


负载均衡概念

负载均衡是用来在多个计算机、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。

负载均衡实践

  1. 设计流量分配方案时,一定要注意集群负载是否均衡。当部分集群或服务压力过大时应具备水平扩展的能力。
  2. 如果业务会出现热点,可以采用分桶或者热点缓存的方案来规避,尽量把热点请求分散到多台服务器上。
  3. 要注意负载均衡器自身是否会成为瓶颈,包括容量和高可用的部署。

三 服务状态


服务状态类型

有状态 (Stateful) 和无状态 (Stateless) 是服务设计的两种模式,首先我们来看看两者的定义:

无状态服务对单次请求的处理,不依赖其他请求,也就是说处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如说数据库或缓存),服务器本身不存储任何信息。

无状态服务典型的例子就是HTTP协议,它的每个请求都是完全独立的,每个请求包含了处理这个请求所需的完整的数据。

有状态服务:有状态服务(stateful service)则相反,它会在自身保存一些数据,先后的请求是有关联的 ,比如session等

两种状态的优劣

有状态服务: 有状态服务常常用于实现事务, 对于实现事物,Stateful将变得很easy,但这并非唯一的方案;案例:

在商城里购买一件商品。需要经过放入购物车、确认订单、付款等多个步骤。由于HTTP协议本身是无状态的,所以为了实现有状态服务,就需要通过一些额外的方案。比如最常见的session,将用户挑选的商品(购物车),保存到session中,当付款的时候,再从购物车里取出商品信息

无状态服务: 无状态服务对水平扩展是非常友好的,服务的伸缩,对用户来说是感知的;

如果server是无状态的,那么对于客户端来说,就可以将请求发送到任意一台server上,然后就可以通过负载均衡等手段,实现水平扩展。

如果server是有状态的,那么就无法很容易地实现了,因为客户端需要始终把请求发到同一台server才行,此时需要使用session共享等方案来实现

最佳实践

  1. 在分布式系统的设计中,需要考虑系统的可扩展性需求,尽量规避有状态设计
  2. 不要将IP、磁盘路径(OS不同分隔符及路径读写权限不同)写死在代码或者配置里面,最好通过代理实现
  3. 本地磁盘只写日志,不存储业务相关数据

四 监控


监控的重要性

除非你看到问题发生,否则你不会知道应用程序中存在的问题,这就要求我们必须要对应用进行必要的监控,以便于我们能够从外部和内部两个视角来观察应用程序的运行状况;

监控的注意点

  1. 在系统设计之初就设计好监控,而不是事后再补;
  2. 监控需要覆盖基本的系统指标监控(如CPU,Memory,Load,JVM,QPS,RT...)及核心业务指标监控;
  3. 相关的指标需要有同环比的视图,离群视图等,来借此发现性能或容量上的不足;
  4. 核心业务指标监控是站在业务视角,为系统所提供的商业服务做监控,如系统的下单成功率、购物车转化率、GMV等。同样这些核心业务指标需要有同环比的监控、如果有业务指标的同环比异动、可以在第一时间报警给相关技术团队进行排查定位;

报警

只是有监控,或者说监控只是用来做展示的,那么监控将显得毫无价值;有了监控,我们应该配置状态变化的报警;以便我们能随时掌握线上的状态,即便你在外露营,你也能知道线上服务是否出问题;

报警也是要有限制的,不是什么指标都报,那你将被劈天盖地的短信给轰炸的不能正常工作;在配置报警时我们需要注意的是:

  1. 报警收敛: 要么报警,要么不报警,一旦报警就是准确的,是需要立刻处理的

  2. 报警灵敏: 从众多的报警类型(阈值,趋势,指标缺失,同环比,离群...)中选择一个合适的,能够精确表达指标的告警方式

关于线上监控的思考总结

五 回滚


回滚(Rollback)指的是程序或数据处理错误,将程序或数据恢复到上一次正确状态的行为。回滚包括程序回滚和数据回滚等类型。

架构演进的过程中,架构的调整升级不可避免。如从单体应用升级到SOA架构,或者从SOA架构升级到微服务架构。为了避免新架构升级过程中带来的系统性风险,系统变更应该具备可回滚的能力,以此来预防故障出现时定位解决问题造成的长时间业务不可用,故障出现时第一时间回滚即可。

系统变更过程中的回滚能力应该作为系统升级中很重要的一部分体现在技术方案设计文档中,并在上线前进行严格review。一般对于应用的发布变更,变更系统都会提供回滚到某个版本的能力。

注意事项

  • 不要盲目自信,升级过程中要保证所有的线上变更都有回滚方案,故障出现时第一时间先回滚相关变更。
  • 回滚能力应该作为系统升级中很重要的一部分体现在技术方案设计文档中,并在上线前进行严格review。

六 服务降级


当系统访问量激增、性能急剧下降或者非核心服务影响到核心服务的响应时,为了保证主流程的高可用,需要对非核心流程或非核心服务进行降级。

服务降级的核心思路即通过对部分非核心页面和服务的有策略不处理(暂停处理或延时处理)来释放服务器资源保证核心流程的正常或高效运作。

常见于故障出现时的应急操作或大促高峰期对弱依赖的解耦。一般来说降级通常都是有损的,服务降级的四种实现思路:

  • 开关服务: 统一的开关服务将各种服务降级预案统一管理,当出现故障或者业务需求需要降级时,则在开关服务管控平台上操作对应的开关进行服务关闭打开。而开关的统一管理也有利于经验和系统信息的传承,当系统开发负责人不在岗时,其他人也可以通过统一的开关服务管理平台来全局了解系统中的开关信息。
  • 预案服务: 将开关服务、配置服务等其他降级方案组合成预案统一管理,典型的场景如电商平台进行促销活动时,需要有多个开关及配置在大促时做修改,此时就可以将这些服务组合成一个针对性的预案,在适当的时候操作预案启用和恢复即可。
  • 限流降级:限流降级的主要目的是防止系统高负荷运行,保证资源的有效利用。当系统访问量过大或出现突发流量的时候,瞬间的大规模请求可能会超过系统的最大承载能力而导致系统雪崩。这种情况一般通过限流来保障系统核心服务可用,通过局部不可用来交换全局可用。
  • 熔断:熔断一般作用在客户端,当客户端与服务端的通信在一定的时间窗口内达到一定阈值时则执行熔断策略(可以换服务列表中的其他节点重试或进入队列),同时采用异步线程探测服务是否恢复可用。当服务恢复可用时则恢复调用链路。

最佳实践

  1. 系统设计时需要针对强弱依赖做甄别,弱依赖不可用不能导致整个系统崩溃。同样,系统作为服务提供者也需要保证客户端的异常请求不能打垮自己。以上提到的降级措施可以根据场景不同选择使用。
  2. 开关、预案需要沉淀起来,并选择适当的时机对开关及预案的可用性做演练,做好高可用文化的传承。

七 隔离


隔离概念

隔离是指当系统发生故障时,能够将故障的影响范围限定在一定范围,是一种有效限制爆炸半径的策略。通过将系统和资源隔离的方式,一方面可以防止故障发生时影响传播,另一方面可以减少资源竞争。

隔离类型

常见的隔离方案有动静资源分离、线程(池)隔离、进程隔离、集群(分组)隔离、机房(包含逻辑机房)隔离,租户隔离等

  1. 线程隔离主要是线程池的隔离,线程隔离需要我们对应用的请求进行分类,不同的任务交给不同的线程池处理。比如隔离核心线程池和非核心线程池,当非核心任务线程池出问题时,不影响核心线程的处理。
  2. 进程隔离的典型例子就是单体系统拆分成多个系统。随着系统的发展变化,系统所包含的模块和功能会越来越多,极有可能其中一个模块出问题影响到其他模块。通过将系统拆分成多个子系统的进程隔离方式可以有效解决这种问题。
  3. 集群(分组)隔离主要是通过将服务分级分组独立部署的方式来隔离故障。举个例子,系统中商品服务会提供给多个下游调用,其中有核心的交易业务,也有其他非核心的业务,为了保证其他非核心的服务出故障时不影响核心交易,可以对商品服务做分级部署,核心集群和非核心集群的SLA甚至服务器资源可以不一致,这样既保证了故障的隔离性,也充分利用了服务器资源。
  4. 机房隔离包含两个层次的含义:一方面是系统为了满足高可用性的需求,需要多机房进行部署,当一个机房出现不可控的故障时可以通过流量迁移的方式引流到其他正常的机房;第二个方面是机房内尽量保持封闭隔离,也就是说尽量保证服务本机房优先调用,这样可以避免在机房间网络出问题或者其他机房故障时不受影响。

最佳实践

  1. 设计系统时要全面考虑好是否需要隔离性,如果是服务多个核心与非核心业务,最好通过服务分机的方式拆分部署,对不同的集群承诺不同的SLA。
  2. 隔离需要系统化的思考和设计,从线程隔离、进程隔离、集群隔离、机房隔离几个层次逐层递进。

八 同步异步


同/异步优劣

同步与异步并没有孰优孰劣,在系统设计中选用同步或异步实现必须带入实际场景。

但是站在稳定性的视角,同步交互会比异步交互带来更高的故障率,原因也很简单,同步的反馈是即时的,而异步会有一定的时间弹性,利用好异步的时间弹性则可以为我们规避很多不必要的故障。试想在一个同步调用链条中,如果有其中的一个系统响应变慢或者出现故障,整个调用链的吞吐量就会急剧下降。

同步调用的优势在于简单直接,工程师在编写代码和调试时非常方便。因此在实际的项目设计编码中,可能会更倾向于用同步调用来实现需求,长此以往,随着业务流程的变化累积调用链路可能会越来越长也越复杂,而整条链路的可用性则是链路上各个系统可用性的乘积。举个例子,系统链路A->B->C->D,其中ABCD可用性均为99.9%,则整条链路的可用性最终为P(A)P(B)P(C)P(D)=99.9%99.9%99.9%99.9%=96.05%.因此在设计系统的时候,能异步处理的流程尽量采用异步,一方面可以解耦弱依赖,另外一方面可以通过异步的特性来给故障恢复更多的缓冲空间

最佳实践

  1. 在做系统设计的时候,涉及系统间交互或系统内流程模块交互,能采用异步通信的话尽量采用异步。
  2. 一些常见的场景,如相互独立的任务、长耗时任务等都可以采用异步,减少阻塞、降低串联失败的可能性。

九 软件设计


何为设计

设计是把一种设想通过合理的规划,周密的计划,通过各种感觉形式传达出来的过程。设计是将目标更好变现的过程。

在软件设计上,就体现在如何将软件高度抽象,解耦以达到分而治之的过程。

设计是如此重要,我们既要具备对事物的抽象,解耦能力,又要能够从细节上作把控,做到大处着眼,小处着手

对于方法论,软件设计领域也诞生了很多软件设计思想,比如面向过程设计面向对象设计面向服务设计面向消息设计面向失败设计等,每种思想也都是在企业软件发展不同阶段解决某个领域或某一类的问题,没有好与坏的区别。

当你的业务发展壮大,任何一点点的故障都可能带来严重的后果,因此面向失败设计显得尤为重要。我们需要横向从网络,dns,cdn,软负载,中间件,容器,存储,数据库等维度去梳理沉淀经验,也需要从纵向运维发布,监控报警,管控平台,调度,环境部署等维度梳理。

软件设计中常见的参考指标

  1. 编码设计 在模块,服务,字段等设计的时候,需要考虑命名的可读性,见名知意,避免产生歧义。 接口设计需要满足开闭原则。
  2. 分布式设计 需要考虑一致性,避免脑裂问题产生。
  3. 缓存设计 确定命中率指标的前提下,需要持续观察未命中数据特征,特别是无效的查询,容易穿透缓存,命中数据库。
  4. 容量设计 自身稳态及尖刺容量评估确定的前提下,做好容量保护相应措施,比如限流,降级,隔离等。
  5. 依赖设计 做好强弱依赖梳理,避免核心功能依赖非核心系统,非核心流程节点,能走异步最好走异步。
  6. 日志设计 系统日志尽可能异步化,尽量避免无效日志,控制台日志输出
  7. 监控设计 关键监控项及SLA的指标必须要做监控,监控项尽可能覆盖全,同时要做到有效报警,不要让大量无效报警淹没有价值的报警信息。
  8. 权限设计 本着够用原则,避免出现大权限,大账号,如果避免不了,需要做天花板限制。
  9. 表结构设计 分库分表需要考虑热点数据问题,时刻关注表的数据量大小

十 自我保护


系统的处理能力都有一个上限,这个可能是受硬件能力限制,如其所在的服务器硬件性能,网络带宽能力;可能是受下游系统的能力限制,如后端连接的数据库并发能力;也可能是为了保护自己的核心功能。

系统自保护是一个非常重要的功能,它可以防止单点问题扩大化、瞬时问题长尾化,以及非关键问题灾难化。业务系统调用各种其他系统时应该有面向失败的设计,不要无条件的相信其他系统,设计一套自己的容错方案。

系统设计的一个重要原则是认为所有的依赖都是不靠谱的,基于此原则做依赖管理。

最佳实践

  1. 通过 TCP 滑动窗口和拥塞控制等机制来缓解请求拥塞的情况。
  2. 服务端要借助限流组件,合理地配置限流规则,来应对突发的大流量,防止被流量洪峰打垮。
  3. 客户端对不稳定或超长时间的调用进行自动熔断,配置合理的超时时间,防止被不稳定下游服务阻塞而导致级联失败。
  4. 限流是为了减少业务流量,降低系统压力。限流的处理逻辑应简单快速,不应加剧系统开销

十一 缓存设计


缓存的重要性

缓存是系统设计中必不可少的一环,但很多时候缓存设计不当可能会导致一些问题.例如:

缓存穿透,是指每次查询,都走了从缓存,再穿透到DB这个过程,原因一般是数据在DB中不存在。一般的缓存使用流程是,先进行缓存查询,如果key不存在或者key已经过期,再对DB进行查询,并把查询到的对象,放进缓存。如果DB查询对象为空,则不放进缓存。如果查询一定不存在的对象,就会每次都去查询 DB,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对DB造成压力,甚至压垮DB。缓存设计时,应考虑缓存穿透保护,即针对缓存穿透问题采取有效措施,防止大量请求穿透缓存访问 DB 导致 DB 挂掉。

生产实践


  1. 一般缓存使用,先进行缓存查询,如果 key 不存在或者 key 已经过期,再从 DB 进行查询,并把查询到的对象放进缓存;如果 DB 查询对象为空,也将空值写进缓存,只是设定的缓存过期时间较短,比如设置为 60 秒。当有新 key 产生的时候,对缓存进行清理。倘若请求为空的情况太多,导致缓存空数据占用内存空间太大,推荐使用布隆过滤器。即创建一个足够大的 bitmap,用于存储可能访问的 key,不存在的 key 直接被过滤。通过一致性 hash 来进行热点散列。
  2. 业务代码中,缓存层统一添加在数据层上,服务层下,禁止出现嵌套调用。
  3. 构建数据发生异常时,错误数据不放入缓存。
  4. 在任何需要远程调用的逻辑,都应该有恰当的缓存策略,特别是元数据的场景。

十二 容量设计


为什么需要容量设计

新老应用进行替换是在然间迭代中常见的一些做法,而在迭代过程中往往只考虑到了功能问题而忽略了性能问题.

我们需要在做新老应用迁移过程中需要注意可能产生的性能问题,因为在迁移过程中提高了系统复杂度,有时甚至会有双倍的流量进入系统,所以在这种场景下更要重点评估对系统性能的影响.

容量设计在生产中的应用

  1. 设计系统时,需要全面考虑好该系统的处理能力和吞吐量,根据压测结果值去设置系统的相应资源值和流量阈值,包括但不仅限于流控QPS、数据库连接池、分布式服务线程等、也可以通过配置中心做成动态拉取,当系统指标升级、应用扩容时合理设置
  2. 在系统迁移过程中前就需要列清楚上下游关联关系,将所有可能产生的影响都需要进行check
  3. 要有提高对性能的敏感度,在做任何变更前都需要问下自己,这个变更会不会带来性能问题

十三 耦合性


耦合性概念

耦合性是一种软件度量,是指一程序中,模块及模块之间信息或参数依赖的程度。内聚性是一个和耦合性相对的概念,一般而言低耦合性代表高内聚性,反之亦然。低耦合性是结构良好程序的特性,低耦合性程序的可读性及可维护性会比较好。 松散耦合是指二个彼此相关的模块,其中的接口是一个简单而稳定的接口,且其接口和任一模块内部的实现方式无关。

耦合性在生产中的应用

  1. 面对单个应用的高可用设计时,每个功能模块都需要考虑单功能模块失效时程序是否能够继续提供正常服务.
  2. 大规模、多团队维护的同一套系统,需要考虑降低耦合以避免稳定性问题牵一发而动全身,可以从代码层面接耦、进程层面解耦,从机器、应用层面解耦去考虑降低系统整体耦合.

十四 冗余备份


冗余备份模型

工程学里有一个叫冗余备份的模型,在工程领域,航空领域,计算机领域大规模的都有应用。比如航空领域里民航飞机一定会是两个引擎, 两份操作系统。计算机领域里,我们的同城双机房,单元化异地多机房都是为了应对单点故障风险。

如何实现荣誉

  1. 为了提升系统的高可用性,在取得成本投入与高可用保障的平衡前提下,尽可能的对最核心的系统依赖做多份冗余
  2. 我们在做日常需求中,为了满足功能的实现,往往会引入很多跨进程的外部依赖,每引入一个依赖就多一个故障点。一些特别重要的功能,比如像用户下单支付、淘宝交易,由于这些功能对系统的高可用性要求极高,满足可用性的收益远远大于做冗余备份所付出的成本。
  3. 对应用的外部依赖进行整理,依赖最小化,重要依赖组件(如 MQ)增加冗余备份机制。

十五 敏感信息


法律法规

2017年6月《网络安全法》正式发布,围绕个人信息保护的国内立法不断出台,国内外个人信息保护的风险事件(Facebook,空姐滴滴打车遇害)层出不穷,我们所面临监管及舆论环境日益严峻,同时新业务的不断探索和发展,在人脸采集、智能商业服务等场景下我们面对更加复杂多样的情况,这些都对我们如何平衡用户个人信息合规使用和业务快速发展提出了更高的要求,在此基础上对用户敏感信息的使用进行规范以及脱敏保护,防止数据泄露安全时间发生。

我们该怎么做

  1. 规范客户数据在包含但不限于网站、 App 端、工具(业务报表、应用日志、bops工作台)、软件、系统、WAP 端、云服务、OS 及其他服务等页面展示格式。
  2. 建立自己全面细致的敏感字段脱敏二方库
posted @ 2019-07-08 16:03  梧桐花落  阅读(1089)  评论(0编辑  收藏  举报