前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微服务设计原则——高可用

微服务设计原则——高可用

作者头像
恋喵大鲤鱼
发布2024-03-25 08:05:12
2180
发布2024-03-25 08:05:12
举报
文章被收录于专栏:C/C++基础

1.限流(保护下游)

限流一般用于保护下游不被大流量压垮。

常见的场景有:

(1)下游有严格的请求限制;比如银行转账接口,微信支付接口等都有严格的接口限频。

(2)调用的下游不是为高并发场景设计;比如提供异步计算结果拉取的服务,并不需要考虑各种复杂的高并发业务场景,提供高并发流量场景的支持。每个业务场景应该在拉取数据时缓存下来,而不是每次业务请求都过来拉取,将业务流量压垮下游。

(3)失败重试。调用下游失败了,一定要重试吗?如果不管三七二十一直接重试,这样是不对的。比如有些业务返回的异常表示业务逻辑出错,那么你怎么重试结果都是异常;又如有些异常是接口处理超时异常,这个时候就需要结合业务来判断了,有些时候重试往往会给后方服务造成更大压力,雪上加霜。所有失败重试要有收敛策略,必要时才重试,做好限流处理。

常用的限流算法有漏桶算法和令牌桶算法。必要的情况下,需要实现分布式限流。

2.熔断

何为熔断?

如果依赖的服务出现故障:一直超时或者一直抛出异常,那在一段时间内就没有必要调用这个服务了,这就是熔断。

熔断即停止对下游访问或切换到备用服务,可以减少对下游施加的压力,也可以防止下游故障向上游传导。

为何要熔断?

在分布式架构中,一个服务通常会与多个外部服务进行交互,这些外部服务可能是RPC接口、数据库、第三方 API 等。例如,在支付过程中,可能需要调用银联提供的 API;而查询某个商品的价格,则可能需要进行营销活动查询。然而,除了自身服务外,依赖的外部服务不可能 100% 可靠。

当依赖的第三方服务出现异常情况时,例如第三方服务过载,会导致调用第三方服务的响应时间变长,甚者形成级联效应。这样一来,服务自身的请求可能会积压,最终可能耗尽自身资源,导致自身服务不可用。

如何应对这种情况?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的服务也需要装上“保险丝”,以防止非预期请求压垮系统。

服务的保险丝就是熔断器(Circuit Breaker)。

熔断器可以帮助系统在依赖的下游服务出现问题时保证整个系统的高可用,减少对下游的压力,防止故障进一步扩散。同时也能在一段时间后重新尝试恢复正常操作。避免局部不稳定因素导致整个分布式系统雪崩。

如何实现熔断?

熔断的实现通常包括监控、熔断器、降级和自动恢复等机制。

重点说一下熔断器的工作原理。

熔断器分为三个状态:关闭状态、开启状态和半开状态。

  • 关闭状态(Closed)

允许调用远程服务,记录调用的状态和延迟等信息。

  • 开启状态(Open)

当调用失败率达到一定阈值后,熔断器会进入开启状态,停止调用远程服务,直接返回失败的响应。

  • 半开状态

开启状态下,每隔一段时间,熔断器会进入半开状态,允许部分请求通过,以测试远程服务的可用性。

如果测试成功,则熔断器进入关闭状态,否则重新进入开启状态。

一般不需要自己实现一个熔断器,可以使用常见的熔断组件,主要有奈飞的 Hystrix 以及阿里的 Sentinel,两种互有优缺点,可以根据业务的实际情况进行选择。

3.降级

降级是一种在面对特殊业务或异常情况时保持系统可用的策略。当依赖的服务不可用时,退而求其次提供一些基本功能或返回预设的默认值,以确保系统依然能够提供有限的功能或服务;又或者某些特定活动场景(例如双十一)下优先保障计算资源投入到 业务倾向的服务,降级边缘服务。

大部分服务是如下结构,既要给上游使用,又依赖于下游提供的第三方服务,中间又穿插了各种业务逻辑,这里每一块都可能是故障的来源。

如果第三方服务挂掉怎么办?我们业务也跟着挂掉?显然这不是我们希望看到的结果,如果能制定好降级兜底的方案,那将大大提高服务的可靠性。

比如我们做个性化推荐服务时,需要从用户中心获取用户的个性化数据,以便代入到模型里进行打分排序,但如果用户中心服务挂掉,我们获取不到数据了,那么就不推荐了?显然不行,我们可以在本地 cache 里放置一份热门商品以便兜底。

又比如做一个数据同步的服务,这个服务需要从第三方获取最新的数据并更新到 MySQL 中,恰好第三方提供了两种方式:

  • 一种是消息通知服务,只发送变更后的数据。
  • 一种是 HTTP 服务,需要我们自己主动调用获取数据。

我们一开始选择消息同步的方式,因为实时性更高,但是之后就遭遇到消息迟迟发送不过来的问题,而且也没什么异常,等我们发现一天时间已过去,问题已然升级为故障。合理的方式应该两个同步方案都使用,消息方式用于实时更新,HTTP 主动同步方式定时触发(比如 1 小时)用于兜底,即使消息出了问题,通过主动同步也能保证一小时一更新。

4.过载保护(保护自己)

如果是高并发场景使用的接口,那么需要做过载保护,防止服务过载引发雪崩。

相信很多做过高并发服务的同学都碰到类似事件:某天 A 君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。

当流量过大,服务过载时,可以采取「拒绝」或者「引流」等措施。

过载保护的具体做法:

  • 请求等待处理超时

比如把接收到的请求放在指定的队列中排队处理,如果请求等待处理超时了(假设是 100ms),这个时候直接拒绝超时请求。再比如队列满了之后,清除队列中一定数量的排队请求,保护服务不过载,保障服务高可用。

  • 服务过载及早拒绝

根据服务当前指标(如 CPU、内存使用率、平均耗时等)判断服务是否处于过载,过载则及早拒绝请求并带上特殊错误码,告知上游下游已经过载,应做限流或熔断处理。

  • 引流

自身只做轻量的处理逻辑,然后将流量转发给性能更高的系统去做处理,或者完全不处理,只做一个代理转发。

5.快速失败

遵循快速失败原则,一定要设置超时时间。

假设一个第三方接口正常响应时间是 50ms,某天该第三方接口出现问题,大约有15%的请求响应时间超过 2s。没过多久我们的服务负载飙生到 10 倍以上,响应时间也变得很慢,即第三方服务将我们的服务拖垮了。

为什么会被拖垮?没设置超时!我们采用的是同步调用方式,使用了一个线程池,该线程池里最大线程数设置了50,如果所有线程都在忙,多余的请求就放置在队列里中。如果第三方接口响应时间都是 50ms 左右,那么线程都能很快处理完自己手中的活,并接着处理下一个请求,但不幸的是如果有一定比例的第三方接口响应时间为 2s,那么最后这 50 个线程都将被拖住,队列将会堆积大量请求,从而拖垮整服务。

正确的做法是和第三方商量确定一个较短的超时时间比如 200ms,这样即使他们服务出现问题也不会对我们服务产生很大影响。

6.无状态

尽可能地使微服务无状态。

无状态服务,可以横向扩展,从而不会成为性能瓶颈。

状态即数据。如果某一调用方的请求一定要落到某一后台节点,使用服务在本地缓存的数据(状态),那么这个服务就是有状态的服务。

我们以前在本地内存中建立的数据缓存、Session 缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存,让业务服务变成一个无状态的计算节点。迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。

7.最少依赖

能不依赖的,尽可能不依赖,越少越好。

减少依赖,便可以减少故障发生的可能性,提高服务可靠性。

任何依赖都有可能发生故障,即使其如何保证,我们在设计上应尽可能地减少对第三方的依赖。如果无法避免,则需要对第三方依赖在发生故障时做好相应处理,避免因第三方依赖的抖动或不可用导致我们自身服务不可用,比如降级兜底。

8.简单可靠

可靠性只有靠不断追求最大程度的简化而得到。

乏味是一种美德。与生活中的其他东西不同,对于软件而言,“乏味”实际上是非常正面的态度。我们不想要自发性的和有趣的程序;我们希望这些程序按设计执行,可以预见性地完成目标。与侦探小说不同,缺少刺激、悬念和困惑是源代码的理想特征。

因为工程师也是人,他们经常对于自己编写的代码形成一种情感依附,这些冲突在大规模清理源代码的时候并不少见。一些人可能会提出抗议,“如果我们以后需要这个代码怎么办?”,“我们为什么不只是把这些代码注释掉,这样稍后再使用它的时候会更容易。”,“为什么不增加一个功能开关?”,这些都是糟糕的建议。源代码控制系统中的更改反转很容易,数百行的注释代码则会造成干扰和混乱;那些由于功能开关没有启用而没有被执行的代码,就像一个定时炸弹等待爆炸。极端地说,当你指望一个Web服务7*24可以用时,某种程度上,每一行新代码都是负担。

法国诗人 Antoine de Saint-Exupéry 曾写道:“不是在不能添加更多的时候,而是没有什么可以去掉的时候,才能达到完美”。这个原则同样适用于软件设计。书写一个明确、简单的 API 是接口可靠的保证。我们向 API 消费者提供的功能和参数越少,这些 API 就越容易理解。在软件工程上,少就是多!一个很小很简单的 API 通常也是一个对问题深刻理解的标志。

软件的简单性是可靠性的前提条件。当我们考虑如何简化一个给定的任务的每一步时,我们并不是在偷懒。相反,我们是在明确实际上要完成的任务是什么,以及如何容易地做到。我们对新功能说“不”的时候,不是在限制创新,而是在保持环境整洁,以免分心。这样我们可以持续关注创新,并且可以进行真正的工程工作。

9.分散原则

鸡蛋不要放一个篮子,分散风险。

比如一个模块的所有接口不应该放到同一个服务中,如果服务不可用,那么该模块的所有接口都不可用了。我们可以基于主次进行服务拆分,将重要接口放到一个服务中,次要接口放到另外一个服务中,避免相互影响。

再如所有交易数据都放在同一个库同一张表里面,万一这个库挂了,此时影响所有交易。我们可以对数据库水平切分,分库分表。

10.隔离原则

控制风险不扩散,不放大。

不同模块之间要相互隔离,避免单个模块有问题影响其他模块,传播扩散了影响范围。

比如部署隔离:每个模块的服务部署在不同物理机上;

再如 DB 隔离:每个模块单独使用自身的存储实例。

古代赤壁之战就是一个典型的反面例子,铁锁连船导致隔离性被破坏,一把大火烧了80W大军。

隔离是有级别的,隔离级别越高,风险传播扩散的难度就越大,容灾能力越强。

例如:一个应用集群由N台服务器组成,部署在同一台物理机上,或同一个机房的不同物理机上,或同一个城市的不同机房里,或不同城市里,不同的部署代表不同的容灾能力。

例如:人类由无数人组成,生活在同一个地球的不同洲上,这意味着人类不具备星球级别的隔离能力,当地球出现毁灭性影响时,人类是不具备容灾的。

11.幂等设计

所谓幂等,指对接口的多次调用和调用一次所产生的结果是一致的。

接口需要幂等设计的主要原因是确保在不同条件下的重复请求或意外的重复操作不会导致意外的行为或数据损坏。

重复请求很容易发生,比如用户误触,超时重试等。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果时网络异常(超时成功),此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。

在设计幂等接口时,通常会使用一些标识符或令牌来确保相同的请求不会被重复处理。这可以通过在请求中包含唯一标识符或使用状态管理来实现。幂等设计是分布式系统和服务设计中的一个重要原则,有助于提高系统的可靠性、稳定性和一致性。

12.故障自愈

没有 100% 可靠的系统,故障不可避免,但要有自愈能力。

人体拥有强大的自愈能力,比如手指划破流血,会自动止血,结痂,再到皮肤再生。微服务应该像人体一样,当面对非毁灭性伤害(故障)时,在不借助外力的情况下,自行修复故障。比如消息处理或异步逻辑等非关键操作失败引发的数据不一致,需要有最终一致的修复操作,如兜底的定时任务,失败重试队列,或由用户在下次请求时触发修复逻辑。

13.CAP 定理

2000 年,加州大学伯克利分校的计算机科学家 Eric Brewer 在分布式计算原理研讨会(PODC)上提出了一个猜想,分布式系统有三个指标:

代码语言:javascript
复制
一致性(Consistency)
可用性(Availability)
分区容错性(Partition tolerance)

它们的第一个字母分别是 C、A、P。

Eric Brewer 说,这三个指标最多只能同时实现两点,不可能三者兼顾,这便是著名的布鲁尔猜想。

在随后的 2002 年,麻省理工学院(MIT)的 Seth Gilbert 和 Nancy Lynch 发表了布鲁尔猜想的证明,使之成为一个定理,即 CAP 定理。

CAP 定理告诉我们,如果服务是分布式服务,那么不同节点间通信必然存在失败可能性,即我们必须接受分区容错性(P),那么我们必须在一致性(C)和可用性(A)之间做出取舍,即要么 CP,要么 AP。

如果你的服务偏业务逻辑,对接用户,那么可用性显得更加重要,应该选择 AP,遵守 BASE 理论,这是大部分业务服务的选择。

如果你的服务偏系统控制,对接服务,那么一致性显得更加重要,应该选择 CP,遵守 ACID 理论,经典的比如 Zookeeper。

总体来说 BASE 理论面向的是大型高可用、可扩展的分布式系统。与传统 ACID 特性相反,不同于ACID的强一致性模型,BASE 提出通过牺牲强一致性来获得可用性,并允许数据段时间内的不一致,但是最终达到一致状态。同时,在实际分布式场景中,不同业务对数据的一致性要求不一样,因此在设计中,ACID 和 BASE 应做好权衡和选择。

14.BASE 理论

在 CAP 定理的背景下,大部分分布式系统都偏向业务逻辑,面向用户,那么可用性相对一致性显得更加重要。如何构建一个高可用的分布式系统,BASE 理论给出了答案。

2008 年,eBay 公司选则把资料库事务的 ACID 原则放宽,于计算机协会(Association for Computing Machinery,ACM)上发表了一篇文章Base: An Acid Alternative,正式提出了一套 BASE 原则。

BASE 基于 CAP 定理逐步演化而来,其来源于对大型分布式系统实践的总结,是对 CAP 中一致性和可用性权衡的结果,其核心思想是即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。BASE 可以看作是 CAP 定理的延伸。

BASE 理论指:

  • Basically Available(基本可用)

基本可用就是假设系统出现故障,要保证系统基本可用,而不是完全不能使用。比如采用降级兜底的策略,假设我们在做个性化推荐服务时,需要从用户中心获取用户的个性化数据,以便代入到模型里进行打分排序。但如果用户中心服务挂掉,我们获取不到数据了,那么就不推荐了?显然不行,我们可以在本地 cache 里放置一份热门商品以便兜底。

  • Soft state( 软状态)

软状态指的是允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。

  • Eventual consistency(最终一致性)

上面讲到的软状态不可能一直是软状态,必须有时间期限。在期限过后,应当保证所有副本保持数据一致性,从而达到数据的最终一致性,因此所有客户端对系统的数据访问最终都能够获取到最新的值,而这个时间期限取决于网络延时,系统负载,数据复制方案等因素。

参考文献

微服务的4个设计原则和19个解决方案 博客园.如何健壮你的后端服务? 高可用的本质 CAP 定理的含义 - 阮一峰的网络日志 CAP理论该怎么理解?为什么是三选二?为什么是CP或者AP?面试题有哪些? Base: An Acid Alternative

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.限流(保护下游)
  • 2.熔断
    • 何为熔断?
      • 为何要熔断?
        • 如何实现熔断?
        • 3.降级
        • 4.过载保护(保护自己)
        • 5.快速失败
        • 6.无状态
        • 7.最少依赖
        • 8.简单可靠
        • 9.分散原则
        • 10.隔离原则
        • 11.幂等设计
        • 12.故障自愈
        • 13.CAP 定理
        • 14.BASE 理论
        • 参考文献
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档