查看原文
其他

爱奇艺基于SpringCloud的韧性能力建设

The following article is from 爱奇艺技术产品团队 Author 海外后端团队

国际站后端业务不断扩展,支撑的服务实例规模也越来越大。并且在此过程中,支持了双云及多地部署。

这也给服务治理带来了挑战,如何应对同城多机房路由、多地容灾等场景,并解决微服务优雅上下线等问题,是国际站业务拓展亟需解决的课题。

本文将从设计、开发、实践各个维度,叙述爱奇艺解决上述问题的思路和实践。


01

SpringCloud客户端路由


客户端就近路由


什么是就近路由?在业务多机房部署场景中,内部服务如果存在大量的跨机房、甚至跨地域的网络调用,则请求时延会显著加大,会直接影响到服务质量,甚至是用户体验。

但是在一般的微服务架构中,路由策略并不支持这种机房或者地区的多级别判断。所以实现自定义的客户端负载均衡路由变得迫在眉睫。

在常规单机房部署的时候,如下图中的dc1中,consumer只会请求到provider1,整个链路并没有什么问题。但是当需要多机房多区域部署的时候,瓶颈就出现了。假设有下图的简单部署场景:



  1. provider分别部署在同zone的dc1、同region的dc2和不同region的dc3

  2. 各自注册到所在dc的注册中心


可以预想到会有以下几个问题:


  1. 如果consumer能从注册中心获取到所有provider列表,那么它会轮询请求,这样正常情况下就会跨机房访问

  2. 如果consumer不能从注册中心获取到provider2和provider3,那么在容灾情况下,provider1挂了,不能故障转移到provider1和provider2


这里就有了智能路由的概念,也就是就近路由,如何满足需求呢?需要做到以下几点:


  1. 各consumer能获取到其他dc的实例列表,也就是注册中心需要支持多dc

  2. 正常情况下,consumer的流量只会请求到同dc的provider1(通道1),而不会跨机房访问

  3. 当同dc的provider出现不可用情况下,会首先降级到不同dc但是同region的provider2(通道2),如果provider2也不可用,才会降级到不同region的provider3(通道3)

 

在讨论实现之前,先同步一下我们后面要用到的idc、zone及region的概念。


  • idc、zone及region


这里先给出 AWS 的 Region 和 AZ 示意图,如下:




  • AZ – Availability Zone 内部保证<1ms,一个机房或多个机房组成

  • Region 内部之间保证2-5ms时延,多个AZ组成

  • Region 之间通常20-100ms,取决于物理距离


  • SpringCloud现有能力


  • Netflix Ribbon


Ribbon是一个为客户端提供负载均衡功能的服务,它内部提供了一个叫做ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、获取所有的服务器列表、获取可用的服务器列表等等。


以下就是Ribbon提供的负载均衡规则列表:



可以看到,Ribbon是提供了基于zone的ZoneAvoidanceRule,它可以根据zone进行服务选择。但是如果有region等概念,它就没办法处理了。


  • Spring Cloud Loadbalancer


Spring Cloud在新版本中,逐渐抛弃Netflix的内容,比如ribbon。也确实是ribbon已经停止更新很久了。重新推出的Spring Cloud LoadBalancer只有简单的轮询和随机路由策略。在新的版本中,也在新增按照时间权重等等策略。


它主要是支持了响应式的服务选择,像ribbon的服务选择还是同步的,这与Spring Cloud 在倡导的响应式趋势不符。


可以看到新版本的Load Balancer支持的路由功能还很初级,也并不支持微服务高可用方方案下的智能就近路由。


  • 自定义扩展能力


经过对Spring Cloud现有能力的调研和期望需求的评估,决定对Spring Cloud进行自定义扩展,支持就近路由的功能,并期望在短期内服务公司内部开源,未来贡献给开源社区。


经过和架构组同学的讨论和之前dubbo就近路由的扩展经验,设计了以下功能改动:




可以看到,大体分为以下几步:


  1. provider注册时,调用服务获取自身所在的zone和region,并且向注册中心注册时携带zone和region信息

  2. consumer启动时也调用服务获取自身所在的zone和region

  3. consumer在拉取实例时,首先筛选同zone的实例

  4. 如果同zone实例中健康比例大于50%,则进行负载均衡策略选择一台实例

  5. 如果同zone中健康比例小于50%,则降级到同region中进行判断,逻辑同上

  6. 最后如果还未筛选出足够数量的实例,则降级到返回所有实例。然后进行负载均衡策略选择


经过路由策略改造后,客户端负载均衡具备了就近路由的功能,基本具备容灾降级的能力。


  • 国际站落地案例


国际站在爱奇艺海外机房接入自定义扩展的spring-cloud-iqiyi后。把服务进行部署演练大致情况如下:



  • 期望结果


  1. 正常三个dc都健康时,流量是通道1
  2. 当dc1的服务被摘除,流量是通道2
  3. 当dc1和dc2的服务都被摘除,流量是通道3

  • 演练步骤


  1. 使用Hoxton.SR11-iqiyi-0.1.1版本的spring-cloud-iqiyi
  2. 针对JAVA类型应用,对容器进行杀死演练


  • 结果展示



从演练结果看,符合预期。进行了常规情况下就近路由,异常情况下的智能路由


02

SpringCloud优雅上下线


在部署和实践SpringCloud服务过程中,发现在服务部署过程中,总有接口超时或者接口5xx的情况。分析后发现原因有以下两点:


  1. 新启动的实例没有进行预热或者预热没有执行完,流量就进入,导致接口请求超时
  2. kill的实例,在退出后,还有consumer的流量进入,导致出现接口5xx

其实这是微服务架构中的常见问题,即如何进行预热以及优雅上下线。具体应该如何处理?

假如有这样的简单的微服务架构:



在微服务架构体系中,理想的优雅上下线过程应该是像下面这样:



provider1就是对应的优雅下线,provider2就是对应的优雅上线。而且顺序不能颠倒。


在Spring Cloud的体系中,使用consul、ribbon等组件下,总结下优雅上下线就是:



SpringCloud优雅上线


  • 自定义扩展能力


针对SpringCloud现有架构,我们在SpringBoot启动过程中,改变之前服务注册的时机,延迟注册并保证服务预热。



通过禁用SpringBoot原生在WebServerIntializedEvent事件监听器中实现的自动注册功能,改为在ApplicationReadyEvent事件监听器中实现自定义的自动注册,实现了延迟注册和执行自定义预热逻辑的能力。


当然自定义预热逻辑可以由业务代码控制,可以根据实际项目中的需求,进行本地缓存预热、长连接预热、连接池预热等。


同步执行完预热后,再进行服务注册,注册完成后才会收到consumer请求,避免由于冷启动造成的慢请求。


SpringCloud优雅下线


  • 自定义扩展能力


针对SpringCloud现有架构,我们在SpringBoot退出过程中,增加自定义逻辑,保证服务下线过程中严格按照上面的流程。



具体如上图所示,在ContextClosedEvent事件中,拦截处理。首先执行解注册,这个时候注册中心已经没有当前provider。


然后等待一段时间(可配置),直到consumer的serverList更新(ribbon默认是30s),再继续执行退出流程。


Spring Boot优雅退出


上面我们介绍了微服务架构中的优雅上下线,但是在服务本身,也存在优雅停机的问题。


什么叫优雅停机?简单说就是,在对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响。应用接收到停止指令之后的步骤应该是,停止接收访问请求,等待已经接收到的请求处理完成,并能成功返回,这时才真正停止应用。


这种完美的应用停止方式如何实现呢?Java语言本身是支持优雅停机的,当我们使用kill PID的方式结束一个Java应用的时候,JVM会收到一个停止信号,然后执行shutdownHook的线程。


  • Spring Boot现有能力


SpringBoot 2.3.0开始提供了官方的优雅停机方案,那我们首先来看下需要怎么使用呢?首先需要在配置文件中配置优雅停机,如下:

server: shutdown: graceful ## 开启优雅停机spring: lifecycle:    timeout-per-shutdown-phase: 5s    ## 优雅停机等待时间,默认30s

而在Spring Boot 2.3以前,是没有官方方案的,需要自己实现shutdownhook,具体参考官方的issue。大体步骤就是判断是否为tomcat的线程,如果是则等待线程状态完成再关闭。


所以,一般建议直接升级到Spring Boot 2.3,使用新特性。


03

成果


经过一系列的自定义扩展,SpringCloud已完善大多比较重要的功能,基于现有扩展功能,国际站完成部署两地三中心架构:



后端服务整体稳定性得到大大提升,并且具备很强的容灾能力。


还有一些比如标签路由、灰度部署等扩展功能,也亟待开发解决。未来,我们也计划将这些扩展开源贡献给SpringCloud社区,共同进步!


参考阅读:



技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。


高可用架构
改变互联网的构建方式

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

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