查看原文
其他

基于微服务成熟度模型的高可用优化实践

移动开发团队 爱奇艺技术产品团队 2022-03-15

前言

     随着微服务的流行,每个互联网公司后台都有无数大大小小的服务,服务与服务之间又有着千丝万缕的调用关系。要保证整个微服务系统的成熟稳定,就必须保证每个微服务的成熟度。但如何来定义服务的成熟度?应该从哪些纬度来考量?各个纬度里又有哪些普遍的问题?如何来优化?

     本文介绍了爱奇艺技术产品团队用来衡量服务成熟度的模型,并基于此模型对多个后台服务进行评估,总结出了一些常见的低分项,并对低分项整理了相关优化方案。希望对大家有所帮助。


01

 服务成熟度模型

       为了能够对服务的成熟度进行量化,我们需要从多个维度对服务进行评估。

       每个服务随着需求不断迭代,每个迭代周期都需要经历如下三个阶段:开发测试,运维上线,线上运行。每一个阶段,都需要一系列的措施来保证服务最终的成熟可靠。我们针对每一个阶段,梳理出来需要关注的指标,来衡量该阶段的整体质量。

       我们对上面3个阶段进行综合评分,并根据评分对服务成熟度进行了等级划分。(1-5分为准入级,6-7分为服务级,8-10分为成熟级。)

       通过这样的服务成熟度模型,我们就可以以量化的指标直观的评价一个服务的成熟度,并发现服务有哪些可改进项。


02

 低分项梳理

      基于服务成熟度模型,我们对多个后台服务进行了评估,发现了一些指标项在各个服务中普遍得分较低。这些指标项包括:

  • 开发测试方面:代码规范,单元测试,压力测试,接口拨测;

  • 运维上线方面:灰度上线,上线回滚;

  • 线上可用性方面:系统保护,数据备份,报警处理,应急预案。

       于是我们优先对这些低分项的优化方案进行了梳理,期望通过对低分项的优化,提升服务的成熟度,使我们的服务提升至成熟级别。


03

 低分项优化方案

      我们的服务主要基于Java语言开发,主要是虚机部署的部署方式,并少量采用容器部署。所以下面的优化方案主要围绕我们的服务现状展开。

3.1

开发测试

       线上bug往往都是开发新需求引入的,如果在开发测试阶段尽可能的发现问题,提高代码质量,能够将问题的影响范围控制在最小。下面是开发测试阶段一些常见的措施和手段:


3.1.1 代码规范

       代码风格:采用统一的代码风格,一方面可以使得代码的可读性提升,另一方面会减少代码合并时的冲突,并与开发工具集成,支持自动提示不规范代码,有的不规范代码甚至可以一键修正。配合GitLab CI和Sonar检查,可以对每次的提交进行代码规范的检查,并以报告的形式直观的展现。


3.1.2 单元测试

      单元测试位于测试金字塔的最底层。运行效率快,一般完成的测试case跑下来不会超过10分钟,可以集成在CI流程中,快速反馈问题。维护成本低,单元测试在开发阶段就能发现问题,修复成本低。测试金字塔越向上反馈的时间越长,实现的成本也越高。因此推荐大家提升单元测试的覆盖率。

      Java最常用的测试框架是Junit。另外配合Mock测试库Mockito和测试覆盖率工具Jacoco形成完整的单元测试工具集。在IDEA和SonarQube中都可以配置Jacoco的插件来直观的展示单元测试的覆盖率。

      在推进单元测试的过程中,我们经常遇见的问题是:项目补充单元测试费时费力,新增代码的测试覆盖率如何把关?

我们的解决思路是:

  • 已有代码优先对重要代码(如公共库,工具类)补充单元测试。

  • 新增代码增加代码覆盖率的指标(如:设定新增代码测试覆盖率不得低于90%)

  • 随着新增代码和对老代码修改,项目整体的测试覆盖率会逐步提升

新增代码测试覆盖率统计:使用diff-cover工具可以通过分析分支代码差异和Jacoco测试报告,计算出当前分支修改代码的测试覆盖率。


3.1.3 单元测试

(1)为什么要做压力测试?通过压力测试,我们能够:

·  发现性能瓶颈

·  预估资源使用情况

·  为限流配置提供参考

·  发现新需求引入的问题

(2)压力测试工具应具备什么功能?

常见的压力测试工具有JMeter等。我们使用爱奇艺云平台提供的LoadMaker云压测系统,它具备如下特点:

·  多个施压集群,压力可以达到很高

·  创建和保存压测场景,重复执行

·  提供压测报告:QPS,响应时间,错误率等数据

       同时,为了压测不影响线上用户的真实流量,我们构建了单独的压测资源池。在需要压测时,选择资源池中的机器和带压测模块,可自动构建压测环境。

(3)什么情况下需要压测?

·  例行压测:版本灰度期间对重点项目进行例行压测。

·  新项目/接口上线前压测:新项目上线前压测,指导容量预估和限流配置。


3.1.4 接口拨测

       对于后台接口服务,我们比较关心线上接口返回的数据是否正确以及新代码上线是否影响了已有逻辑?接口拨测可以很好的覆盖上面两个问题。一方面可以用于线上接口返回数据的校验,实时发现接口异常。另一方面可以用于自动化接口测试,配合上线流程,发现新增代码是否有引入的问题,及时终止上线操作。一个接口拨测系统应具备如下特性:

(1)支持对接口返回结果做校验。

(2)复杂校验支持编写脚本。

(3)可设置多个拨测点,模拟正式用户请求场景。

(4)支持报警。

(5)详细的测试报告。

(6)和上线流程打通,支持灰度后的自动化接口测试。

3.2

运维上线

3.2.1 灰度上线机制

       代码在测试环境测试通过并不能保证代码完全没有问题,仍然有可能因为测试疏漏或环境差异等原因导致未发现的问题。如果没有灰度上线的机制,这些潜在问题直接上线,可能对线上系统带来灾难性的后果。

       灰度上线需要配合灰度检查来确保本次上线的安全性。我们采用的检查方式有:

(1)灰度上线后针对灰度机器进行自动化接口检测。

(2)对比灰度机监控指标和线上机器的监控指标。

(3)灰度环境测试功能验收。


3.2.2 上线回滚

       如果发生因为上线新代码导致的线上问题,首先要做的应该是回滚操作,而不是去分析问题和修复代码上线。回滚操作需要尽量做到以下几点:

(1)回滚速度快,如果需要revert代码,重新编译打包,上传至服务器,整个流程会比较漫长,故障恢复的时间也会比较长。

(2)回滚完整,有些代码和配置文件是有依赖的,如果只回滚了代码,可能导致启动后的异常。

(3)可回滚性,这需要架构层面的思考,不要出现无法回滚的情况,如上线后数据写入,对回滚后的代码不兼容。

       我们采用的是RPM打包的方式进行上线和回滚。通过RPM的形式把一次部署的所有内容(代码和配置)打包,部署时服务器保留历史版本的RPM包,当需要回滚时,直接执行Job安装历史版本的RPM包即可。如果服务采用的是容器部署,则回滚起来会更简单,只需启动历史版本的镜像即可。

3.3

线上可用性

3.3.1 系统保护

       线上运行时可能面对各种各样的问题,机器故障,网络问题,依赖的服务方挂掉,流量剧增等等。如何在如此险恶的环境下生存?不能相信别人,只能提升服务自身的可靠性。一般采取方式有:熔断,限流,降级,重试等。


熔断

      当我们依赖的服务方有问题时,不能把我们的服务拖垮,常用的方式是熔断。当依赖方成功率低于我们设置的阈值时,暂停对其调用,过一定间隔再去重试。

      常用的熔断组件有:Hystrix,Sentinel等。下表是Hystrix和Sentinel的对比。

      鉴于Netflix对Hystrix不再维护,以及Sentinel更加丰富的功能,我们选择了Sentinel作为我们的熔断组件。


降级

       当发生突发事故,服务整体超负载时,为了避免服务整体不可用,一般会启用服务降级,以保证重要或基本服务正常运行,非重要服务延迟使用或暂停使用。

       为此,我们实现了一个页面降级服务。该服务会定时请求页面接口,并对其进行校验,校验通过后将结果保存起来;当服务出现异常或超负载时,打开降级开关,页面服务会直接读取之前保存的静态页面数据返回。牺牲个性化,从千人千面降级为千人一面。由于去除了页面构造的流程,处理能力会大幅提升,保证服务的正常运行。


限流

       限流非常重要,它是保护服务正常运行的重要关卡。尤其是在出现流量突增时,合理的限流可以保护你的服务不被打垮。如果没有配置限流,不但流量突增时服务会被打垮,服务也难以恢复,因为服务重启后会被再次打垮。

       我们一般会在网关层和服务层分别配置限流。

       网关层使用nginx的限流插件可以配置单机的限流。

       服务层使用Sentinel限流。Sentinel可以基于QPS,并发数,调用关系来限流,并且支持集群整体限流。下图是Sentinel集群限流的实现。


重试

     从客户端到后端服务,中间各个环节都有可能出现异常,如DNS故障,运营商节点故障,后台服务器故障等等,导致请求失败。

      针对不同的情形,适当的重试可以增加请求成功率。爱奇艺APP端的重试分为如下几种:

1. IP直连重试,通过配置直连IP数来控制重试次数。

2. 超级管道重试,公司自研基于HTTP的网关代理服务,可做到异地重试。

3. HTTP重试。

4.原url重试。

      详细信息可以参考公众号文章《爱奇艺移动端网络优化实践分享:网络请求成功率优化篇》。

       重试可以提高成功率,但是过度重试可能会导致后端服务压力过大,出现雪崩效应。所以重试策略上应该注意如下几点:

1.  重试策略可云控,极端情况下可后台配置关闭重试。

2.  区分错误码重试,不是所有错误码都需要重试,如后台触发限流返回的错误码就不需要重试。


3.3.2 数据备份

       在数据库的使用上,我们需要考虑数据库的高可用。在这方面,公司的服务云团队做了很多方面的优化,大家可以在公众号文章里查询到,本文就不详细展开了。作为业务方,我们需要注意的是数据库的使用要严格按照两地三中心的架构申请和使用。


3.3.3 监控报警

       完善的监控报警可以让我们在服务出现问题的第一时间感知到,及时处理,避免问题的扩散。从不同的纬度,我们需要不同的监控,主要包括:


指标监控

     监控服务的关键指标,这里包括3部分:机器指标(CPU,内存,网络流量等),服务指标(QPS,成功率,响应时间等),第三方接口指标(QPS,成功率,响应时间等)。

      具体的实现方案是:通过Collectd和自研Meerkat组件进行指标上报,上报给Graphint进行汇总存储,并通过Grafana进行展示。


应用日志监控

      基于应用日志的监控可以让我们发现更加细粒度的异常,如应用内抛出的异常,内部业务逻辑错误等。

       首先我们定义了一套日志打印的格式规范,所有需要监控的日志都按统一的格式进行日志输出。然后通过日志收集组件Venus将机器上的日志投递到Kafka队列,最终入到Druid时序数据库内,进行多维度的分析存储。前端报表使用Graphna进行展示。


全链路监控

       如果要监控上下文的链路关系、跨系统的故障定位等相关问题,需要进行全链路监控。详细信息可以参考公众号文章《爱奇艺全链路自动化监控平台的探索与实践》。


3.3.4 应急预案

       尽管我们上面列举了一系列的可用性优化措施,但在真实的线上环境里,仍然会出现各种各样的异常,故障,需要我们进行手动处理。当问题真正出现时,如果现场去想应该如何处理,可能由于慌乱而没有做到最优的处理方案。因此,我们需要提前想好可能出现的问题,以及出现这些问题时应该如何处理,并形成书面的文档。在问题出现时,我们只需要对着文档一步一步处理即可。


04

# 服务质量检查

       有了优化方案,如何判断服务有没有按照优化方案实施?如何保证服务长期保持较高的可用性?我们不希望做完一轮优化,服务成熟度提升之后,长期没有跟进的话,服务成熟度又慢慢的开始下降。

       因此对这些常见的服务成熟度指标进行了自动化的检查,并形成每日的健康度报告,把服务的成熟度监控起来。一旦有某项指标出现异常,我们能够及时处理。这样使我们的服务成熟度保持长期的稳定状态。


总结与展望

       在本文介绍了爱奇艺提出的服务成熟度模型的定义,以可量化的指标来评估服务的成熟度。然后以该模型为标准,对后台服务进行了评估,并总结了各个后台服务普遍得分较低的指标项。针对这些低分项,梳理了优化方案。并建立了自动化的服务质量检测机制,以保证服务成熟度的长期稳定。

       通过可用性优化,多个重要服务的成熟度模型评分都已经提升到了8分以上,并通过自动化的服务质量检测机制,长期监测成熟度的各个指标,一旦发现某些指标异常,及时进行修复改进。

      未来,会在下面两点进行持续的改进和增强:

1.   成熟度模型评分的自动化。避免人工评分的主观因素,同时提升效率。

2.   自动化的服务质量检测机制增加更多的指标。

       希望通过基于服务成熟度模型的优化,使我们的服务可用性得到持续的提升。

也许你还想看

爱奇艺号基于Prometheus的微服务应用监控实践

爱奇艺微服务监控的探索与实践



扫一扫下方二维码,更多精彩内容陪伴你!


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存