开源的服务发现

时间:2021-06-01 18:19:55

原文http://jasonwilder.com/blog/2014/02/04/service-discovery-in-the-cloud/

服务发现是大部分分布式系统和面向服务架构的核心组件。最初问题看起来很简单:客户如何决定服务的IP地址和端口,这些服务已存在于多个服务器上的。

通常,你开始一些静态的配置,这些配置离你需要做的还挺远的。当你开始布署越来越多的服务时,事情会越来越复杂。在一个上线的系统中,由于自动的或人为的规模变化,服务的位置会经常的变化,例如布署新的服务,服务器宕机或者被替换。

在这些应用场景中为了避免服务冲突,动态的服务注册和发现会越来越重要。

这一问题被以多种方式涉及,还将继续扩展。我们将要分析一些开源的或者开放讨论此问题的解决方案,从而理解它们是如何工作的。我们会关注这些解决方案的优势和劣势,在一致性存储、运行时依赖、客户集成选项和这些特征的利弊权衡等。

我们从一些强一致性的项目开始如Zookeeper,Doozer和Etcd,它们做为一致性服务的典型,也可以用做服务注册。

随后我们将分析一些用于服务注册和发现的有趣的解决方案。包括:Airbnb的SmartStack,Netflix的Eureka,Bitly的NSQ,Spotify和DNS,最后是SkyDNS.

定位服务的问题划分为两类。服务注册与服务发现。

  • 服务注册 - 服务进程在注册中心注册自己的位置。它通常注册自己的主机和端口号,有时还有身份验证信息,协议,版本号,以及运行环境的详细资料。

  • 服务发现 - 客户端应用进程向注册中心发起查询,来获取服务的位置。

任何服务注册、服务发现也有其它开发、操作层面的考虑:

  • 监控 - 如果服务注册失败会发生什么?有时会因为超时、或者其它进程而突然处于未注册状态。通常会要求服务实现心跳机制来确保其活跃性,并且通常要求客户端有能力可靠地处理失效的服务。

  • 负载均衡 -如果多个服务被注册,怎样来处理所有的客户端跨服务的均衡问题?如果有个主服务,它能被客户端正确的判断吗?

  • 集成风格 - 注册中心仅仅提供了少量语言的绑定,例如仅仅支持 Java 吗?集成需要嵌入注册与发现的代码到程应用程序中,还是可以选择一个辅助进程?

  • 运行时依赖 - 它需要 JVM, Ruby 或者其它与你的运行环境不兼容的软件吗?

  • 可用性考虑 - 丢失一个节点能继续工作吗?升级时不会中断服务吗?注册处会成为架构的中心部分,会变成单点故障吗?

我们已经看过许多通用,一致的注册(Zookeeper, Doozer, Etcd)以及许多自定义构建,最终一致的(SmartStack,Eureka, NSQ,Serf, Spotify’s DNS, SkyDNS)。

许多使用嵌入式客户端库(Eureka,NSQ,等. .)和一些使用单独的助手程序(SmartStack, Serf)。

有趣的是,一些专用的解决方案,与一致性相比,他们更喜欢采用了一个可用性的设计。

Name Type Availabilty vs Consistency Language Dependencies Integration
Zookeeper General Consistency Java JVM Client Binding
Doozer General Consistency Go Client Binding
Etcd General Mixed (1) Go Client Binding/HTTP
SmartStack Dedicated Availabilty Ruby haproxy/Zookeeper Sidekick (nerve/synapse)
Eureka Dedicated Availabilty Java JVM JavaClient
NSQ (lookupd) Dedicated Availabilty Go Client Binding
Serf Dedicated Availabilty Go Local CLI
Spotify (DNS) Dedicated Availabilty N/A Bind DNS Library
SkyDNS Dedicated Mixed (2) Go HTTP/DNS Library

(1)如果使用一致的参数,有可能会读取不一致

(2)如果使用一个在SkyDNS前面的缓存DNS客户,读起来可能会不一致

我个人更加关注基于JVM的Eureka 和 Zookeeper。
Spring Cloud Netflix有一套基于spring boot 和spring cloud的实现

Netflix的Eureka

Eureka是Netflix的中间层,用于负载均衡和服务发现。在应用服务中既存有服务器组件,也有智能客户端。服务器和客户端都采用java语言编写,这就意味着理想的应用场景是用于采用java编写的服务,或者是与JVM兼容的语言编写的服务。

Eureka服务器用于注册服务。他们推荐在AWS每个可用的区域运行一个Eureka服务器,通过它来形成聚簇。服务器通过异步模式互相复制各自的状态,这意味着在任意给定的时间点每个实例关于所有服务的状态是有细微差别的。

服务的注册由客户端组件处理。服务嵌入在客户端应用程序代码中。在运行时,客户端注册服务并周期性的发送心跳来更新它的租约。

服务的发现也由智能客户端来处理。它从服务器端检索当前注册的信息并把它们缓存在本地。客户端周期性能刷新它的状态同时处理负载均衡和失效备援。

Eureka在设计时就考虑了失败时的恢复。它依托强一致性提供良好的可用性,可在运行在多种不同的失败场景中。如果聚簇中有分片,那么Eureka就转入自我保护模式。它允许在分片期间过行服务的发现和注册,当分片结束时,聚簇中的成员会把它们的状态再次合并起来。

Zookeeper

Zookeeper是一个集中的服务,它用于维护配置信息、命名、提供分布式的同步和提供分组服务。它是采用java语言编写的,强同步(CP)的,且使用Zab协议来进行跨聚簇的变更协调。

Zookeeper通常运行在有着三、五或七个成员的聚簇中。客户端使用特定的绑定来访问聚簇。访问通常嵌入到客户端应用或者服务中。

服务注册通过命名空间下的短结点来实现。短节点只有当客户端连接时存在,典型的场景是,一个后端服务在启动后,它带有地址信息,注册服务自己。当服务失败或者断开连接,结点会从树上消失。

服务的发现是通过列示和查看服务的命名空间来实现的。客户端收到所有正在注册的服务,同时发现一个服务不再可用或者又有新的服务在注册。客户端同样需要处理负载均衡或者自身的失效备援。

Zookeeper的接口使用起来有些困难,语言绑定有细微的差别导致了这一问题。如果你在正使用基于JVM的语言,Curator服务发现扩展或许会有些用。

因为Zookeeper是一个CP系统,当分片发生时,你的系统就不能注册或者查找已注册的服务,即便在分片期间它的功能是可用的。特别是在不一致时,读和写操作都将返回错误。