您好,欢迎进入开云体育官网!

开云体育咨询热线

您的位置: 主页 > 新闻中心 > 公司资讯 >

开云体育:ShopeePay 自研云原生高可用服务注册中心实践

发布日期:2022-11-06 12:53:17浏览次数:

  在云原生架构下,大部分服务都是以微服务的形式容器化部署,为上游提供服务。服务间的通信依赖服务注册中心,因此对注册中心的可用性提出了很高的要求。

  前者优势在于线性一致性,不同服务可以获取到相同的下游服务信息;后者的优势在于包含了限流熔断等服务治理的套件,可以快速完成接入。

  ShopeePay 早先使用 etcd 作为服务注册中心,在运维过程中,发现 etcd 作为配置中心存在一些问题:

  etcd 设计上强调一致性和分区容忍性,因而牺牲可用性,这与服务注册的核心需求是不一致的,在服务注册与发现场景中,可以容忍数据一段时间内不一致,但需要保证服务高可用;

  etcd 难以水平扩容。随着服务数量越来越多,etcd 即使部署更多节点,默认读写都需要经过 leader 节点,leader 节点会成为瓶颈,无法通过增加节点分散负载;

  etcd 本质上是一个通用的 KV 存储,它实现了很多我们不需要的功能。另外 Raft 协议本身的租约、成员维护等功能也导致系统过于复杂,很容易产生通知事件风暴,出现问题难以定位。

  我们需要一个更简单纯粹的名字服务,因此,我们自研了一套云原生高可用的服务注册中心,结合 sRPC 服务框架,满足服务不间断通信的需求。

  在介绍我们的服务注册中心之前,先来了解一下接下来会涉及到的一些名词术语及其含义。

  Naming Service 在数据一致性与可用性之间选择牺牲强一致,实现高可用与最终一致性。

  在设计之初,Naming Service 就支持云原生部署,能够支持快速扩缩容与容器调度而不影响服务通信。

  Naming Service 和 Naming Client 都会存储服务的路由信息,两者都采用内存存储而非持久化存储。目的是为了满足容器平台的调度。

  一方面,一旦 Naming Service crash,与其连接的 Naming Client 会重新连接到其他节点,不会影响服务间通信。另一方面,使用内存存储可以满足容器平台的调度,不需要与宿主机进行绑定,从而减少宿主机不可用对 Naming Service 的影响。

  Naming Client 同样采用内存缓存,因为 Naming Client 作为业务服务的 SDK 被引入,不能改变原来业务服务的部署方式。而且当业务服务重启之后,重新获取路由信息也是一个合理的逻辑。

  节点对等设计使得扩缩容和重启的处理逻辑都变得简单且一致,是整个可用性设计里面最重要的一点。

  实现高可用最重要的一点就是避免单点。无论其他模块可用性多高,单点故障会导致整个系统不可用。因此我们在设计服务注册中心的时候,从 Client 到 Server,都尽量避免出现单点的情况。

  由于 Naming Service 基于容器部署,并且支持任意扩缩容与容器调度。Naming Client 通过传统固定 IP 方式是不现实的。

  一方面,可以通过监听服务变更事件来更新缓存;另一方面,Naming Client 启动了一个定时器,会定时对缓存进行全量刷新。

  考虑到一个服务访问下游服务数量有限,定时批量刷新不会对 Naming Service 造成太大的负担,这种方式是可以接受的。

  Naming Client 有很多内部配置参数,比如心跳间隔、缓存大小配置等。这些参数可能需要经常进行调整,不能写死在代码中。由于这些配置是内部配置,通过用户配置传入也是不合理的。一个可行的做法是通过配置中心获取,但是这样会引入多一个依赖,增加系统复杂性,并不是最好的做法。

  Naming Service 采用多副本来提升可用性,多副本带来的一个问题是如何实现多个副本间的数据一致性。Naming Service 采用了类似 Gossip 的一致性协议,通过广播心跳的方式,将每个节点的数据同步给其他节点,实现数据最终一致。

  Naming Service 不明确区分续约心跳和注册信息,收到心跳信息后,如果服务实例不存在,则自动触发注册逻辑,如果存在则执行续约。

  统一的处理方式能够简化 Naming Service 设计,无需处理各种异常 case。

  Naming Service 之间会通过通信同步全量的实例信息。传统的做法是,Naming Sevice 节点间同步各自持有的实例数据,接收方更新自己实例数据。这样做有个问题,即需要针对数据进行去重。

  我们采用更简单的方法,Naming Service 节点间相互同步收到实例心跳数据,相当于服务实例连接到所有 Naming Service,这样 Naming Service 统一处理了来自 Replica 的同步请求与服务实例的心跳请求。

  默认情况下,如果在一定时间内没有接收到某个服务实例的心跳,Naming Service 将会移除该实例。

  但是当网络分区故障发生时,微服务与 Naming Service 之间、微服务所在的 Naming Service 与不同网络分区的 Naming Service 之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务。

  每一个 Entry 包含实例 ID、服务名、IP、端口、时间和一些自定义的额外数据。

  当 Naming Service 收到 Naming Client 的注册、下线、心跳请求时,向其余服务器发送心跳广播,此时这两台服务器之间数据会出现短暂的不一致。

  注意:即使消息广播失败,但只要 Naming Service 继续收到 Naming Client 的心跳,仍会再次向所有的 Naming Service(包括失联的 Naming Service)广播心跳任务,因此不必对心跳失败做重试,简化了系统的实现。下线的情况将在下一节展开。

  服务异常下线后,与其相连的 Naming Service 发现该服务超过时间阈值没有心跳,则将该服务状态置为 unhealthy;

  再过一段时间依旧没有心跳,则将其删除,并向其他 Naming Service 推送删除消息,其他 Naming Service 发现该实例已经是 unhealthy 状态,则将其删除,否则不删除(防止由于网络抖动,心跳延迟导致误删)。

  :和 Naming Service 同分区的服务可以正常访问,而不同区的服务因为心跳无法到达,会触发 Naming Service 自我保护。

  :接收其它 Naming Service 发送过来的心跳请求(包含其注册的实例),如果当前实例数据还在 Naming Service 存储中,则更新心跳,如果不存在则执行注册。

  触发自我保护机制后,Naming Service 不再从 Instance Table 中移除由于长时间没收到心跳而应该过期的服务。但它仍然能够接受新服务的注册和查询请求,保证当前节点依然可用,只是新注册的服务实例不会被同步到其他节点上。

  当网络恢复稳定后,当前 Naming Service 新的注册信息会被同步到其他节点中。

  我们在测试环境中部署了一套多节点的 Naming Service 与 Coordinator,并随机启动了一些模拟的 sRPC 服务,持续地重启、注册、下线;并且随机 kill 掉或者重启 Naming Service 节点,通过 Coordinator 全量对账验证数据一致性。

  Coordinator 启动定时任务执行分钟级对账。按照对比数据量大小与准确性,分为以下几类:

  这三个数据的获取不需要对 Instance Table 进行遍历,因此可以频繁进行对比。通过对比这三个数据,可以快速发现不一致的问题。

  但是快速对账只能发现是否有问题,不能发现是哪些服务不一致。因此,我们需要一个更完整的全量数据对账。

  这个流程也可以通过 Web 端人工发起,主要用于当快速对账发现数据不一致时,人工通过 Web 界面定位具体不一致服务。

  因此我们对 Instance Table 进行了优化,采用了分段锁结构,避免对整个 Table 进行加锁。另外实现了 iterator 接口,将加锁的过程分散到每个服务的迭代过程,从而避免快照对实时流程的影响。

  ShopeePay 早期使用 etcd 作为服务注册发现中心。升级过程中,必然存在部分服务注册新 Naming Service,部分服务注册在 etcd 的情况。

  我们采用的是双写注册的方案,在 sRPC 服务框架注册发现模块中,同时持有 Naming Client 和 etcd Client,服务启动后注册到 Naming Service 和 etcd。服务发现时也会从两个服务注册中心获取数据并进行整合。

  当服务基本上在 Naming Service 注册发现之后,可以停止对 etcd 的依赖。

  目前每个 Naming Service 节点都保存了全量实例数据,随着服务数和实例数的增加,数据量逐渐增大,存储和同步的开销也会变大。

  一个更优雅的做法是,将 Naming Service 进行分组,每个组只保存部分分片数据,这样就能很好地满足水平扩展的需求。

  目前 Naming Service 的健康检查只是简单的探活,没有感知服务的负载情况。负载均衡相关的能力由 sRPC 框架提供。但是实际上,Naming Client 可以与 sRPC 框架更好地结合,提供一些负载信息到 sRPC,帮助 sRPC 更合理地选择下游服务节点。


开云体育

Copyright 2005-2016 开云体育官网 版权所有
本站程序界面、源代码受相关法律保护,未经授权,严禁使用
备案号:冀ICP备15028837号网站地图

扫一扫咨询开云体育下载客服