Open-Source Service Discovery
Service discovery is a key component of most distributed systems and service oriented architectures. The problem seems simple at first: How do clients determine the IP and port for a service that exist on multiple hosts?
Usually, you start off with some static configuration which gets you pretty far. Things get more complicated as you start deploying more services. With a live system, service locations can change quite frequently due to auto or manual scaling, new deployments of services, as well as hosts failing or being replaced.
Dynamic service registration and discovery becomes much more important in these scenarios in order to avoid service interruption.
This problem has been addressed in many different ways and is continuing to evolve. We’re going to look at some open-source or openly-discussed solutions to this problem to understand how they work. Specifically, we’ll look at how each solution uses strong or weakly consistent storage, runtime dependencies, client integration options and what the tradeoffs of those features might be.
We’ll start with some strongly consistent projects such as Zookeeper, Doozer and Etcd which are typically used as coordination services but are also used for service registries as well.
We’ll then look at some interesting solutions specifically designed for service registration and discovery. We’ll examine Airbnb’s SmartStack, Netflix’s Eureka, Bitly’s NSQ, Serf, Spotify and DNS and finally SkyDNS.
The Problem
There are two sides to the problem of locating services. Service Registration and Service Discovery.
- Service Registration - The process of a service registering its location in a central registry. It usually register its host and port and sometimes authentication credentials, protocols, versions numbers, and/or environment details.
- Service Discovery - The process of a client application querying the central registry to learn of the location of services.
Any service registration and discovery solution also has other development and operational aspects to consider:
- Monitoring - What happens when a registered service fails? Sometimes it is unregistered immediately, after a timeout, or by another process. Services are usually required to implement a heartbeating mechanism to ensure liveness and clients typically need to be able to handle failed services reliably.
- Load Balancing - If multiple services are registered, how do all the clients balance the load across the services? If there is a master, can it be deteremined by a client correctly?
- Integration Style - Does the registry only provide a few language bindings, for example, only Java? Does integrating require embedding registration and discovery code into your application or is a sidekick process an option?
- Runtime Dependencies - Does it require the JVM, Ruby or something that is not compatible with your environment?
- Availability Concerns - Can you lose a node and still function? Can it be upgraded without incurring an outage? The registry will grow to be a central part of your architecture and could be a single point of failure.
General Purpose Registries
These first three registries use strongly consistent protocols and are actually general purpose, consistent datastores. Although we’re looking at them as service registries, they are typically used for coordination services to aid in leader election or centralized locking with a distributed set of clients.
Zookeeper
Zookeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. It’s written in Java, is strongly consistent (CP) and uses the Zab protocol to coordinate changes across the ensemble (cluster).
Zookeeper is typically run with three, five or seven members in the ensemble. Clients use language specific bindings in order to access the ensemble. Access is typically embedded into the client applications and services.
Service registration is implemented with ephemeral nodes under a namespace. Ephemeral nodes only exist while the client is connected so typically a backend service registers itself, after startup, with its location information. If it fails or disconnects, the node disappears from the tree.
Service discovery is implemented by listing and watching the namespace for the service. Clients receive all the currently registered services as well as notifications when a service becomes unavailable or new ones register. Clients also need to handle any load balancing or failover themselves.
The Zookeeper API can be difficult to use properly and language bindings might have subtle differences that could cause problems. If you’re using a JVM based language, the Curator Service Discovery Extension might be of some use.
Since Zookeeper is a CP system, when a partition occurs, some of your system will not be able to register or find existing registrations even if they could function properly during the partition. Specifically, on any non-quorum side, reads and writes will return an error.
Doozer
Doozer is a consistent, distributed data store. It’s written in Go, is strongly consistent and uses Paxos to maintain consensus. The project has been around for a number of years but has stagnated for a while and now has close to 160 forks. Unfortunately, this makes it difficult to know what the actual state of the project is and whether is is suitable for production use.
Doozer is typically run with three, five or seven nodes in the cluster. Clients use language specific bindings to access the cluster and, similar to Zookeeper, integration is embedded into the client and services.
Service registration is not as straightforward as with Zookeeper because Doozer does not have any concept of ephemeral nodes. A service can register itself under a path but if the service becomes unavailable, it won’t be removed automatically.
There are a number of ways to address this issue. One option might be to add a timestamp and heartbeating mechanism to the registration process and handle expired entries during the discovery process or with another cleanup processes.
Service discovery is similar to Zookeeper in that you can list all the entries under a path and then wait for any changes to that path. If you use a timestamp and heartbeat during registration, you would ignore or delete any expired entries during discovery.
Like Zookeeper, Doozer is also a CP system and has the same consequences when a partition occurs.
Etcd
Etcd is a highly-available, key-value store for shared configuration and service discovery. Etcd was inspired by Zookeeper and Doozer. It’s written in Go, uses Raft for consensus and has a HTTP+JSON based API.
Etcd, similar to Doozer and Zookeeper, is usually run with three, five or seven nodes in the cluster. Clients use a language specific binding or implement one using an HTTP client.
Service registration relies on using a key TTL along with heartbeating from the service to ensure the key remains available. If a services fails to update the key’s TTL, Etcd will expire it. If a service becomes unavailable, clients will need to handle the connection failure and try another service instance.
Service discovery involves listing the keys under a directory and then waiting for changes on the directory. Since the API is HTTP based, the client application keeps a long-polling connection open with the Etcd cluster.
Since Etcd uses Raft, it should be a strongly-consistent system. Raft requires a leader to be elected and all client requests are handled by the leader. However, Etcd also seems to support reads from non-leaders using this undocumented consistent parameter which would improve availabilty in the read case. Writes would still need to be handled by the leader during a partition and could fail.
Single Purpose Registries
These next few registration services and approaches are specifically tailored to service registration and discovery. Most have come about from actual production use cases while others are interesting and different approaches to the problem. Whereas Zookeeper, Doozer and Etcd could also be used for distributed coordination, these solutions don’t have that capability.
Airbnb’s SmartStack
Airbnb’s SmartStack is a combination of two custom tools, Nerve and Synapse that leverage haproxy and Zookeeper to handle service registration and discovery. Both Nerve and Synapse are written in Ruby.
Nerve is a sidekick style process that runs as a separate process alongside the application service. Nerve is reponsible for registering services in Zookeeper. Applications expose a /health endpoint, for HTTP services, that Nerve continuously monitors. Provided the service is available, it will be registered in Zookeper.
The sidekick model eliminates the need for a service to interact with Zookeeper. It simply needs a monitoring endpoint in order to be registered. This makes it much easier to support services in different languages where robust Zookeeper binding might not exist. This also provides many of benefits of the Hollywood principle.
Synapse is also a sidekick style process that runs as a separate process alongside the service. Synapse is responsible for service discovery. It does this by querying Zookeeper for currently registered services and reconfigures a locally running haproxy instance. Any clients on the host that need to access another service always accesses the local haproxy instance which will route the request to an available service.
Synapse’s design simplifies service implementations in that they do not need to implement any client side load balancing or failover and they do not need to depend on Zookeepr or its language bindings.
Since SmartStack relies on Zookeeper, some registrations and discovery may fail during a partition. They point out that Zookeepr is their “Achilles heel” in this setup. Provided a service has been able to discover the other services, at least once, before a partition, it should still have a snapshot of the services after the partition and may be able to continue operating during the partition. This aspect improves the availability and reliability of the overall system.
Update: If you’re intested in a SmartStack style solution for docker containers, check out docker service discovery.
Netflix’s Eureka
Eureka is Netflix’s middle-tier, load balancing and discovery service. There is a server component as well as a smart-client that is used within application services. The server and client are written in Java which means the ideal use case would be for the services to also be imlemented in Java or another JVM compatible language.
The Eureka server is the registry for services. They recommend running one Eureka server in each availability zone in AWS to form a cluster. The servers replicate their state to each other through an asynchronous model which means each instance may have a slightly, different picture of all the services at any given time.
Service registration is handled by the client component. Services embed the client in their application code. At runtime, the client registers the service and periodically sends heartbeats to renew its leases.
Service discovery is handled by the smart-client as well. It retrieves the current registrations from the server and caches them locally. The client periodically refreshes its state and also handles load balancing and failovers.
Eureka was designed to be very resilient during failures. It favors availabilty over strong consistency and can operate under a number of different failure modes. If there is a partition within the cluster, Eureka transitions to a self-preservation state. It will allow services to be discovered and registered during a partition and when it heals, the members will merge their state again.
Bitly’s NSQ lookupd
NSQ is a realtime, distributed messaging platform. It’s written in Go and provides an HTTP based API. While it’s not a general purpose service registration and discovery tool, they have implemented a novel model of service discovery in theirnsqlookupd agent in order for clients to find nsqd instances at runtime.
In an NSQ deployment, the nsqd instances are essentially the service. These are the message stores. nsqlookupd is the service registry. Clients connect directly to nsqd instances but since these may change at runtime, clients can discover the available instances by querying nsqlookupd instances.
For service registration, each nsqd instance periodically sends a heartbeat of its state to each nsqlookupd instance. Their state includes their address and any queues or topics they have.
For discovery, clients query each nsqlookupd instance and merge the results.
What is interesting about this model is that the nsqlookupd instances do not know about each other. It’s the responsibility of the clients to merge the state returned from each stand-alone nsqlookupd instance to determine the overal state. Because each nsqd instance heartbeats its state, each nsqlookupd eventually has the same information provided each nsqd instance can contact all available nsqlookupd instances.
All the previously discussed registry components all form a cluster and use strong or weakly consistent consensus protocols to maintain their state. The NSQ design is inherently weakly consistent but very tolerant to partitions.
Serf
Serf is a decentralized solution for service discovery and orchestration. It is also written in Go and is unique in that uses a gossip based protocol, SWIM for membership, failure detection and custom event propogation. SWIM was designed to address the unscalability of traditional heart-beating protocols.
Serf consists of a single binary that is installed on all hosts. It can be run as an agent, where it joins or creates a cluster, or as a client where it can discover the members in the cluster.
For service registration, a serf agent is run that joins an existing cluster. The agent is started with custom tags that can identify the hosts role, env, ip, ports, etc. Once joined to the cluster, other members will be able to see this host and it’s metadata.
For discovery, serf is run with the members command which returns the current members of the cluster. Using the members output, you can discover all the hosts for a service based on the tags their agent is running.
Serf is a relatively new project and is evolving quickly. It is the only project in this post that does not have a central registry architectural style which makes it unique. Since it uses a asynchronous, gossip based protocol, it is inherently weakly-consistent but more fault tolerant and available.
Spotify and DNS
Spotify described their use of DNS for service discovery in their post In praise of “boring” technology. Instead of using a newer, less mature technology they opted to build on top of DNS. Spotify views DNS as a “distributed, replicated database tailored for read-heavy loads.”
Spotify uses the relatively unknown SRV record which is intended for service discovery. SRV records can be thought of as a more generalized MX record. They allow you to define a service name, protocol, TTL, priority, weight, port and target host. Basically, everything a client would need to find all available services and load balance against them if necessary.
Service registration is complicated and fairly static in their setup since they manage all zone files under source control. Discovery uses a number of different DNS client librarires and custom tools. They also run DNS caches on their services to minimize load on the root DNS server.
They mention at the end of their post that this model has worked well for them but they are starting to outgrow it and are investigating Zookeeper to support both static and dynamic registration.
SkyDNS
SkyDNS is a relatively new project that is written in Go, uses RAFT for consensus and also provides a client API over HTTP and DNS. It has some similarities to Etcd and Spotify’s DNS model and actually uses the same RAFT implementation as Etcd, go-raft.
SkyDNS servers are clustered together, and using the RAFT protocol, elect a leader. The SkyDNS servers expose different endpoints for registration and discovery.
For service registration, services use an HTTP based API to create an entry with a TTL. Services must heartbeat their state periodically. SkyDNS also uses SRV records but extends them to also support service version, environment, and region.
For discovery, clients use DNS and retrieve the SRV records for the services they need to contact. Clients need to implement any load balancing or failover and will likely cache and refresh service location data periodically.
Unlike Spotify’s use of DNS, SkyDNS does support dynamic service registration and is able to do this without depending on another external service such as Zookeeper or Etcd.
If you are using docker, skydock might be worth checking out to integrate your containers with SkyDNS automatically.
Overall, this is an interesting mix of old (DNS) and new (Go, RAFT) technology and will be interesting to see how the project evolves.
Summary
We’ve looked at a number of general purpose, strongly consistent registries (Zookeeper, Doozer, Etcd) as well as many custom built, eventually consistent ones (SmartStack, Eureka, NSQ, Serf, Spotify’s DNS, SkyDNS).
Many use embedded client libraries (Eureka, NSQ, etc..) and some use separate sidekick processes (SmartStack, Serf).
Interestingly, of the dedicated solutions, all of them have adopted a design that prefers availability over consistency.
| Name | Type | AP or CP | Language | Dependencies | Integration |
|---|---|---|---|---|---|
| Zookeeper | General | CP | Java | JVM | Client Binding |
| Doozer | General | CP | Go | Client Binding | |
| Etcd | General | Mixed (1) | Go | Client Binding/HTTP | |
| SmartStack | Dedicated | AP | Ruby | haproxy/Zookeeper | Sidekick (nerve/synapse) |
| Eureka | Dedicated | AP | Java | JVM | Java Client |
| NSQ (lookupd) | Dedicated | AP | Go | Client Binding | |
| Serf | Dedicated | AP | Go | Local CLI | |
| Spotify (DNS) | Dedicated | AP | N/A | Bind | DNS Library |
| SkyDNS | Dedicated | Mixed (2) | Go | HTTP/DNS Library |
(1) If using the consistent parameter, inconsistent reads are possible
(2) If using a caching DNS client in front of SkyDNS, reads could be inconsistent
http://jasonwilder.com/blog/2014/02/04/service-discovery-in-the-cloud/
Open-Source Service Discovery的更多相关文章
- 【转帖】Service Discovery: 6 questions to 4 experts
https://highops.com/insights/service-discovery-6-questions-to-4-experts/ What’s Service Discovery? I ...
- Service Discovery with Apache Curator
Curator的介绍 Curator就是Zookeeper的一个客户端工具(不知道Zookeeper的同学可以到http://www.ibm.com/developerworks/cn/opensou ...
- Android WiFiDirect 学习(二)——Service Discovery
Service Discovery 简介 在Android WifiDirect学习(一 )中,简单介绍了如何使用WifiDirect进行搜索——连接——传输. 这样会有一个问题,那就是你会搜索到到附 ...
- [译]Ocelot - Service Discovery
原文 你可以指定一个service discovery provider,ocelot将使用它来找下游的host和port. Consul 下面的配置要放在GlobalConfiguration中.如 ...
- Service Discovery And Health Checks In ASP.NET Core With Consul
在这篇文章中,我们将快速了解一下服务发现是什么,使用Consul在ASP.NET Core MVC框架中,并结合DnsClient.NET实现基于Dns的客户端服务发现 这篇文章的所有源代码都可以在G ...
- ZooKeeper之service discovery
Zookeeper整体介绍 ZooKeeper is a centralized service for maintaining configuration information, naming, ...
- Service discovery
https://www.cnblogs.com/dirt2/p/5987067.html Use Assigned Numbers in the Service Discovery Protocol ...
- Service Discovery in WCF 4.0 – Part 2 z
Service Discovery in WCF 4.0 – Part 2 In the previous post I discussed about the basic usage of WCF ...
- Service Discovery in WCF 4.0 – Part 1 z
Service Discovery in WCF 4.0 – Part 1 When designing a service oriented architecture (SOA) system, t ...
随机推荐
- linux打包压缩常用命令
打包: zip gzip bzip2 tar xz //rar zip 包 zip xxx.zip test.c 压缩 unzip xxx.zip ...
- winform编程设定listview选中行
在做项目中,需要用到listview显示数据.同时,项目要求,通过检索用户输入的数据,程序通过搜索,确定数据所在的行并通过程序设定为选中状态并高亮显示.同时,正常响应鼠标单击响应的效果,单击时,程序设 ...
- C语言可变参实现参数累加返回
C语言可变参的作用真的是非常大,自从发表了可变参如何实现printf,fprintf,sprintf的文章以来,便有不少博友私信问我实现的机制,我也解释了相关的知识点.今天,我们借着这个机会,再来举一 ...
- OpenCV——PS图层混合算法(六)
具体的算法原理可以参考: PS图层混合算法之六(差值,溶解, 排除) // PS_Algorithm.h #ifndef PS_ALGORITHM_H_INCLUDED #define PS_ALGO ...
- C#基础随手笔记之基础操作优化
对数据的查询,删除等基本操作是任何编程语言都会涉及到的基础,因此,研究了一下C#中比较常用的数据操作类型,并顺手做个笔记. List查询时,若是处理比较大的数据则使用HashSet<T>类 ...
- java基础语法(一)
java基础语法(一) 1.类是一种抽象的概念,对象是类的一种具体表示形式,是具体的概念.先有类,然后由类来生成 对象(Object).对象又叫做实例(Instance). 2.类由两大部分构成:属性 ...
- Linux的chkconfig命令详解
chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息.谨记chkconfig不是立即自动禁止或激活一个服务,它只是简单的改变了符号连接. 使用语法: chkconfig [--a ...
- jjava Date格式是 May 07 17:44:06 CST 2018,怎么插入数据库中的timestamp格式中
首先 我来记录下错误 死在时间格式转换错误手里了 大致就是时间格式转化失败 java代码中的May 07 17:44:06 CST 2018 是这个格式转换为 数据库的 yyyy-MM-dd HH: ...
- jQuery的学习笔记
JQuery学习笔记 Chapter one初识jQuery 1.2测试jQuery 在jQuery库中,$是jQuery的别名,如:$()相当于jQuery() 注意:在使用JQuery进行开发的时 ...
- mysql-入门
创建数据库 Create database 数据库名字 [库选项]; 创建数据库 create database mydatas charset utf8; 查看数据库 show databases; ...