目录

注册中心

CAP 理论

常见注册中心

Zookeeper

Eureka

Nacos

CAP 理论对比

Eureka

组成部分

Eureka Server(服务端)

Eureka Client(客户端)

快速上手

搭建 Eureka Server

服务注册

服务发现


在上一篇文章  Spring Cloud 概述-CSDN博客 示例中,进行远程调用时,我们的 URL 是写死的:

String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();

当我们需要更换或新增机器时,URL 也会随之发生变化,此时就需要修改对应的服务的 URL,并重新部署项目,非常繁琐

那么,我们如何解决这个问题呢?

注册中心

我们可以通过 注册中心 来帮我们完成 URL 管理:

当 服务方 启动或信息变更时,主动通知注册中心,注册中心记录相关信息

当 调用方 需要调用时,先去注册中心查询服务方对应信息,再进行调用

从上图可看出,注册中心主要涉及到三个角色

服务提供者(Service Provider):启动时向注册中心注册自己的元数据(服务名、IP、端口、健康状态、权重等),并定期发送心跳(或健康检查)维持注册状态,当下线时主动注销(或因超时被剔除)

服务消费者(Service Consumer):从注册中心拉取或订阅目标服务的实例列表,并基于负载均衡策略(如轮询、随机、一致性哈希)选择实例发起调用,为避免每次调用都查询注册中心,可缓存服务列表并监听变更

注册中心(Registry):存储和管理服务实例的元数据(服务名→实例列表的映射),维护服务健康状态(通过心跳或主动探测)以及 通知消费者服务变更(如实例上线/下线)

而它们之间的关系,可以通过以下两个概念来描述:

服务注册(Service Registration):服务提供者实例启动时,将自己的网络地址(如IP、端口)、服务名称、健康状态等信息记录到注册中心

服务发现(Service Discovery):服务消费者通过查询注册中心,获取可用服务实例列表,并基于负载均衡策略(如轮询、权重)选择实例发起调用

接下来,我们来学习一个重要的理论 —— CAP 理论

CAP 理论

CAP 理论是分布式系统设计的核心原则,用于描述分布式系统中三个不可兼得的特性一致性(Consistency)可用性(Availability)分区容错性(Partition Tolerance)

C( Consistency,一致性):所有节点在同一时间看到的数据完全一致(强一致性)

A(Availability,可用性):每个请求都能得到响应(不保证数据最新),系统始终可用。也就是说即使部分节点故障,服务仍可响应

P(Partition Tolerance,分区容错性):系统在网络分区(节点间通信中断)时仍能继续运行

但是,一个分布式系统不可能同时满足 数据一致性服务可用性 分区一致性 这三个基本需求,最多只能同时满足其中两个

那为什么最多只能同时满足其中两个呢?任意两个都可以满足吗?

在分布式系统中,系统之间的网络不可能 100% 保证健康(即,可能会出现网络分区

而当网络分区发生,且保证分区容错(P满足)的情况下,系统只能在 C 和 A 中二选一

选择 C(一致性):系统检测到节点间网络中断,为保证数据一致性,拒绝写入或读取(如返回超时错误,此时系统对外不可用,不能满足 A),例如  Zookeeper 在 Leader 节点故障时,选举新 Leader 期间不可写入(满足 CP)

选择 A(可用性): 系统继续响应请求,但不同分区可能返回不一致的数据(如部分节点未同步最新写入,部分数据不同步,不能满足 C),例如 Eureka 在节点间网络中断时,各节点仍可提供服务,但服务列表可能不一致(满足 AP)

因此,CAP 理论中最多只能同时满足两个特性的核心原因在于:在网络分区(P)不可避免的分布式环境下,强一致性(C)和高可用性(A)在本质上存在冲突

而 CAP 的“三选二”本质是分布式系统在网络不可靠条件下的必然妥协:

选择 CP:适合对数据准确性要求高的场景(如银行转账)

选择 AP:适合对可用性要求高的场景(如电商商品展示)

CA 无意义:分布式系统必须面对网络分区(P)

接下来,我们来看常见的注册中心

常见注册中心

Zookeeper

Zookeeper 核心功能包括分布式协调服务,提供注册中心、分布式锁、配置管理等功能,基于 ZAB(Zookeeper Atomic Broadcast)协议,类似 Paxos

特点:

强一致性:所有节点数据实时同步,读写请求由 Leader 统一处理

高可靠性:适合金融、支付等对数据准确性要求高的场景

基于 CP优先保证强一致性,牺牲部分可用性:

Leader 选举期间(约 200ms~几秒)服务不可用。

写入性能较低(需多数节点确认)

适用场景:

1. Kafka、Hadoop、Dubbo 等分布式系统的元数据管

2. 需要强一致性的服务注册与发现(如分布式锁)

Eureka

Eureka 主要用于 服务注册与发现,Spring Cloud 默认集成

特点:

高可用性

      1. 节点间通过异步复制数据,容忍网络分区导致的数据不一致

       2. 客户端缓存服务列表,即使注册中心宕机仍可本地调用

简单轻量:无复杂依赖,适合中小规模集群

缺点:

数据最终一致性,可能读到旧数据

2.x 版本已停止维护

适用场景:

对一致性要求不高、需要快速响应的服务发现(如电商商品服务)

Nacos

Nacos 的核心功能包括 服务注册发现 + 动态配置管理,且支持 AP/CP 模式切换

特点:

灵活性

    1. AP 模式:基于自研 Distro 协议,高可用优先(类似 Eureka)

    2. CP 模式:基于 Raft 协议,强一致性优先(类似 Zookeeper)

高性能:支持百万级服务实例注册

生态集成:完美兼容 Spring Cloud、Kubernetes、Dubbo

适用场景:

1. 云原生微服务(如 Kubernetes + Spring Cloud Alibaba)

2. 需要同时满足服务发现和配置管理的场景

CAP 理论对比

注册中心CAP一致性协议可用性表现适用场景
ZookeeperCPZAB网络分区时拒绝写入强一致性需求(如金融系统)
EurekaAP无(异步复制)网络分区仍响应,数据可能旧高可用优先(如互联网应用)
NacosAP/CPDistro/Raft根据模式动态调整灵活场景(云原生、混合部署)

接下来,我们就继续来学习 Eureka 的使用

Eureka

Eureka 作为 Netflix 开源的 服务注册与发现组件,其架构主要由 两大部分 构成:Eureka Server(服务端)和 Eureka Client(客户端)

组成部分

Eureka Server(服务端)

Eureka Server:作为注册中心,存储和管理服务实例信息

核心功能:

服务注册中心:接收并管理所有微服务实例的注册信息(如服务名、IP、端口、健康状态)

服务列表维护:通过心跳机制检测实例存活状态,自动剔除故障节点

服务信息同步:在集群模式下,多个 Eureka Server 节点之间通过异步复制共享服务注册表

Eureka Client(客户端)

Eureka Client:负责处理服务注册、服务发现和心跳保活

核心功能:

服务注册:微服务启动时,向 Eureka Server 注册自身信息

服务发现:从 Eureka Server 拉取其他服务的实例列表,并缓存到本地

心跳保活:定期(默认30秒)向 Eureka Server 发送心跳,证明自身存活

负载均衡:结合 Ribbon 实现客户端负载均衡(如轮询、随机策略)

Eureka Client Eureka Server 核心交互流程:

服务启动:Client 向 Server 发送注册请求(POST /eureka/apps/{serviceId})。

心跳维持:Client 定期发送心跳(PUT /eureka/apps/{serviceId}/{instanceId})。

服务发现:Client 从 Server 拉取注册表(GET /eureka/apps),缓存到本地。

故障剔除:Server 检测到心跳超时(默认90秒)后,将实例标记为下线(DELETE /eureka/apps/{serviceId}/{instanceId})。

接下来,我们就来学习如何快速使用 Eureka

快速上手

我们还是以之前的 订单服务和商品服务 作为示例来进行学习,要想在其中使用 Eureka,需要完成以下三步:

1. 搭建 Eureka Server

2. 将 order-service 和 product-service 都注册到 Eureka Server

3. order-service 在进行远程调用时,从  Eureka Server 获取 product-service 服务列表,再进行交互

搭建 Eureka Server

Eureka Server 是一个独立的微服务,因此我们需要先创建 eureka-server 子模块:

在 pom 文件中引入 eureka-server 依赖 和 项目构建插件:

    
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-server
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

完善启动类,并在启动类上添加 @EnableEurekaServer 注解,开启 Eureka 注册中心服务

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

完善配置文件:

server:
  port: 10010
spring:
  application:
    name: eureka-server
eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: false # 表示是否从Eureka Server获取注册信息,默认为true.因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为false
    register-with-eureka: false # 表示是否将自己注册到Eureka Server,默认为true.由于当前应用就是Eureka Server,故而设置为false.
    service-url:
      # 设置Eureka Server的地址,查询服务和注册服务都需要依赖这个地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
logging:
  pattern:
    console: '%d{MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'

启动 eureka-server,访问 Eurekahttp://127.0.0.1:10010/

可以看到 Eureka 启动成功

接下来,我们将 product-service 注册到 eureka-server

服务注册

 product-service 中引入 eureka-client 依赖:

        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        

application.yml 中添加服务名称和 eureka 地址

spring:
  application:
    name: product-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka/

启动 product-server,刷新注册中心:

可以看到 product-server 已经注册到 eureka 上了

服务发现

接下来我们继续修改 order-service,先将 order-service 注册到 eureka-server 中:

同样引入依赖:

 在 product-service 中引入 eureka-client 依赖:

        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        

application.yml 中添加服务名称和 eureka 地址

spring:
  application:
    name: order-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka/

修改远程调用代码:

@Slf4j
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;
    public OrderInfo findOrderInfoById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        // 根据应用名称获取服务列表
        List instances = discoveryClient.getInstances("product-service");
        log.info("获取 product-service 实例列表:", instances.toString());
        EurekaServiceInstance instance = (EurekaServiceInstance)instances.get(0);
        log.info("选择实例: " + instance.getInstanceId());
        // 拼接 URL
        String url = instance.getUri() + "/product/" + orderInfo.getProductId();
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

通过 DiscoveryClient 根据 服务名称 获取服务列表,由于现在只有 1 个 product-server,因此我们直接获取列表中的第一个 product-server

启动 order-server,刷新注册中心:

可以看到 order-service 也成功注册到 eureka 上

访问接口 127.0.0.1:8080/order/1

远程调用成功

在本篇文章中,我们对于服务注册和服务发现,以及 Eureka 有了基本的了解

在之后的文章中,我们再来进一步学习 Eureka 具体是如何实现服务注册和服务发现等核心功能的