1 可靠性治理的痛点

对于亿级流量的线上系统来说,可靠性是至关重要的。从字面上理解,可靠性要求故障少、可信赖。与安全性一样,它们都是信息系统的固有属性之一,也是保障产品质量的关键因素。

对照Google的可靠性模型来看,测试同学会投入很多精力在用例设计、测试执行、持续交付等环节上,研发同学则会更多关注监控、应急和故障分析等。但往往由于项目进度和人力因素,在设计和编码阶段对可靠性的投入和关注不足,导致后续需要付出更高的成本发现和解决潜在隐患。有鉴于此,我们希望能找到更低成本且以更有效的方式发现和治理这些隐患,从而提升系统整体的可靠性。

在研发设计阶段,我们需要关注系统弹性,考虑潜在故障风险、适应流量变化等,其中相关治理涉及幂等性、健壮性、一致性、超时、限流、熔断等场景。与一般功能测试相比,可靠性治理需要面对不同的服务和系统,发现并治理技术问题,在模糊度上有较大的提升和挑战。就目前而言,质量问题非常明确,但潜在风险策略和解决路径比较模糊。因此,我们希望能找到办法识别并解决这些问题。

模糊度的提升会带来两种最常见的现象:

  • 一种是过于具体,Case by Case解决问题,类似算法的过拟合,过拟合的问题在于对更广泛范围内的问题缺乏有效性。以幂等性为例,想验证一个接口是否幂等可以很快完成并很快补充接口幂等相关的测试用例,但是对不同的接口、服务、系统以及不同的幂等性设计,还有哪些问题和风险,我们没有办法关注到并控制这些风险。
  • 另一种是过于泛化,类似算法的欠拟合,欠拟合的问题在于过度虚化导致没有抓住问题的共性特征。以主从延迟为例,主从延迟会给系统带来一致性风险,需要针对性做保护,并进行相关验证,因此我们可以制定规范、梳理Check List和测试模板,虽然这样可以最大程度在产研各环节提醒大家关注到这类问题,但并没有找到彻底解决问题的方法。

2 模式的定义

类似这些问题如何找到更好的解决办法?我们重点看一下模式对可靠性治理的启发。模式在维基百科的定义是:揭示了这个世界上人工设计和抽象思想中的规律。

例如下图所示,计算机图形学中的经典分形图案柯赫雪花,是1904年瑞典数学家科赫提出。可以看到它有明显的规律,这样的分形规律在自然界无处不在。

技术场景的模式会更加丰富些,这类模式和可靠性治理想找到的模式非常接近。

举例缓存设计的两种常见模式:

  • 第一种是Cache-Aside(旁路缓存),也是使用比较广泛的一种方式,它只有在缓存没有命中时,才会查询数据库并更新缓存。
  • 另外一种是Write-throught(只写模式),这种模式在每次数据库变更时都会同步更新缓存。

对比第一种模式,第二种模式的优点是逻辑更清晰、操作简单、缓存命中率更高;缺点是不常请求的数据会被写到缓存中,导致缓存更大。

那么,我们如何找到这些潜在模式并应用到可靠性治理呢?我们现有的业务测试数据、专业知识积累、相关问题分析和复盘经验,都可以帮助我们找到治理这些通用技术场景的规律。在这里,很重要的一部分是真实的业务数据,我们可以从最基础的数据提取信息,找到解决共性问题的思路。

3 大数据下的尝试

随着JVM非侵入式AOP解决方案的成熟,现在我们已经可以采集任意环境下的任意协议流量,以及这些流量依赖的数据关系,也可以在测试环境回放这些流量,包括线上真实采集的流量。这里依赖两个关键点:一是流量采集,这里涉及很多技术方案,这里分享主要是作为一个基础设施;二是有了全链路Mock能力,我们才能在测试环境进行各种流量的回放和验证。

另一个重要基础设施是环境隔离技术,经过快速发展,它已经具备了泳道级别的数据复制隔离,也支持一站式数据消息和部署环境的即拿即用。从最开始通过泳道降低人工测试相互影响,到现在一站式拉出一套环境,能支持各类专项检查独立运行,使用线下数据且不污染主干环境。

基于流量采集和环境隔离这两个能力的成熟,给自动化领域带来了很多新可能。流量采集信息包含了请求参数、返回值和调用链路等信息;环境隔离技术支持数据隔离、消息隔离、各种协议以及部署版本隔离。

在这种情况下,海量的业务流量可以直接转化成基于规则验证的接口自动化用例,也可以应用到基于业务模型的场景级用例,模式在这里更像是两者之间的“折中”,我们希望通过这种“折中”来解决可靠性治理的难题。

4 典型实践分享

接下来,我们重点介绍基于模式的三个可靠性治理的典型实践,主要包括幂等性治理、依赖治理和越权治理三个方向。

4.1 幂等性治理

维基百科中,幂等的定义是数学和计算机科学中某些运算的性质,可以被多次应用,而不会改变最初应用之外的结果。在数学中也有相关的定义,就以一元运算为例,当f(f(x))=f(x)时,可以认为这个运算符f是幂等的;在计算机科学领域,HTTP规范中也有对幂等的定义,即多个相同请求的副作用与单个请求相同,例如GET、PUT和DELETE是幂等的。

在大部分分布式系统中,请求超时、网络抖动等在线上环境中随时可能发生。幂等性设计是保证服务在高并发情况下高可用的关键。对于每天产生海量订单的线上业务,比如库存、交易、支付和财务等系统都需要通过幂等性避免超卖、重复支付、重复打款等问题的发生;同时幂等性也是消息队列、定时任务、分布式事务等技术类场景稳定运行的基础。

如下图举例,当一次调用部分成功的情况下,系统会触发重试,而幂等性可以保证在重试时,成功部分不再被重复执行。

我们要挖掘通用模式,就需要分析幂等性所有可能的实现方案。

如下图是几种常见的幂等性实现方案:数据库层面的唯一索引;通过版本或其他状态、条件来限制操作执行的悲观锁和乐观锁;通过具体业务属性参数,构造具有唯一性的Token以及分布式系统中广泛使用的分布式锁等。

尽管幂等性的实现方案有多种,但回到幂等性的本质,我们希望多次调用不会产生新的副作用,而系统中副作用的产生往往是通过“写”操作发生。

分析调用链路发现,当链路上某个节点不幂等而对资源产生副作用后,其后的多个节点都可以检测到相关变化。例如,前序节点通过数据库的写入生成了新的单号,后序节点的参数和返回值很可能会出现这个新单号。这样,我们就可以构造多次同样的请求,之后检查链路上的这些变化来验证幂等性。

调用链路节点的类型包括了MYBATIS、RPC、HTTP、MAFKA、CRANE等,不同幂等性方案在不同类型的节点上有相应的表现,例如唯一索引,更多在MYBATIS节点上,不同类型节点的检查策略和优先级也不同。

如下图,列举了部分节点检查策略和降噪策略。以MYBATIS为例,我们会关注到写SQL的内容和返回结果,结合索引冲突、锁失败等节点的异常返回进行降噪。比如THRIFT节点,我们会关注接口的参数和返回值变化,考虑随机ID的生成、时间戳字段等进行相关降噪,最终针对不同幂等性方案和不同节点类型形成通用整体策略。

基于通用检查能力,我们可以应用在场景用例编写、流量用例生成和离线流量的自动分析上,通过分析每天线上、线下环境产生的真实流量,我们可以对增量和存量问题进行差异化治理和跟进。

4.2 依赖治理

随着微服务的发展,我们的系统变得越来越复杂,调用链路越来越长,例如单接口的下游依赖多达上百个,任何外部依赖的抖动都会成为核心业务的威胁,很多时候系统内部或外部的一些错误被激活,没有得到正确处理,就会在服务内部不断传播,导致系统偏离正确的服务状态,造成服务失败,最终导致业务失败,引起用户可以感知的故障。

在业务上可以通过依赖分级和熔断策略,保障弱依赖发生故障时,核心流程依然可用。因此我们需要进行依赖治理,而依赖的治理关键在于如何自动化完成分级合理性以及熔断策略有效性的验证。

类似前面,我们会采用回放业务流量的方式,但基于依赖治理,我们的策略是修改依赖的Mock结构,构造依赖故障场景,进行相关验证。

我们的预期是如果命中了弱依赖,我们期望业务主流程不被阻塞,调用链路也没有阻塞,日志打印和返回信息都符合预期,没有异常表现;如果命中强依赖,验证策略则相反。

具体的策略是我们依据接口和依赖关系构造指定依赖故障场景,注入异常后,分析这个异常是否被捕获。如果直接抛到了外层或者接口返回值有相关的异常信息,那当前是强依赖;如果注入依赖后,后续的调用链路被阻断,认为当前依赖是强依赖。反之,则是弱依赖。

具备这样闭环依赖分级识别以及熔断有效性的治理能力后,我们就可以周期性地对核心服务进行下游依赖等级治理和对熔断策略有效性进行自动验证。

运营内容主要包括两方面:配置检查和业务验证。

  • 对于依赖等级正确与否的检查,每周运行,发现依赖等级与熔断策略不相符的情况,推动治理。
  • 对于业务验证,每天运行,持续产生每天增量报告,针对强依赖业务未被阻断、弱依赖业务未被处理,对应的异常等问题推动修改。

4.3 越权治理

越权访问是Web应用程序中一种常见的漏洞,它存在范围广、危害大,被OWASP应用程序安全项目列为Web应用十大安全隐患第二名。对于这种商户、用户规模大,交易频繁的线上业务来说,更是存在比较大的安全和合规风险。

越权就是两个同等级用户,一个可以操作另一个数据;垂直越权则涉及到不同等级用户,例如普通用户可以操作管理员才有的权限数据。

典型的有越权处理接口在收到请求后经历以下三个阶段:

  • 第一步是身份认证,让系统明确当前登录的用户是谁,是后续进行鉴权的基础条件,每家公司和业务可能会有多套鉴权系统。
  • 第二步是系统决策判断,基于当前登录用户信息,根据身份权限判断是否可以继续操作。
  • 第三步是数据权限验证,判断当前数据是否是该用户所属,即数据归属判断。

当系统未做角色判断时,容易发生垂直越权问题;当系统未做数据归属判断时,容易发生水平越权问题。

我们可以通过回放业务流量构造对应场景,验证接口是否有做权限控制。

第一次回放,会结合识别到当前流量鉴权方式,构造一个无权限账户进行回放,其余的依赖数据保持不变;第二次回放与第一次类似,只不过需要构造一个有权限账户信息进行回放;比对两次回放结果以及调用链路,验证这个接口是否有相关的鉴权逻辑;再结合调用链路对比以及原始流量的调用链路,比较有效地识别当前的鉴权场景,兼容一些场景通过返回值没有办法完全识别到是否有做鉴权的情况。

在实际应用中,要考虑我们所使用的流量质量、有效性以及鉴权方式等进行筛选。目前越权检查经过优化和适配不同业务,已经可以自动化、常态化对新增流量进行检测,并将结果同步到报告中,进行日常运营,也支持问题确认、加白和工单创建等。

以上三个治理能力,已经对美团优选部分核心服务默认开启,可以低成本自动化实现相关问题的常态治理及运营。目前覆盖了500+服务、2万+接口和8000+下游、累计发现并治理问题有1000+。近期已经开始陆续接入到公司内其他业务线进行应用。

通过以上3个案例,我们可以看到共性能力和解法,因此后续的规划主要是建设通用基础设施,包含线上、线下以及不同来源的流量积累、流量分析,在其上进行模式挖掘、结果跟进和运营,在这样体系基础上,不断迭代底层能力,构建更丰富的可靠性治理场景。

5 Q&A

Q1:怎样预防配置异常造成的故障?

A:用相关事件举例,我们的一些配置平台包含一些规则,可以字符串形式或者一些类型形式存储,系统对这些配置的兼容性或表现,我们可以构造这些配置的异常场景,比如它当前是一个数值类型的配置,那我们可以构造这个配置的异常值、边界值以及空值,比如它包含分隔符的字符串,我们可以用特殊分隔符以及特殊字符,构造异常配置的获取,验证一个配置的兼容性和可靠性的规则是当读取到这样的异常配置后,我们本来能正确回放的流量,在返回值、抛出异常、日志和调用链路层面有哪些相应表现。很多线上配置变更,因为人为原因和系统默认规则没有兜住的情况下,会引入这样的异常配置健壮性验证,有比较好的保障。

Q2:越权场景检查,链路对比是指走过的链路对比吗?还是每个调用点数据对比?

A:对于越权场景,我们可以识别到它在哪个环节进行了鉴权相关调用,不同的鉴权体系,有对应的权限相关服务和基础接口,构造有权限和没权限的场景后,它会识别没权限后的链路调用变化,比如链路节点数量以及哪个节点返回可以发现大部分问题,如果在这层没有识别到是否做鉴权,我们会关注每个节点的请求和返回,通过其他维度信息增强发现的有效性,降低噪声。

Q3:怎么自动构造接口里没有权限的用户?

A:在原始流量里,我们可以识别到它当前的鉴权方式以及它使用了哪些鉴权服务。这样,我们可以基于这个鉴权方式和服务构造有权限或者没有权限的用户。

Q4:可靠性治理系统是自研的,还是开源的,研发工作量多少人月?

A:可靠性系统建设的思路,目前是自研,它基于美团的基础设施和能力,展开上层解决可靠性问题的实践;研发工作量其实还好,它的点在于我们能找到哪些可以进行治理,快速迭代相关能力,并且在这一过程中不断补全新能力加进去,因此它是一个持续的过程,整体规划上,我们会考虑可靠性,从服务和代码的每一层,从机器、资源、基础组件到服务自身、上游流量和网关分层,拆分不同节点,构建不同策略,这样会有一个整体投入。

Q5:流量限流和服务降级如何实现?

A:美团有服务限流和降级能力的基础设施Rhino平台,服务降级是研发根据当前依赖等级,结合具体业务分析它是否是一个可降级的依赖,再配置对应的熔断策略,当降级时,是绕过当前故障进行降级还是在故障恢复后Fallback,这样的相关规则和策略都可以配置化,自动化验证这些策略是否生效、是否符合预期。

Q6:在有了这些能力基础上,基于模式的可靠性治理用例占比多少?价值怎样评价?

A:我们想基于模式找到一个通用的治理策略和能力,这样的话,我们就可以将线上、线下海量流量数据都应用到这里,不需要QA同学投入成本,编写对应的用例,对于研发来说,我们希望直接确认和解决一些已识别到的问题,只需要花费问题确认和修复的成本。对于可识别用例占比,因为它是基于全量流量,所以随着时间的积累,历史场景以及新场景会相应覆盖到,它的用例有多少,取决于流量池以及流量质量和代表性。

Q7:流量回放Mock,使用字节码,还是沙箱模式?

A:这里用到美团的基础设施能力,它在采集过程中,基于字节码增强采集,回放能力也是使用到了同样的能力。