服务发现的原理 Service Discovery:以 SmartStack 为例

SmartStack 是由 Airbnb 内部开发的 Service discovery 框架。这篇原文写于2013年,刨根溯源式的讲解了 service discovery 的由来。

英文原文

很多公司的项目,一开始都是 monolitic app。随着业务的成长,网站开始使用 SOA 或者 microservices 。这样不同团队可以维护不同的代码,而不会在同一个 code repo 里面产生冲突。

使用 SOA 后会遇到以下一些问题:

  • 在一个 service 里面,如何决定一个request 应该分配给哪台 server 呢?
  • 在一个 service 里面,如何添加或者移除一台 server 呢?
  • 一台 server 不工作了,对整个 service 会产生多大的影响?

一个解决方案就是,能通过自动化的办法来解决以上问题。这个解决方案需要做到:

  • 一个 server 起来后,它立刻就能够接收所属 service 的请求
  • 能够为整个 service 做 load balancing
  • server 出问题时,能自动停止分配 request 给这台 server
  • 移除一台 server 不会影响整个 service 的正常工作。这样还能提高 debugibility
  • 能提供 observibility,保证工程师能够看到 service 的工作情况, server 的情况
  • 改变 service 的配置时,需要的人工干涉能够尽量少,比如添加一个 service ,添加一台 server
  • 不要有 single point of failure

不符合要求的解决方案

有两个方案看似能够解决问题,实际上是不行的。我们先来看看它们的问题。

DNS

把服务按照 subdomain 来划分。新添加一台 server 时,直接增加一个 DNS record。这样 request 到来时,DNS 可以随机分配 request 到其下的 server。

这个方案的主要问题是:

  1. DNS 的解析是会被 client 缓存住的。如果 server 发生改变, client 需要刷新缓存,否则还是可能把 request 发送到已经 down 了的 server 上。这增加了 service 的不确定性。而其实,在整个架构的各个环节,只要有 component 缓存了这个 DNS 的解析,都会出问题,而且很难 debug
  2. 另外,DNS 这种随机分配的方式还不同于 round robin。它的 load balancing 方面不保证把 request 发给相对空闲的 server。

Load Balancer

如果使用全局的 load balancer,也就是说, services 之间的请求,都要通过 load balancer 来进行路由。每个 service 只需要保证能联络 load balancer 就行了。如何做到这一点呢?你可能会想到通过 DNS。但这样并没解决问题。如果 load balancer down了,你还是需要通过配置 DNS 来路由到另一台 load balancer。而 DNS 的问题(比如随机分配)在上面已经讲过了。

在 app 内做 Service 注册和发现

一个流行的解决方案,就是把这个 service 注册和发现的逻辑写进 app 的代码里面。在 Airbnb ,从前在一些 java app 里写了 service 注册逻辑。使用 Zookeeper 来管理所有 service。Zookeeper 拥有一份后端所有 services 的列表。service 间歇性的向 zookeeper 请求这份列表,来获得其他 services 的信息。

这个方案也有些问题。Zookeeper 对非 JVM 语言的支持并不好。对那些语言,需要自己写 zookeeper client。有时候甚至需要支持其他团队的项目,也就是说不是你本人写的,你只是负责配置。这也会带来麻烦。

另一种情况是,如果这段代码出现问题,因为代码只在 app 启动时运行,如果要 debug ,就必定重启 app。这加大了debug 的难度。

SmartStack

smartstack

Airbnb 开发的这个 SmartStack,把这个 service discovery 的问题和 app 本身完全独立出来。它由两个部分组成: Nerve 用来做 service registration,Synapse 用来做 service discovery。这两个部分变成两个 app 和主 app 部署在一起。

Nerve

Nerve 负责把自己 app 的信息发给 Zookeeper。这与 java zookeeper client 很相似。

而在注册自己之前, Nerve 有一个 health checklist 需要检测。只有当所有 health check 都通过,才能注册自己。这也就要求,所有 service 都实现了被 health check 的功能。

Synapse

Synapse 实现 service discovery 的办法是,通过从 Zookeeper 获取其他 services 的信息,在本地配置并运行了一个 HAProxy。由这个HAProxy 来负责 routing request 这件事情。

这其实就是把一个 centralised load balancer 变成了 decentralised。app 的 load balancer 之间在 localhost 运行了,所以也不存在找不到的情况。Synapse 其实只是一个中间进程,和 HAProxy独立。如果 Synapse down 了, app 不能获得 services 信息的更新,但从前的信息还在 HAProxy。通过这种工作方式,还实现了强大的 logging 功能,复杂的 routing 算法,队列算法, retry 机制, timeout 机制。

当其他 service 信息发生改变的时候,Synapse 会重新生成 HAProxy 配置文件,并重启 HAProxy。其他 service down 的时候, Synapse 会把对应的 HAProxy 的 stats socket 变成维护模式。

使用 SmartStack 的好处

  • Zookeeper 会间歇性对 service 做 health check。一旦 service 可用,Zookeeper 会通知其他 server 的 Synapse
  • 如果 server 出现问题,在下一次 health check 时会被 Zookeeper 发现
  • Synapse 接收到 Zookeeper 的更新后会重新生成 HAProxy 配置文件,然后重新读入配置文件,而不需要重启 HAProxy。因此这个过程非常快
  • 关闭 Nerve 即可从集群移除一台 server
  • 通过 HAProxy 的管理页面可以看到各台 server 的状态,observibility
  • 架构是基于 Zookeeper 搭建的,是 robust 分布式系统

存在的问题

在目前这个架构中,如果 Zookeeper 集群出问题,那么整个网站会不可用。

微信公众号:网站架构札记

最近发布