目录
[CAP 理论](#CAP 理论)
[CAP 理论对比](#CAP 理论对比)
[Eureka Server(服务端)](#Eureka Server(服务端))
[Eureka Client(客户端)](#Eureka Client(客户端))
[搭建 Eureka Server](#搭建 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~几秒)服务不可用。
写入性能较低(需多数节点确认)
适用场景:
Kafka、Hadoop、Dubbo 等分布式系统的元数据管
需要强一致性的服务注册与发现(如分布式锁)
Eureka
Eureka 主要用于服务注册与发现,Spring Cloud 默认集成
特点:
高可用性:
节点间通过异步复制数据,容忍网络分区导致的数据不一致
客户端缓存服务列表,即使注册中心宕机仍可本地调用
简单轻量:无复杂依赖,适合中小规模集群
缺点:
数据最终一致性,可能读到旧数据
2.x 版本已停止维护
适用场景:
对一致性要求不高、需要快速响应的服务发现(如电商商品服务)
Nacos
Nacos 的核心功能包括 服务注册发现 + 动态配置管理 ,且支持 AP/CP 模式切换
特点:
灵活性:
AP 模式:基于自研 Distro 协议,高可用优先(类似 Eureka)
CP 模式:基于 Raft 协议,强一致性优先(类似 Zookeeper)
高性能:支持百万级服务实例注册
生态集成:完美兼容 Spring Cloud、Kubernetes、Dubbo
适用场景:
云原生微服务(如 Kubernetes + Spring Cloud Alibaba)
需要同时满足服务发现和配置管理的场景
CAP 理论对比
| 注册中心 | CAP | 一致性协议 | 可用性表现 | 适用场景 |
|---|---|---|---|---|
| Zookeeper | CP | ZAB | 网络分区时拒绝写入 | 强一致性需求(如金融系统) |
| Eureka | AP | 无(异步复制) | 网络分区仍响应,数据可能旧 | 高可用优先(如互联网应用) |
| Nacos | AP/CP | Distro/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,需要完成以下三步:
搭建 Eureka Server
将 order-service 和 product-service 都注册到 Eureka Server
order-service 在进行远程调用时,从 Eureka Server 获取 product-service 服务列表,再进行交互
搭建 Eureka Server
Eureka Server 是一个独立的微服务,因此我们需要先创建eureka-server子模块:

在 pom 文件中引入eureka-server 依赖和 项目构建插件:
XML
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
完善启动类,并在启动类 上添加 @EnableEurekaServer 注解,开启 Eureka 注册中心服务:
java
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
完善配置文件:
java
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 ,访问 Eureka
http://127.0.0.1:10010/

可以看到 Eureka 启动成功
接下来,我们将product-service 注册到eureka-server 中
服务注册
在product-service 中引入eureka-client依赖:
XML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在application.yml 中添加服务名称和 eureka 地址:
java
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依赖:
XML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在application.yml 中添加服务名称和 eureka 地址:
java
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka/
修改远程调用代码:
java
@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<ServiceInstance> 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 具体是如何实现服务注册和服务发现等核心功能的