远程服务的多样性带来了对“服务发现”概念的双重理解。首先,我们有“百科全书式”的服务发现,代表技术是UDDI。这种方法提供了从宏观到微观的信息层次,包括提供服务的企业背景(如企业实体、联系方式、分类目录)到具体的服务程序接口细节(例如方法名称、参数、返回值、技术规范)。它覆盖了服务发现过程中的广泛信息需求。
另一端,我们看到了类似于DNS的“门牌号码式”服务发现。这种方式专注于将服务提供者的全限定名转换为实际的主机IP地址,其关注点更加狭窄。这种服务发现不深入到服务的具体提供商或其提供的方法细节,而是假设服务消费者已经掌握这些信息。在这里,服务的定位信息被简化为“全限定名+端口号”的形式。
但随着微服务的逐渐流行,服务的非正常宕机重启和正常的上下线操作变得更加频繁,仅靠着 DNS 服务器和负载均衡器等基础设施,就显得逐渐有些疲于应对,无法跟上服务变动的步伐了。
随着分布式系统的复杂性增加,服务发现成为了一个关键问题,促使人们探索新的解决方案。ZooKeeper,一个分布式键值存储框架,曾是解决服务注册和发现问题的热门工具。它在微服务的早期阶段,特别是在远程服务发现领域,占据了主导地位。然而,由于ZooKeeper是一个底层的分布式工具,它要求开发者投入大量的工作来满足服务发现的具体需求。
2014年,经过Netflix内部长时间的实践考验,Eureka这一专为服务发现设计的工具宣布开源。Eureka的开源不仅标志着其成熟和可靠,而且由于其后被Spring Cloud纳入,成为了Java开发者在远程服务发现方面的首选解决方案,大大减少了服务注册的工作量。
到了2018年,随着Spring Cloud Eureka进入维护模式,Consul和Nacos这两个新兴的服务发现框架迅速崛起,接过了Eureka的接力棒。这些现代服务发现框架的发展已经非常成熟,它们不仅支持通过DNS或HTTP进行地址转换,还提供了包括服务健康检查、集中配置管理、键值存储、跨数据中心数据同步等多种高级功能,标志着应用级服务发现的一个高峰。
进入云原生时代,基础设施的灵活性被极大增强,人们开始重新关注使用基础设施本身来实现服务发现的透明化方式。目前,探索在基础设施和网络协议层面,如何实现对应用几乎无感知且便捷的服务发现,成为了研究的主要方向。
服务发现要解决注册、维护和发现三大功能问题
服务发现流程中包含三个关键环节:服务注册、服务维护、和服务发现,每个环节对于维护系统的高效运转都至关重要。
服务注册
服务注册是服务发现机制的第一步。当服务启动时,它需要以某种方式(例如,通过API调用、事件消息发布、在ZooKeeper或Etcd中记录位置、或将信息存入数据库等)将其坐标信息注册到服务注册中心。这一过程既可以由应用程序自身通过特定的注解或配置完成(如Spring Cloud的@EnableDiscoveryClient注解),也可以由容器管理框架(例如Kubernetes)自动处理。
服务维护
服务维护确保服务列表的准确性和更新性。尽管许多服务发现框架提供了服务下线的机制,但并非所有服务都能保证优雅地下线,可能因为故障或网络问题突然中断。因此,服务发现框架需要通过各种手段(如HTTP、TCP协议、长连接、心跳检测、探针或进程状态监测等)自动监控服务的健康状况,并将不健康的服务自动下线,以避免向消费者提供无法访问的服务地址。
服务发现
服务发现是指消费者查询并获得服务实际坐标信息的过程。这通常通过HTTP API请求或DNS查找操作完成,允许消费者通过服务标识(如Eureka的ServiceID、Nacos的服务名或通用的FQDN)找到服务的具体地址。尽管这里着重讨论的是核心功能,许多服务发现框架还提供了一系列附加功能,如负载均衡、流量管理、键值存储、元数据管理和业务分组等,进一步增强了服务发现的能力和灵活性。
为什么服务发现对 CAP 如此关注、如此敏感呢?
在概念模型中,服务中心所处的地位是这样的:提供者在服务发现中注册、续约和下线自己的真实坐标,消费者根据某种符号从服务发现中获取到真实坐标,它们都可以看作是系统中平等的微服务。我们来看看这个概念模型示意图:
服务发现在系统中的作用不同于其他服务,因为它是所有服务相互通信的基础。就像配置中心一样,服务发现也变得非常关键,因为几乎所有服务都依赖它。如果服务发现出问题,整个系统都会受影响。因此,确保服务发现的稳定和可靠是非常重要的,需要在技术上做好准备,以防任何可能的问题。
所以,在分布式系统中,服务注册中心一般会以内部小集群的方式进行部署,提供三个或者五个节点(通常最多七个,一般也不会更多了,否则日志复制的开销太高)来保证高可用性。你可以看看下面给出的这个例子:
我拿前面提到的最有代表性的 Eureka 和 Consul 来举个例子。
在分布式系统设计中,对于数据一致性和系统可用性的取舍常常是一个核心讨论点,归结为CAP理论中的两种选择:一致性(Consistency)与可用性(Availability),同时还要考虑分区容错性(Partition tolerance)。Consul和Eureka这两个服务发现工具就很好地体现了不同的设计选择。
Consul的CP取向
Consul采用Raft协议,这是一个强调一致性的协议。在Consul的设计中,只有当多数节点确认写入操作后,服务的注册或变更才会被确认。这种方式确保了任何时候从集群外部读取的服务发现信息都是一致的,即便这可能会牺牲一些可用性——在极端情况下,如果达不到多数节点的确认,服务更新操作可能会被阻塞。Consul通过这种设计,优先保证了系统的一致性(Consistency)和分区容错性(Partition tolerance),属于CAP理论中的CP模型。
Eureka的AP取向
与Consul不同,Eureka的设计更倾向于保证系统的可用性(Availability)。它的节点间通过异步复制来交换服务注册信息,这意味着服务注册或变动可以立即在当前节点上反映出来,而不需要等待这些信息被复制到其他节点。这种设计虽然提高了系统的响应速度和可用性,使得即使在部分节点不可用的情况下,服务注册和发现仍然可以继续工作,但它牺牲了一致性——不同节点间的信息可能会出现暂时的不一致。Eureka通过这种方式,优先保证了系统的可用性(Availability)和分区容错性(Partition tolerance),符合CAP理论中的AP模型。
微服务架构中的一个重要设计原则是“通过服务来实现独立自治的组件”(Componentization via Services),微服务强调通过“服务”(Service)而不是“类库”(Library)来构建组件,这是因为两者具有很大的差别:类库是在编译期静态链接到程序中的,通过本地调用来提供功能;而服务是进程外组件,通过远程调用来提供功能。