复杂系统如何在不停机升级同时保持稳定?你必须考虑如下几个点...

简介: 千门万户闲鱼日,总把新桃换旧符算法

做者:闲鱼技术-兰林数据库

背景

在互联网行业,线上服务的升级更新可谓屡见不鲜。据统计,在过去的一个季度中闲鱼工程师们执行了千余次发布,总计更新的代码数量超过百万行。缓存

这些发布中,有一些可能只更新了几行代码,而有一些可能执行了整个集群的迁移升级。而不管这些变动的影响面有多大,咱们都必须保证线上服务的可用性,用户无感知。本文将以闲鱼搜索服务的迁移升级为例,向你们介绍其背后的技术方案。安全

闲鱼搜索服务基本架构

闲鱼的底层搜索服务由查询规划服务 Search Planner、查询理解服务 Query Planner、打分排序服务 Rank Service 以及搜索引擎 Heaven Ask 3 所组成。它们之间的相互调用关系以下图所示:服务器

闲鱼搜索架构

能够看到,整个搜索服务是由多个相互独立的微服务所构成的。不一样的微服务之间相互隔离,经过预先向外暴露的接口提供服务。全部的微服务最终经过 Search Planner 收口,对外提供统1、完整的搜索能力。架构

在底层搜索服务之上,还有业务逻辑层和接入网关层,具体架构在此再也不赘述。用户的搜索请求先经过网关层转发给逻辑层处理,再向底层搜索服务发起搜索请求。这条请求链上包含数十个集群,调用深度达到两位数,整个过程当中提供服务的服务器数量可能有成百上千。负载均衡

对于这样一个复杂的系统,升级过程显然没法一蹴而就。好消息是各个微服务之间合理的解耦合给升级工做带来了很大的便利,有效避免牵一发动全身而致使无从下手,使咱们能够分门别类地处理升级问题。框架

  • 注1:Search Planner 是一个基于函数式、服务化、可视化、并行化开发框架所构建的搜索服务网关层。
  • 注2:Query Planner 的主要做用是理解用户输入,而后对搜索词进行算法优化。最终得到更好的搜索召回结果。
  • 注3:Rank Service 是实时打分排序服务,它的做用是根据多维度的特征对搜素引擎召回的海选结果进行算法打分。分数越高的商品就越有机会出如今搜索结果的前列。
  • 注4:Heaven Ask 3 (问天3)是阿里巴巴研发的一款稳定高效、功能强大的搜索引擎。为阿里集团包括淘宝、天猫在内的核心业务提供搜索服务支持。

保持兼容

开始升级以前,咱们首先须要确认被升级的服务是否保持了向前与向后兼容性。保持兼容不只减小了工做量,也减小了升级所致使的故障风险。分布式

为了尽可能避免升级致使的不兼容,咱们能够总结一些开发原则:函数

  • 远程过程调用(RPC)须要可以忽略未知参数,而且容许缺失参数。
  • 若是须要删除已有参数,须要与全部依赖方确认。能够先将参数标记为 Deprecated 而不是直接移除。
  • 使用参数时,区分缺省值和缺失值。
  • 若是接口没法保持兼容,则建立新接口代替旧接口。不要破坏旧接口的兼容性。

在升级时,先升级那些没有外部依赖的服务。等到被依赖方升级完毕以后,再去升级依赖方。肯定了每个服务的升级顺序以后,咱们再根据服务的实际状况肯定升级方案。

无状态服务升级

正式进入升级流程,咱们首先关注搜索链路中的被设计成无状态服务的部分,例如用于处理业务逻辑的 Java 微服务、用于处理查询逻辑的 Search Planner 等。它们的共同特色是,每一个请求处理完毕以后,关于该次请求的资源即被释放。不一样的请求之间没有相互依赖和时序要求。同一个无状态服务内不一样的机器节点是彻底等价的。

无状态服务的特色使得它们很容易经过水平扩展来动态扩缩容。所以在保证兼容的前提下,它们的升级流程相对通用而且简单:

  1. 根据服务最小可用度决定分批数。
  2. 选取一批待更新的容器,中止服务。
  3. 批量升级容器、更新镜像。
  4. 等待这一批容器所有恢复服务后,继续更新下一批容器。

无状态服务升级

通常来讲咱们能够经过把状态存储在消息队列、缓存、数据库或者其它外部中间件中来达成服务的无状态。把服务设计成无状态的好处显而易见:升级时不须要分配额外的机器资源,升级速度快,变动代价小,于是能够支持频繁的迭代更新。可是,这种设计也给状态访问和更新带来了额外的开销,在某些性能敏感的场合多是不适用的。

有状态服务升级

咱们继续关注有状态的部分。有状态服务升级的麻烦之处在于,状态的存储、恢复、转移每每由服务根据实际状况单独设计(或者根本没有设计),于是升级较为困难。咱们能够简单列举一些相对通用的有状态服务升级可选方案。

  • 接入层网关提供热更新的能力(例如 Nginx),把状态的保持隔离在接入层内部。适合须要长时间保持状态的场景。
  • 渐进更新,新请求逐步切换到新服务上处理,旧服务处理完存量请求后销毁。适合短期保持状态的场景(例如游戏服务、实时音视频通信服务)。
  • 建立全新的服务副本,经过数据双写保持新旧服务状态一致,逐步用新服务取代旧服务。

在闲鱼搜索的架构中,搜索引擎自己提供的虽然是无状态服务,可是引擎内部保存了用于处理索引分区,增量进度的各类状态。最终使用的升级方案以下:

  1. 使用新版本镜像建立一个彻底独立的新引擎。
  2. 新旧引擎全量数据同步。
  3. 增量数据同时向新旧引擎发送。
  4. 新引擎上线,逐步扩大承接流量的比例。
  5. 旧引擎再也不承接流量后下线。

搜索引擎升级

和无状态服务的升级相比,这种方式不只额外使用了一倍的机器资源,并且每次升级都须要作一次复杂而繁琐的服务配置。若是服务自己不是无状态的,还须要自行编码实现切流逻辑,保证同一个用户的请求可以落到同一个集群上。总体升级成本较为昂贵,只适合更新频率很是低的服务。若是服务的更新频率较高,则应该根据服务的实际状况设计实现升级成本更低的方案。

服务发现

在升级过程当中,服务发现机制承担着重要做用。它为咱们提供了如下功能:

  • 保证分布式一致性
  • 服务优雅上下线
  • 负载均衡
  • 流量调控与请求降级
  • 同机房优先调度
  • 跨机房容灾调度

服务发现

服务发现是流量调控的总阀门。一个成熟稳定的服务发现机制不只能够有效避免发布致使的请求成功率抖动,也为发生异常时快速回滚止血提供了保证。

风险防控

对搜索链路的每个集群按照依赖顺序进行服务升级、挂载、切流无疑是高危操做,稍有不慎就可能引发线上故障。所以,咱们按照阿里巴巴安全生产三板斧原则对升级流程进行了梳理:

  • 可监控
    重要链路的重要指标均提早保证监控覆盖。例如请求总量,请求成功率,请求响应时长等等。确保重大问题能够经过监控指标及时发现。
  • 可灰度
    任何变动都不容许未经灰度直接全量发布到线上。对于无状态服务,咱们通常经过调整服务发现中的权重或者调整机器比例来完成灰度放量。对于部分不能随机灰度的情形,咱们设计了按用户分批放量的机制。
  • 可回滚
    变动系统提供了通用的一键回滚能力,但并不是是最快的方式。在不少状况下,咱们在执行变动前就作好了把待更新的机器或集群在服务发现上从新挂载或移除的准备,从问题发现到恢复的时间基本是秒级的。

总结

综上所述,复杂系统不停机升级的原则和流程能够归纳以下:

  1. 服务间解耦与隔离,确保单次升级的范围和影响可控。
  2. 根据兼容性和依赖关系决定服务的升级顺序。
  3. 根据服务是否无状态决定升级方式。
  4. 提早准备好监控和回滚方案,灰度升级。

闲鱼搜索服务升级的整个执行过程经历了两个月的时间。这其中咱们既保证了用户无感知,线上服务稳定运行,也保证了与咱们合做开发的算法团队以及其余工程团队的正常开发不受影响。

在实际执行的过程当中,咱们还遇到了不少细节上的问题。例如建立新服务时未能提早合理预估预算需求,致使升级过程当中不断挪借预算,拆东墙补西墙。又好比异地多活部署带来的延迟问题迫使服务保持单元化,给升级过程当中的流量调控工做带来了不少挑战。这些暴露的问题也为咱们继续完善架构和方案提供了指引。

但愿本次的分享可以给你们带来一些帮助和启发。