Java框架 SpringCloud 快速入门: Eureka 注册中心的搭建

概述

微服务拆分之后,订单服务调用用户服务不能再写死 http://localhost:8081,因为用户服务会扩容成多个实例,IP 和端口随时在变。这一篇就把解决这个问题的 Eureka 注册中心跑起来:一个独立的微服务,三步配置,启动后打开控制台能看到实例列表。

纲要

  • 角色划分
    • Eureka Server:注册中心,保存「服务名 → 实例地址列表」的映射
    • Eureka Client:服务提供者与消费者,注册信息、拉取列表都走这一端
  • 核心流程
    • 服务注册:provider 启动时上报自己的服务名、IP、端口
    • 服务保存:Server 保管地址列表,靠心跳(默认 30s)判断实例存活
    • 服务发现:consumer 按服务名拉取实例列表
    • 远程调用:负载均衡挑一个实例发请求
  • 动手搭建(五步)
    • 建 eureka-server 子模块
    • 引 spring-cloud-starter-netflix-eureka-server 依赖
    • 启动类加 @EnableEurekaServer
    • application.yml 配端口、服务名、defaultZone
    • 启动并访问 http://127.0.0.1:10086 验证控制台
  • 关键坑 :defaultZone 少写 /eureka、漏掉 @EnableEurekaServer、spring.application.name 不写导致控制台显示 UNKNOWN、端口冲突
  • 延伸:集群为什么要互相注册;Eureka 2.x 停维护与 Nacos 替代

Eureka 的两个角色

注册中心解决的问题只有一个:让调用方通过服务名找到可用实例的真实地址。围绕这件事,Eureka 把参与方分成两类。

角色 对应依赖 职责 典型代表
Eureka Server spring-cloud-starter-netflix-eureka-server 接收注册、保存地址列表、心跳检测、对外提供查询 eureka-server 模块
Eureka Client spring-cloud-starter-netflix-eureka-client 上报自身信息(注册)、拉取他人信息(发现) user-service、order-service

这里有个容易被忽略的点:注册和发现被封装在同一个 client 依赖里 。一个微服务既可能是提供者,也可能是消费者,所以 Eureka 没有拆成两套 API,引入 eureka-client 就同时具备两种能力。也就是说,user-service 用这个依赖,order-service 也用这个依赖,配置写法几乎一样。

整体链路如下。
#mermaid-svg-kOYbW6YveG5bDgDs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kOYbW6YveG5bDgDs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kOYbW6YveG5bDgDs .error-icon{fill:#552222;}#mermaid-svg-kOYbW6YveG5bDgDs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kOYbW6YveG5bDgDs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kOYbW6YveG5bDgDs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kOYbW6YveG5bDgDs .marker.cross{stroke:#333333;}#mermaid-svg-kOYbW6YveG5bDgDs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kOYbW6YveG5bDgDs p{margin:0;}#mermaid-svg-kOYbW6YveG5bDgDs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kOYbW6YveG5bDgDs .cluster-label text{fill:#333;}#mermaid-svg-kOYbW6YveG5bDgDs .cluster-label span{color:#333;}#mermaid-svg-kOYbW6YveG5bDgDs .cluster-label span p{background-color:transparent;}#mermaid-svg-kOYbW6YveG5bDgDs .label text,#mermaid-svg-kOYbW6YveG5bDgDs span{fill:#333;color:#333;}#mermaid-svg-kOYbW6YveG5bDgDs .node rect,#mermaid-svg-kOYbW6YveG5bDgDs .node circle,#mermaid-svg-kOYbW6YveG5bDgDs .node ellipse,#mermaid-svg-kOYbW6YveG5bDgDs .node polygon,#mermaid-svg-kOYbW6YveG5bDgDs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kOYbW6YveG5bDgDs .rough-node .label text,#mermaid-svg-kOYbW6YveG5bDgDs .node .label text,#mermaid-svg-kOYbW6YveG5bDgDs .image-shape .label,#mermaid-svg-kOYbW6YveG5bDgDs .icon-shape .label{text-anchor:middle;}#mermaid-svg-kOYbW6YveG5bDgDs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kOYbW6YveG5bDgDs .rough-node .label,#mermaid-svg-kOYbW6YveG5bDgDs .node .label,#mermaid-svg-kOYbW6YveG5bDgDs .image-shape .label,#mermaid-svg-kOYbW6YveG5bDgDs .icon-shape .label{text-align:center;}#mermaid-svg-kOYbW6YveG5bDgDs .node.clickable{cursor:pointer;}#mermaid-svg-kOYbW6YveG5bDgDs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kOYbW6YveG5bDgDs .arrowheadPath{fill:#333333;}#mermaid-svg-kOYbW6YveG5bDgDs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kOYbW6YveG5bDgDs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kOYbW6YveG5bDgDs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kOYbW6YveG5bDgDs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kOYbW6YveG5bDgDs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kOYbW6YveG5bDgDs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kOYbW6YveG5bDgDs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kOYbW6YveG5bDgDs .cluster text{fill:#333;}#mermaid-svg-kOYbW6YveG5bDgDs .cluster span{color:#333;}#mermaid-svg-kOYbW6YveG5bDgDs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kOYbW6YveG5bDgDs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kOYbW6YveG5bDgDs rect.text{fill:none;stroke-width:0;}#mermaid-svg-kOYbW6YveG5bDgDs .icon-shape,#mermaid-svg-kOYbW6YveG5bDgDs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kOYbW6YveG5bDgDs .icon-shape p,#mermaid-svg-kOYbW6YveG5bDgDs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kOYbW6YveG5bDgDs .icon-shape .label rect,#mermaid-svg-kOYbW6YveG5bDgDs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kOYbW6YveG5bDgDs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kOYbW6YveG5bDgDs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kOYbW6YveG5bDgDs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务消费者 order-service
注册中心 eureka-server :10086
服务提供者 user-service
服务注册
心跳续约
服务发现/拉取列表
调用真实地址
启动时注册

服务名 + IP + 端口
每 30s 发送心跳
保存映射关系

userservice -> 8081, 8082
剔除心跳超时实例
按服务名拉取实例列表
负载均衡选一个实例

一次完整调用在时间轴上是这样铺开的。
order-service eureka-server(10086) user-service(8081) order-service eureka-server(10086) user-service(8081) #mermaid-svg-O8x92dicCu1uaaXG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O8x92dicCu1uaaXG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O8x92dicCu1uaaXG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O8x92dicCu1uaaXG .error-icon{fill:#552222;}#mermaid-svg-O8x92dicCu1uaaXG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O8x92dicCu1uaaXG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O8x92dicCu1uaaXG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O8x92dicCu1uaaXG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O8x92dicCu1uaaXG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O8x92dicCu1uaaXG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O8x92dicCu1uaaXG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O8x92dicCu1uaaXG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O8x92dicCu1uaaXG .marker.cross{stroke:#333333;}#mermaid-svg-O8x92dicCu1uaaXG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O8x92dicCu1uaaXG p{margin:0;}#mermaid-svg-O8x92dicCu1uaaXG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-O8x92dicCu1uaaXG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-O8x92dicCu1uaaXG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-O8x92dicCu1uaaXG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-O8x92dicCu1uaaXG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-O8x92dicCu1uaaXG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-O8x92dicCu1uaaXG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-O8x92dicCu1uaaXG .sequenceNumber{fill:white;}#mermaid-svg-O8x92dicCu1uaaXG #sequencenumber{fill:#333;}#mermaid-svg-O8x92dicCu1uaaXG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-O8x92dicCu1uaaXG .messageText{fill:#333;stroke:none;}#mermaid-svg-O8x92dicCu1uaaXG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-O8x92dicCu1uaaXG .labelText,#mermaid-svg-O8x92dicCu1uaaXG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-O8x92dicCu1uaaXG .loopText,#mermaid-svg-O8x92dicCu1uaaXG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-O8x92dicCu1uaaXG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-O8x92dicCu1uaaXG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-O8x92dicCu1uaaXG .noteText,#mermaid-svg-O8x92dicCu1uaaXG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-O8x92dicCu1uaaXG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-O8x92dicCu1uaaXG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-O8x92dicCu1uaaXG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-O8x92dicCu1uaaXG .actorPopupMenu{position:absolute;}#mermaid-svg-O8x92dicCu1uaaXG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-O8x92dicCu1uaaXG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-O8x92dicCu1uaaXG .actor-man circle,#mermaid-svg-O8x92dicCu1uaaXG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-O8x92dicCu1uaaXG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} POST 注册请求(userservice, 192.168.x.x:8081) 写入地址列表 每 30s 心跳(续约) GET /eureka/apps 拉取 userservice 实例 返回 8081, 8082 负载均衡挑一个 HTTP GET http://192.168.x.x:8081/user/1

搭建前的工程结构

注册中心必须是一个独立的微服务 ,不能塞进 user-service 里。原因很直接:注册中心自己要是挂掉重启,不应该牵连任何一个业务服务;反过来业务服务重启,也不该影响注册中心的地址列表。所以它单独建模块,单独占端口。

沿用前面的 cloud-demo 父工程,最终结构如下。

tree 复制代码
cloud-demo
├── pom.xml                      # 父工程,统一管理 SpringCloud / SpringBoot 版本
├── eureka-server                # 注册中心(本篇主角)
│   ├── pom.xml
│   └── src/main
│       ├── java/cn/itcast/eureka/EurekaApplication.java
│       └── resources/application.yml
├── user-service                 # 服务提供者,后续注册进来
│   ├── pom.xml
│   └── src/main
│       ├── java/cn/itcast/user/UserApplication.java
│       └── resources/application.yml
└── order-service                # 服务消费者,后续来拉取列表
    ├── pom.xml
    └── src/main
        ├── java/cn/itcast/order/OrderApplication.java
        └── resources/application.yml

父工程先把 eureka-server 登记进 <modules>,否则建了模块也不会参与构建。

xml 复制代码
<modules>
    <module>user-service</module>
    <module>order-service</module>
    <module>eureka-server</module>
</modules>

引依赖:为什么不用写版本号

在 eureka-server 的 pom.xml 里加上服务端 starter。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- eureka 服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

注意这里没有 version 。不是漏写,是踩在有版本管理的前提下才成立:父工程 cloud-demo 里用 dependencyManagement 导入了 spring-cloud-dependencies 这个 BOM,所有 SpringCloud 组件的版本已经在 BOM 里定义好了。

xml 复制代码
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR10</spring-cloud.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

父工程 spring-boot-starter-parent 的版本是 2.3.9.RELEASE,对应 SpringCloud Hoxton.SR10,Eureka 的 1.9.x 就在里面。写版本号不是不可以,只是后面升级 BOM 时会漏改,反而容易出「某个模块版本对不上」的诡异问题。

artifactId 后缀是 -server,说明这是服务端 依赖。换成 -client 就是客户端依赖,这是选依赖时最容易看错的地方,两个名字只差一个词。

starter 的含义是:依赖里已经带好了 Eureka Server 的自动装配,理论上可以零配置启动。但自动装配需要一个开关,开关就是下一步的注解。

启动类:@EnableEurekaServer 是那个开关

java 复制代码
package cn.itcast.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

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

注解拆开看:

注解 作用 漏写会怎样
@SpringBootApplication 开启 SpringBoot 自动配置、组件扫描 应用根本起不来
@EnableEurekaServer 开启注册中心功能,导入 Eureka Server 的自动配置类 服务能启动,但 /eureka/** 全部 404,控制台打不开

两个注解顺序无所谓,但 @EnableEurekaServer 一定不能少。它做的事情是把 EurekaServerAutoConfiguration 拉进容器,注册中心的一整套 REST 接口(注册、续约、下线、查询)都是这么装上去的。

写完之后 IDE 里应该有一个 Eureka 的启动项。启动类的包名是 cn.itcast.eureka,注意别被组件扫描的范围挡住------它自己就在根包下,扫描没问题。

application.yml:三块配置各管什么

src/main/resources/application.yml:

yaml 复制代码
server:
  port: 10086 # 服务端口
spring:
  application:
    name: eurekaserver # eureka 的服务名称
eureka:
  client:
    service-url: # eureka 的地址信息
      defaultZone: http://127.0.0.1:10086/eureka

三段配置,各自解决一个问题。
#mermaid-svg-zOsWwI7RQawQY63I{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zOsWwI7RQawQY63I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zOsWwI7RQawQY63I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zOsWwI7RQawQY63I .error-icon{fill:#552222;}#mermaid-svg-zOsWwI7RQawQY63I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zOsWwI7RQawQY63I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zOsWwI7RQawQY63I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zOsWwI7RQawQY63I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zOsWwI7RQawQY63I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zOsWwI7RQawQY63I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zOsWwI7RQawQY63I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zOsWwI7RQawQY63I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zOsWwI7RQawQY63I .marker.cross{stroke:#333333;}#mermaid-svg-zOsWwI7RQawQY63I svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zOsWwI7RQawQY63I p{margin:0;}#mermaid-svg-zOsWwI7RQawQY63I .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zOsWwI7RQawQY63I .cluster-label text{fill:#333;}#mermaid-svg-zOsWwI7RQawQY63I .cluster-label span{color:#333;}#mermaid-svg-zOsWwI7RQawQY63I .cluster-label span p{background-color:transparent;}#mermaid-svg-zOsWwI7RQawQY63I .label text,#mermaid-svg-zOsWwI7RQawQY63I span{fill:#333;color:#333;}#mermaid-svg-zOsWwI7RQawQY63I .node rect,#mermaid-svg-zOsWwI7RQawQY63I .node circle,#mermaid-svg-zOsWwI7RQawQY63I .node ellipse,#mermaid-svg-zOsWwI7RQawQY63I .node polygon,#mermaid-svg-zOsWwI7RQawQY63I .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zOsWwI7RQawQY63I .rough-node .label text,#mermaid-svg-zOsWwI7RQawQY63I .node .label text,#mermaid-svg-zOsWwI7RQawQY63I .image-shape .label,#mermaid-svg-zOsWwI7RQawQY63I .icon-shape .label{text-anchor:middle;}#mermaid-svg-zOsWwI7RQawQY63I .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zOsWwI7RQawQY63I .rough-node .label,#mermaid-svg-zOsWwI7RQawQY63I .node .label,#mermaid-svg-zOsWwI7RQawQY63I .image-shape .label,#mermaid-svg-zOsWwI7RQawQY63I .icon-shape .label{text-align:center;}#mermaid-svg-zOsWwI7RQawQY63I .node.clickable{cursor:pointer;}#mermaid-svg-zOsWwI7RQawQY63I .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zOsWwI7RQawQY63I .arrowheadPath{fill:#333333;}#mermaid-svg-zOsWwI7RQawQY63I .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zOsWwI7RQawQY63I .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zOsWwI7RQawQY63I .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zOsWwI7RQawQY63I .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zOsWwI7RQawQY63I .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zOsWwI7RQawQY63I .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zOsWwI7RQawQY63I .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zOsWwI7RQawQY63I .cluster text{fill:#333;}#mermaid-svg-zOsWwI7RQawQY63I .cluster span{color:#333;}#mermaid-svg-zOsWwI7RQawQY63I div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zOsWwI7RQawQY63I .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zOsWwI7RQawQY63I rect.text{fill:none;stroke-width:0;}#mermaid-svg-zOsWwI7RQawQY63I .icon-shape,#mermaid-svg-zOsWwI7RQawQY63I .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zOsWwI7RQawQY63I .icon-shape p,#mermaid-svg-zOsWwI7RQawQY63I .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zOsWwI7RQawQY63I .icon-shape .label rect,#mermaid-svg-zOsWwI7RQawQY63I .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zOsWwI7RQawQY63I .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zOsWwI7RQawQY63I .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zOsWwI7RQawQY63I :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} server.port = 10086
注册中心对外端口

控制台与注册接口都在这个端口
spring.application.name = eurekaserver
微服务名称

注册进地址列表时用作 key
eureka.client.service-url.defaultZone

= http://127.0.0.1:10086/eureka
告诉客户端

注册中心在哪里

端口 用 10086 是课程惯例,换别的也行,但要和下面的 defaultZone 一致,改一个必须改另一个。

服务名 是 eurekaserver。这里最容易犯的错是干脆不写,[Eureka 控制台](#Eureka 控制台) 那一栏就会显示成 UNKNOWN。因为服务端启动时也会把自己当成一个微服务注册进去,没有名字就只能叫 UNKNOWN。

defaultZone 是注册中心的地址。既然自己就是注册中心,为什么还要配自己的地址?因为Eureka Server 自己也是一个微服务 ,启动时会把自己也注册到自己身上。这一步在单机环境下看起来多余,但它是为集群准备的:两台/三台 Eureka 互相把对方写进 defaultZone,就能彼此交换地址列表,数据才通。多节点写法就是把地址用逗号隔开。

yaml 复制代码
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:10086/eureka,http://eureka2:10087/eureka

本篇是单节点,写自己就够了。到这里「服务名 + 地址」其实就是在做服务注册,这两项是注册动作的必要参数。

defaultZone 为什么必须带 /eureka 后缀

这是整个搭建过程里最经典的坑。地址写成 http://127.0.0.1:10086,启动时不会立刻报错,而是在注册阶段抛异常,或者实例状态一直是 DOWN。

原因是 Eureka Server 的 REST 接口挂在 /eureka 这个上下文路径下,注册用的是 /eureka/apps/{appName},查询用的是 /eureka/apps。defaultZone 指的是注册接口的基准地址 ,不是首页地址。少了后缀就等于请求打到了 http://127.0.0.1:10086/apps/...,自然 404。

text 复制代码
正确:http://127.0.0.1:10086/eureka
错误:http://127.0.0.1:10086
错误:http://127.0.0.1:10086/eureka/     ← 尾部多余斜杠,某些版本会拼出 //eureka/apps,也出问题

顺带记住:浏览器里访问控制台用的是 http://127.0.0.1:10086,不带 /eureka;配置文件里写的是 http://127.0.0.1:10086/eureka,带。两者的区别就在这,别混着抄。

register-with-eureka 与 fetch-registry

两个开关经常和 defaultZone 一起出现,但课程里默认没配,先看它们默认值是什么。

配置项 默认值 含义 单节点下建议
eureka.client.register-with-eureka true 是否把自己注册到注册中心 可设 false
eureka.client.fetch-registry true 是否拉取服务列表并缓存到本地 可设 false

默认都是 true,所以课程里不配,Eureka 也会注册自己------这正是控制台「Instances currently registered with Eureka」里有 eurekaserver 一行的原因。

单节点部署时把它们关掉是常见做法:注册中心不需要发现别人,也不需要被自己发现,关掉能少一次无意义的注册请求和一份本地缓存。代价是控制台那一栏会空着,看不出「服务是否正常上报」,调试阶段反而不如留着。

yaml 复制代码
eureka:
  client:
    register-with-eureka: false # 单节点不需要注册自己
    fetch-registry: false       # 单节点不需要拉取服务列表
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

结论:集群下必须保持默认 true(节点之间靠注册和拉取同步数据),单节点下关不关都行。遇到「控制台列表为空」先回头确认这两个开关,别急着怀疑网络。

启动与验证

找到 EurekaApplication 的 main 方法直接运行。IDEA 的 Services 面板会多出一个 EurekaApplication 启动项,启动日志里搜 Started EurekaApplication,端口是 10086。

浏览器访问 http://127.0.0.1:10086(IDEA 里直接点日志中的端口也能跳过去),看到的就是 Eureka 自带的管理页面。

text 复制代码
+--------------------------------------------------------------------------------+
|  Eureka                                                          [Last updated] |
+--------------------------------------------------------------------------------+
|  System Status                                                                 |
|    Environment .................... test                                       |
|    Current time ................... Wed Sep 30 21:03:41 CST 2026               |
|    Uptime ......................... 0000 00:02:15                              |
|    Memory ......................... 301M (max 402M, total 402M)                |
|    Available processors ........... 8 (of 8)                                   |
+--------------------------------------------------------------------------------+
|  DS Replicas                                                                   |
|    eurekaserver                                                                |
|    <hr/>                                                                       |
|    Instances currently registered with Eureka                                  |
|      Application     AMIs    Availability Zones    Status                      |
|      EUREKASERVER    n/a     (1)                    UP (1) - Wang:10086        |
+--------------------------------------------------------------------------------+
|  General Info                                                                  |
|    total-avail-memory ............ 402mb                                       |
|    num-of-cpus ................... 8                                           |
+--------------------------------------------------------------------------------+

三段内容各看什么:

  • System Status:环境、启动时间、JVM 内存。用来确认应用真的起来了,不是缓存页面。
  • DS Replicas :对等的注册中心节点。单机只有一个 eurekaserver。
  • Instances currently registered with Eureka :最关键的一段,注册进来的实例列表。Application 列是服务名,后面跟着状态和地址,将来 userservice、orderservice 都会出现在这里。

最后一段里的 UP 表示实例健康,DOWN 表示不可用。地址列在 Windows 上显示的往往是计算机名 (本例是机器名 Wang)而不是 IP,这是本机解析行为,不影响使用;换成 IP 部署或调 eureka.instance.prefer-ip-address 后就会显示真实 IP。

常见启动报错排查

现象 原因 改法
访问首页 404,日志无异常 启动类漏了 @EnableEurekaServer 补上注解(import 是 org.springframework.cloud.netflix.eureka.server.EnableEurekaServer)
启动后报 com.netflix.discovery.shared.transport.TransportException / Cannot execute request on any known server defaultZone 少写 /eureka,注册请求打到了 404 地址 地址补成 http://127.0.0.1:10086/eureka
控制台实例名显示 UNKNOWN spring.application.name 没配或写错层级 补 spring.application.name: eurekaserver
端口被占用 Port 10086 was already in use 上一次进程没退干净,或端口被别的程序占 lsof -i:10086(Windows 用 `netstat -ano
实例状态长时间是 DOWN 注册中心和实例地址不通,或 hostname 解析异常 检查 defaultZone、hosts 文件,必要时配 eureka.instance.prefer-ip-address: true
依赖拉不下来 / 类找不到 spring-cloud.version 与 SpringBoot 版本不匹配 参照官方版本对照表,2.3.x 对应 Hoxton.SR10

关于 Eureka 的现状

Eureka 2.x 已经停止维护,Netflix 也不再继续开源版本的演进。课程里用它是因为概念干净、控制台直观,适合把「注册中心」这件事讲透。生产环境新建项目,现在多数会选 Nacos ------同时做注册中心和配置中心,还带控制台和权重、命名空间之类的管理能力。迁移时注意一点:Nacos 的地址写法不带 /eureka 这类上下文,spring.cloud.nacos.discovery.server-addr 直接写 127.0.0.1:8848,别把 Eureka 的习惯直接搬过去。

关键配置速览

配置项 示例值 说明
server.port 10086 注册中心端口,必须与 defaultZone 一致
spring.application.name eurekaserver 微服务名,不写会在控制台显示 UNKNOWN
eureka.client.service-url.defaultZone http://127.0.0.1:10086/eureka 注册中心地址,必须带 /eureka 后缀
eureka.client.register-with-eureka true(默认) 是否注册自身,单节点可设 false
eureka.client.fetch-registry true(默认) 是否拉取服务列表,单节点可设 false
eureka.instance.prefer-ip-address true 需要显示真实 IP 而非主机名时开启

API 速览

Eureka Server 对外就是一组 REST 接口,配置里的 defaultZone 拼的就是它们的前缀。

方法 路径 说明
POST /eureka/apps/{appName} 服务注册,客户端启动时调用
PUT /eureka/apps/{appName}/{instanceId} 心跳续约,默认每 30 秒一次
DELETE /eureka/apps/{appName}/{instanceId} 服务下线,客户端优雅关闭时调用
GET /eureka/apps 查询全部实例,服务发现的核心接口
GET /eureka/apps/{appName} 按服务名查询实例列表
GET / Eureka 管理控制台页面

官方文档

总结

搭建 Eureka Server 的动作本身只有三步:引 eureka-server 依赖、启动类加 @EnableEurekaServer、配置文件写端口/服务名/defaultZone。

真正需要记住的是几个判断点:

  • 依赖后缀是 -server 还是 -client,选错不会报错但功能对不上
  • defaultZone 后面那截 /eureka 少不得,这是 404 的头号来源
  • spring.application.name 决定控制台里显示名字还是 UNKNOWN
  • register-with-eureka、fetch-registry 默认是 true,单节点关掉是优化不是必须,集群下必须保留
  • 控制台里的实例列表,就是整个注册中心的产物,也是后面服务注册与服务发现的验证入口

下一步就是把 user-service 用 eureka-client 注册进来,再让 order-service 按服务名拉取并完成负载均衡调用。

相关推荐
专业程序开发源1 小时前
springboot古城景区管理系统88564-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
泡茶喝茶写代码1 小时前
A股量化数据工程:从 REST 接口到策略信号(第 7 篇):盈利能力因子:毛利率、净利率与 ROE
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
小蒜学长1 小时前
基于微信小程序的遇见咖啡店点餐系统设计与实现(代码+数据库+LW)
java·spring boot·后端·微信小程序·咖啡店点餐系统
lisanmengmeng1 小时前
NRPE 添加命令(三)
java·linux·服务器
Wang's Blog1 小时前
Java框架 SpringCloud 快速入门: 网关的 CORS 跨域配置
java·开发语言·spring cloud
驭渊的小故事1 小时前
NC229023 题解:二分答案求解“最大化最小值“问题
java·算法
小蒜学长1 小时前
基于SSM+VUE的电影售票平台的设计与实现(代码+数据库+LW)
java·vue.js·spring boot·后端·ssm框架·电影售票平台
Wang's Blog2 小时前
Java框架 SpringCloud 快速入门: Feign 替代 RestTemplate 实现声明式远程调用
java·开发语言·spring cloud
谢亮_vipxieliang2 小时前
Go 结构体与方法集核心知识点
java·开发语言·golang