服务注册与发现模式:微服务的“通讯录”

说实话,服务注册与发现这个模式,我刚开始接触微服务时觉得它挺“虚”的。不就是把服务地址记下来吗?后来真正上手做项目,才发现这东西是微服务架构的基石。没有它,你的服务之间就像一群没有电话簿的人,只能靠喊。

今天咱们就来聊聊这个模式。我会结合我自己的踩坑经历,把注册中心、服务提供者、服务消费者这三者的关系讲透,再对比一下 Eureka 和 Nacos 这两个主流实现。

为什么需要服务注册与发现?

想象一下,你有一个订单服务,它需要调用用户服务。在单体应用时代,这很简单——直接 new 一个对象或者调一个本地方法就行。但到了微服务时代,用户服务可能部署了 3 个实例,IP 分别是 192.168.1.10、192.168.1.11、192.168.1.12,端口也不一样。

更麻烦的是,这些实例随时可能扩容、缩容、重启。你总不能把 IP 硬编码在代码里吧?

这就是服务注册与发现要解决的问题。说白了,它就是一个动态的“通讯录”。服务启动时自己登记上去,服务下线时自动擦掉。消费者只需要问注册中心:“我要找用户服务,给我一个可用的地址。” 注册中心就会返回一个健康的实例。

核心角色有三个:

  • 服务提供者(Provider):启动时向注册中心注册自己的地址,定期发送心跳证明自己还活着。
  • 服务消费者(Consumer):从注册中心获取服务提供者的地址列表,然后发起调用。
  • 注册中心(Registry):存储服务地址信息,监控服务健康状态,通知消费者地址变化。

一张图看懂三者关系

我习惯用一张图来理解这个模式。下面这个 SVG 图展示了服务注册与发现的核心流程,你可以看到服务提供者如何注册、续约,消费者如何发现和调用。

注册中心 Eureka / Nacos 服务提供者 订单服务实例 服务消费者 用户服务调用方 ① 注册 / 续约 ② 获取服务列表 ③ 远程调用 (RPC/HTTP) 心跳续约 (30s) 下线通知 注册/续约 发现/通知 远程调用 心跳

Eureka 与 Nacos:两代注册中心的对比

我在项目中先后用过 Eureka 和 Nacos。说实话,Eureka 是 Netflix 的经典作品,但现在已经进入维护模式了。Nacos 是阿里开源的,功能更全面。下面这个表格是我个人的使用感受对比。

特性 Eureka Nacos
CAP 理论 AP(可用性 + 分区容错) AP 或 CP 可切换
健康检查 客户端心跳(默认30s) 心跳 + 主动探活(支持TCP/HTTP)
配置管理 不支持 支持(配置中心 + 注册中心二合一)
自我保护 有(防止网络抖动误杀) 有(但更精细)
控制台 简陋 功能丰富,可视化强
社区活跃度 维护模式 活跃

我的建议:新项目直接上 Nacos。Eureka 虽然稳定,但功能太单一。Nacos 把注册中心和配置中心合在一起,少维护一个组件,省心不少。

代码实战:用 Spring Cloud 实现服务注册与发现

光说不练假把式。下面我写一个最简单的例子,用 Spring Cloud + Nacos 演示服务提供者和消费者。

1. 搭建 Nacos 注册中心

Nacos 本身是一个 Java 应用,下载后直接启动:

# 下载 Nacos 2.x 版本
# 启动(单机模式)
sh startup.sh -m standalone

启动后访问 http://localhost:8848/nacos,默认账号密码都是 nacos。

2. 服务提供者(Provider)

创建一个 Spring Boot 项目,引入依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置文件 application.yml:

server:
  port: 8081
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

写一个简单的接口:

@RestController
public class OrderController {
    @GetMapping("/order/info")
    public String getOrderInfo() {
        return "订单服务 - 实例端口: " + 
               System.getProperty("server.port");
    }
}

启动后,去 Nacos 控制台就能看到 order-service 已经注册上来了。

3. 服务消费者(Consumer)

消费者同样引入 nacos-discovery 依赖,配置基本一样,只是端口不同:

server:
  port: 8082
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

消费者通过 RestTemplate 调用提供者:

@RestController
public class UserController {
    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/user/call-order")
    public String callOrder() {
        // 从注册中心获取 order-service 的实例
        List<ServiceInstance> instances = 
            discoveryClient.getInstances("order-service");
        if (instances.isEmpty()) {
            return "没有可用的订单服务";
        }
        ServiceInstance instance = instances.get(0);
        String url = "http://" + instance.getHost() + 
                     ":" + instance.getPort() + "/order/info";
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject(url, String.class);
    }
}

启动消费者,访问 http://localhost:8082/user/call-order,就能看到返回的订单服务信息。

注意:生产环境中不要每次都 new RestTemplate,应该用连接池复用。另外,上面的 DiscoveryClient 是 Spring Cloud 通用的,换成 Eureka 也能用,只是配置不同。

避坑指南:我踩过的三个坑

做服务注册与发现,有几个坑我印象特别深,分享出来希望大家别重蹈覆辙。

  • 坑一:服务下线不及时。我曾经遇到过服务进程被 kill -9 强杀,注册中心没收到下线通知,导致消费者一直调用失败的实例。后来我加了优雅停机(Spring 的 @PreDestroy 或者 ShutdownHook),确保服务关闭前主动调用 deregister 接口。
  • 坑二:自我保护模式引发的“假死”。Eureka 的自我保护模式在网络分区时会保留所有实例,包括已经挂掉的。我有个项目在测试环境频繁出现调用超时,查了半天发现是自我保护把死实例保留了。解决方案是调低 renewalPercentThreshold 阈值,或者在测试环境关闭自我保护。
  • 坑三:Nacos 的 CP 模式导致注册慢。Nacos 默认是 AP 模式,但如果切换成 CP 模式(强调一致性),服务注册需要等待集群多数节点确认,延迟会明显增加。我有个金融项目要求强一致,结果压测时注册耗时从 5ms 飙升到 200ms。后来我们只在关键服务上用 CP,普通服务用 AP。

总结一下

服务注册与发现模式,说白了就是给微服务装一个动态通讯录。注册中心是核心,提供者和消费者围绕它工作。Eureka 是经典但已过时,Nacos 是当前的主流选择。

我个人建议,如果你还在用 Eureka,可以考虑迁移到 Nacos。迁移成本不高,但收益很明显——少维护一个配置中心,控制台也更友好。当然,如果你用的是 Kubernetes,那服务发现可以直接用 K8s 的 DNS 和 Service,连注册中心都省了。这是另一个话题了,咱们以后有机会再聊。

一句话记住:服务注册与发现 = 提供者注册 + 消费者发现 + 注册中心协调。选型上,新项目无脑 Nacos,老项目看情况迁移。


公众号:蓝海资料掘金营,微信deep3321