微服务起源可以追溯到 Peter Rodgers 于 2005 年度云计算博览会提出的微 Web 服务(Micro-Web-Service),并于 2014 年由Martin Fowler 与 James Lewis 比较正式提出。国内腾讯也是微服务实践先行者,2009 年公司内部曾经办过一个《海量服务之道》培训系列,当时主要面对各部门后台核心工程师,其中有一堂课叫《大系统小做》,主要内容讲述如何将一个复杂系统从设计上分解成各个小系统来实现,用现在话来讲就是微服务设计。
负载保护历史更为久远。最早一般是单机负载保护,主要集中在 CPU/内存等硬件资源或者系统内部一些限制如队列长度,网络连接数以及 FD 数量等等。
微服务设计流行起来后,系统架构趋向复杂,单机/单服务负载保护已经不能保证系统流量安全。需要一个整体方案或机制对系统进行负载保护。本文主要结合工作中一些实际经历来讨论微服务环境下负载保护设计。
现网生产环境微服务系统大多数是逐步演化而来,系统内各服务往往技术选型各异,性能参次不齐。技术选型灵活是微服务设计一个优点,能根据需求和团队成员技术栈灵活选取。性能参次不齐则是系统流量安全运营的挑战。如果现网调用链路上某个服务性能脆弱,则存在流量突变时把整个系统拖垮可能性。在项目中曾遇到这种场景,在系统调用关键路径上由于历史原因存在一个古老架构服务 A。当时 A 服务有几个特点:
1. 业务逻辑重,不能轻易换掉。
2. 技术架构比较老,性能差。
3. 现网流量达到一定量,A 服务性能会断崖式下降(即使当时机器资源负载不高),易触发系统雪崩。
4. A 自身负载保护能力缺乏。
A 服务自身负载保护脆弱,需上游服务能保护好 A 服务。如果上游服务对 A 调用有重试逻辑。重试必须加策略,如果只是简单按照固定次数重试,易引发 A 服务雪崩。在设计重试时采用了错误抑制重试策略,即根据下游服务质量决定对下游重试概率。上游对 A 服务访问失败,根据滑动历史时间窗口内访问 A 服务性能数据(耗时/错误码等),计算当前重试概率,历史访问质量好,概率则高,反之亦然。当服务质量低于一定阈值,完全取消对下游重试。可以选择与业务匹配的概率计算函数,以决定重试概率收敛快慢。A 服务异常或崩溃并一定都是由于重试引起,有时仅仅因为业务流量增加。所以上述错误抑制重试策略又向前发展一步,形成弹性熔断机制,即根据滑动历史时间窗口访问性能数据(耗时/错误码等),计算当前对下游访问的概率。
基本逻辑如下图:
产生效果示意图如下:
业界相对应的概念主要是断路器(circuit breaker)和熔断概念。断路器比较早出现在 MichaelT.Nygard 的《Release It!》(中文版《发布!》)。基本原理如下图
对照上图可以知道通过引入概率,将熔断概念进一步发展成弹性熔断。尽可能给用户提供柔性服务,跟公司强调的柔性服务设计理念也比较一致(上文提到《海量服务之道》有一堂课讲《柔性服务》)。
除了需对下游服务提供熔断保护机制,服务自身也需提供保护机制。这种机制在微服务之前已经存在,在微服务环境有一些新变化和发展:
1. 不够重视,特别是一些本身性能高服务,认为自身不是系统最短木板,一旦现网出现过载,往往备案比较仓促。
2. 微服务系统出现过载时,有可能几个服务同时过载,往往下游过载,拖慢上游,造成上游也过载。这时需要利用系统调用 Topo 链自动或人工找到真正过载(瓶颈)服务。
3. 采用微服务由于服务之间调用复杂,入口流量进入系统后,易产生放大效果,即外部一次调用在微服务系统内部产生多次调用,从而导致一些中枢服务触发负载保护。
具有了(弹性)熔断机制和软硬件资源保护也不能真正保证微服务系统流量安全。微服务服务负载保护最核心点:不让过载流量进入系统。其原因主要有以下几点:
1. 当外部过载流量进入微服务系统,会产生有的服务熔断,有的服务正常工作现象。系统一方面需有容错处理,另一方面这些被容错流量没有完成业务逻辑,但事实上消耗了系统资源。
2. 如果微服务触发了资源负载保护或熔断机制,很可能会产生数据不一致,对系统产生短期不可恢复影响。
3. 微服务场景不能苛求每个服务都提供完整负载保护功能,需要整体提供一个机制对系统保护。
基于不让过载流量进入系统的设计理念,需要对系统入口流量进行控制,即流控。流控设计需要解决几个基本问题:
1. 系统正常运营,流控对系统负载影响足够小,流量突变是小概率事件或不是一个经常发生事情,大部分时间系统流量还是平稳或有规律。比较理想状态业务正常运行感受不到流控服务存在。具体实现时要求流控 API 高效低负载。
2. 在现网运营系统加入流控功能,需考虑流控如何对现有系统影响最小。影响小体现在两方面一是现有系统代码改动小,二是运营风险小。在现有调用链路入口简单插入流控服务的方案往往比较难以被现有服务接受。这就引入一个问题:在实现流控时需要将数据(业务)链路和控制(流控)链路分离。
3. 流控需要对业务系统扩缩容透明,简单来说流控系统相对业务扩缩容透明。具体实现需考虑扩容时新机器加入流控系统时的冷启动,以及缩容时下线资源的退出流控机制。,
4. 流控需要支持生产环境各种异构机器部署,事实上现网环境很难保证业务部署的资源软硬件完全一致。
5. 流控触发时,流控不给业务系统带来额外负担,即加剧业务系统资源消耗。实现时需考虑引入缓存机制。
6. 流控系统本身对系统业务流量变化不敏感,不能在业务流量剧增时,流控系统自身需要扩容。比较理想的状态,流控服务容量能做好一年规划,即流控服务能支持业务一年的增长而不需频繁扩容。
7. 在现网复杂环境中需有容灾,容错机制。如处理业务上报数据异常,或数据丢失。又如流控服务失效如何处理,流控服务如何冷/热备。
8. 流控服务和业务相结合。游戏营销中读/写调用链路往往是分开的。它们对流控需求往往也不太一样。读流量限制对用户体验影响往往小一些,而写流量限制往往会直接影响用户体验。通常读流量限制了,写流量相应会下降。但有时读/写流量相互之间又没有关系。设计流控时需充分考虑到这些因素。
9. 系统流量必须高度可观察,否则即使流控系统核心功能再强大,也难免寒冷冬夜被急促的电话从睡梦中叫醒:没有高度可观察的支持系统,别人不清楚触发的流控是否精准有效。
以上是流控设计机制层面比较重点的方面,通常讨论的流控算法(如漏桶和令牌桶算法)是流控设计策略层面。策略设计不理想系统也许还能勉强使用。机制设计有问题系统是基本不可用。
流控服务在保护系统同时,如果可能,还需提高系统资源利用率。如 A 服务与 B 服务共享服务资源,但它们业务峰值时间是岔开的。A 峰值时 B 服务若有资源剩余,则可考虑在不影响 B 服务前提下,从 B 服务拆借部分资源尽可能给用户提供服务。从实现角度即需流控服务同时支持业务维度和资源维度的配额分配。
除了这些,还有个比较重要问题需确认:流控服务到什么业务试点?不可能直接到现网关键业务去验证。测试环境模拟测试与正式环境验证是两个不同概念。生活中经常听到电动车续航里程报告,听到总会下意识想这个报告是在什么环境产生的?温度/路况/风速/负载?正式环境往往比测试环境复杂和难以预测。可以从现网一些有选择的业务开始验证。
综上所述对系统入口流量采用了以下整体负载保护结构:
该方案有以下几个特点:
1. 微服务系统入口服务(processor)通过流控服务对上游流量进行配额限制。
2. 微服务系统入口服务(processor)对下游服务进行了弹性熔断保护。
3. 微服务系统入口服务(processor)对自身资源也进行负载保护。
4. 数据(业务)线和控制(流控)线实现了分离,降低运营风险,以及降低业务接入门槛。
5. 业务流量配额按计算单元分配,并采用了 cache 机制。
6. 对业务流控进行了读写分离。
7. 实现了业务维度和机器资源两个维度流控。
入口流量控制有个潜在问题,入口流量如何与微服务系统内部服务负载相匹配。即入口流量配额低,系统内部服务资源没有充分应用,配额高有可能使系统内部服务过载。配额高就可能触发系统内部服务软硬件资源负载保护或熔断机制,在这种情况下可调小入口流量配额,主动触发流控。需要尽量避免这种情况发生,准确规划系统整体容量以及监控及时发出系统过载预警。
上面总体设计思路:提供系统层级整体负载保护机制,不局限于单机/单服务。不苛求微服务系统每个服务都实现完善的负载保护功能。不让过载流量进入微服务系统。未来希望负载保护能更多智能化:能按历史运营数据和系统容量自动计算系统能够提供安全服务的最大流量,尽量减少业务侧配置。
开源平台 Istio 也提供了丰富的流量管理,其中重要设计思想:对流量进行东西向和南北向管理,具体实现将数据平面和控制平面分离,由微服务框架提供负载保护功能,业务服务尽可能聚焦在业务本身。Istio 第一个生产可用版本 1.0 于 2018 年 7 月 31 日正式发布。上述游戏营销系统的负载保护设计和演化其实并没太多参考 Istio,但最终设计理念和演化结果却异曲同工。
一些思考:
1. 个人或团队平时在需要有技术储备或前瞻,才能业务形态发生变化时,积极拥抱变化,适应业务变化。
2. 软件架构是生长,演化而来。每个系统适合自身的具体方案可能有差异,但设计思想和理念往往殊途同归。
3. 微服务负载保护关键:限制过载流量进入系统,避免系统内部服务触发(机器)资源负载保护或熔断。
评论 1 条评论