SmartStack – 来自 Airbnb 的服务发现框架

介绍

“服务发现”是在微服务(micro-service)深入人心后引申出的一个需求。在“微服务”的设定中,原来一个巨大的服务裂变成许多小微服务,服务之间通过 HTTP 或者其他协议通信。当小微服务的数量越来越大时,问题就出现了。比如:如何知道一个服务的 IP 地址和端口是啥?如果有多个 IP 地址,哪个 IP 地址是可用的?下线了一个服务怎么办?

于是 “服务发现” 被开发出来,用于解决这类问题。其中 Airbnb 公布了自己的方案:smartstack,原文请点击
在这篇介绍文章中,Airbnb 详细描述了对 “服务发现” 的需求:

  • 一个服务的后端实例会自动收到请求。
  • 一个服务出现问题后,请求就会自动地停止发送到这个服务上。
  • 负载应该均匀的分发到所有后端实例上,不会出现服务饿死或者累死的情况。
  • 有办法将一个后端实例从服务中移走,并且对服务整体没有影响。
  • 服务发现非常的自省,因此我们能知道哪些后端服务是可用的,哪些负载分发到这些服务上,负载来自哪里。
  • 后端实例的变动对服务的消费者影响应该非常小。理想地,这类变动对消费者来说应该是透明的。
  • 整个系统不能有单点问题,任何一个实例故障不会影响整个系统。

然后文章列举了现存的服务发现方案:DNS、中心化的负载均衡器、应用程序自身基于 Zookeeper 的注册/发现。这些方案适合某一些场景,但是并不能完全满足上述 Airbnb 的所有需求,所以 Airbnb 自己研究了一个 smartstack 方案。

架构

原文中没有架构图,TT 按照文字描述画出了这个图,按照图片解释 smartstack 中的各个组件。

smartstack

橙色两个服务:NerveSynapse 是 smartstack 的核心组件,使用 ruby 语言实现。这两个服务和后端应用部署在同一个机器上上,其中 Nerve 负责服务的注册,而 Synapse 负责服务的发现。

红色的 API 是应用程序,既微服务的实例,它对外提供 HTTP 接口。API 程序如果要使用外部服务,全部通过本地的 Haproxy,并不直接连接到其他服务。

Zookeeper 保存着全局的微服务信息,Synapse 从中获取微服务的信息生成 Haproxy 的配置文件。

Nerve

Nerve 在 Zookeeper 中注册一个 ephemeral node,存放着这个后端应用的具体 IP 和端口。另外,Nerve 会检查后端应用的健康状况,如果任何一个检查失败,那么 Nerve 会解注册这个后端应用。检查规则(check list)配置在 Nerve 的配置文件中,支持的检查方法也很多样,比如可以判断 TCP 或者 HTTP 是否正常工作;如果是其他非 HTTP 的应用,他们也会实现其他探测逻辑,比如 redis,就先设置一个 key,然后获取 key;对于 rabbitmq,就先生产一个消息,然后消费这个消息。

Synapse

Synapse 负责服务发现,和 Nerve 一样, 也是安装在后端应用的机器上。它会从 Zookeeper 中获取可用的后端应用信息,生成一份 haproxy 的配置文件,而本地的后端应用如果要调用其他服务,全部都通过本地的 Haproxy 进行访问。

这里是 smartstack 中最让人眼前一亮的地方,他们充分肯定了 haproxy,同时为了避免全局 haproxy 带来的单点问题,在每一个节点上都部署了 haproxy,实现了去中心化的负载均衡。
这样,他们享受了 haproxy 带来的所有稳定又丰富的功能,比如后端服务健康检查,流量会自动跳过那些失效的节点,还有各种负载均衡算法、排队机制、重试机制、超时机制,还有 Haproxy 强大的自省服务,可以从 Haproxy 中看到各个后端的健康情况,对各个后端的负载等。

这里需要强调的是,虽然 Nerve 负责了后端应用的健康检查,Haproxy 仍然要对后端再一次做检查,为什么?Haproxy 能检查出 Nerve 检查不到的问题,比如网络问题导致无法访问后端应用,只有 Haproxy 能发现。

这种去中心化的负载均衡方案还有一个好处:负载均衡器这个服务的服务发现不用做了,因为只要用本地的 haproxy 就行,比如全部写成 localhost。

有收获必须有付出,这个方案的付出就是每个节点都要维护自己的 haproxy,这个艰巨的重担由 Synapse 承担。乍一看每一次服务变动 Synapse 都可以重新生成一遍 Haproxy 的配置文件,然后 reload haproxy。但是 Airbnb 的工程师们使用了更优雅的办法,使用 Haproxy 的 stats socket 接口,当后端实例不可用时,通过这个接口标记成 maintenance 模式,当实例恢复后,在标记成正常。所以只有在增加新服务的时候才重启整个 Haproxy 进程。

最终效果

回头看 Airbnb 列出的需求,Smartstack 非常好的满足了需求:

  • 新的服务上线,Nerve 自动注册到 Zookeeper,Synapse 从 Zookeeper 中获取信息并自动配置 Haproxy,新的服务就可以自动被使用。
  • 在 smartstack 中,服务故障会自动被 Nerve 发现,然后从 Zookeeper 中删除。如果日常运维,管理员可以直接将 Nerve 服务关闭,其他节点会从 Zookeeper 中发现这个变动,当运维工作结束时再将 Nerve 开启。
  • 虽然 Haproxy 内置了很多调度功能,但是因为是去中心化的,所以无法做全局的调度。对于非常重要的服务,Airbnb 在 Haproxy 中做了智能的队列机制,对于一般服务,用了 Haproxy 的轮询调度策略。
  • 因为 Synapse 根据 Zookeeper 的信息做出响应,并且通过 Haproxy unix socket 增减后端非常非常之快,所以对整体服务的影响很小。
  • 做 debugging 或者日常维护也非常的简单,直接将 Nerve 服务关掉就行了。
  • 从 Haproxy 的 status 页面中可以看到非常详细的状态信息,并且还可以分析 Haproxy 的日志来获取各种指标。
  • 整个基础设施架构是完全分布式的,其中最重要的信息都放在 Zookeeper 中,同时 Zookeeper 又是分布式且高可用的。

Smartstack 在 Airbnb 的生产环境中跑了一年(文章是 2013 年发表的),现在看跑了好多年了,并且他们表示对这个系统爱得不要不要的。

PS:

从整体上看 smartstack 使用的都是现存的,经受过历史考验的工具和服务,这也是为什么 Airbnb 能够快速在生产环境中使用的原因之一,关键在于如何大面积的部署和配置这些组件:Zookeeper,Haproxy,Nerve,Synapse。TT 认为这很大程度依靠配置管理工具 Chef 在 Airbnb 的全面实施,只有通过配置管理工具才能在部署新服务时自动部署对应的 Nerve 组件,并且配置与之对应的检查规则。如果靠人来运维这样的系统,肯定会是另外一场灾难。另外,Zookeeper 的服务发现怎么做?

和 Consul 相比,Smartstack 最可爱的地方是对应用程序没有任何侵入性,最多配置文件里的 API 地址改改。相反,如果使用 Consul 的 API 接口,应用程序是必须修改的!如果用 DNS 接口,又丢失了 Haproxy 负载均衡的功能,并且 DNS 在 linux 上 nscd 是有缓存的,这又扯到 DNS 做服务发现的弊端

参考文献

  • http://siddontang.com/2015/08/03/smartstack-introduction/
  • http://nerds.airbnb.com/smartstack-service-discovery-cloud/

发表评论

电子邮件地址不会被公开。 必填项已用*标注