Java框架 SpringCloud 快速入门: Eureka 服务注册

概述

注册中心已经跑起来了,空列表一个实例都没有,接下来就是把 user-service、order-service 注册进去。这件事本身不难------引一个依赖、写两行配置就完事------但「谁需要注册」这个前提很容易想歪,很多人以为只有服务提供者才要注册,结果消费者拉不到服务列表,卡一整天。

纲要

  • 注册对象 :提供者 user-service 与消费者 order-service 都要注册,理由在下面单独说
  • 三步改造
    • 引入 spring-cloud-starter-netflix-eureka-client 依赖
    • application.yml 配 spring.application.name 与 eureka.client.service-url.defaultZone
    • 启动类是否要加 @EnableDiscoveryClient(新版本可省,源码情况见正文)
  • 核心认知 :spring.application.name 是服务在注册中心里的唯一标识,后续 lb://userservice、@FeignClient("userservice") 全靠它匹配
  • 验证方式 :访问 http://localhost:10086,看 Instances currently registered with Eureka 列表与实例状态
  • 多实例:同一个服务用不同端口启动两次,控制台出现两个实例,这是负载均衡的前提
  • 实战坑 :defaultZone 漏写 /eureka、服务名带大写/下划线、消费者忘引 client、localhost 与 127.0.0.1 混用

先想清楚:到底谁要注册进来

先纠正一个高频误解。很多人的直觉是「提供者提供服务,所以要注册;消费者只是来调用的,不用注册」。这个直觉在 Eureka 里是错的。

Eureka 的 client 依赖把服务注册 和服务发现 封装在了一起,引入它同时获得两种能力。order-service 需要知道 userservice 有哪些实例、IP 端口是多少,这份信息只能从注册中心拉------它自己不是注册中心的一员,凭什么拉?所以它也得注册进去,成为一个「既是消费者、也可能是提供者」的客户端。

服务 角色 是否注册 注册后拿到什么
user-service 服务提供者 要 自己的信息进入注册表,被消费者发现
order-service 服务消费者 要 能拉取 userservice 的实例列表做负载均衡
eureka-server 注册中心 自身也配 client 启动时把自己的地址配进 defaultZone,集群场景下互相注册

所以改造动作是两个模块各做一遍,写法几乎一样,只是 name 不同。
#mermaid-svg-onqz9yUqT1WSRU2a{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-onqz9yUqT1WSRU2a .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-onqz9yUqT1WSRU2a .error-icon{fill:#552222;}#mermaid-svg-onqz9yUqT1WSRU2a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-onqz9yUqT1WSRU2a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-onqz9yUqT1WSRU2a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-onqz9yUqT1WSRU2a .marker.cross{stroke:#333333;}#mermaid-svg-onqz9yUqT1WSRU2a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-onqz9yUqT1WSRU2a p{margin:0;}#mermaid-svg-onqz9yUqT1WSRU2a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-onqz9yUqT1WSRU2a .cluster-label text{fill:#333;}#mermaid-svg-onqz9yUqT1WSRU2a .cluster-label span{color:#333;}#mermaid-svg-onqz9yUqT1WSRU2a .cluster-label span p{background-color:transparent;}#mermaid-svg-onqz9yUqT1WSRU2a .label text,#mermaid-svg-onqz9yUqT1WSRU2a span{fill:#333;color:#333;}#mermaid-svg-onqz9yUqT1WSRU2a .node rect,#mermaid-svg-onqz9yUqT1WSRU2a .node circle,#mermaid-svg-onqz9yUqT1WSRU2a .node ellipse,#mermaid-svg-onqz9yUqT1WSRU2a .node polygon,#mermaid-svg-onqz9yUqT1WSRU2a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-onqz9yUqT1WSRU2a .rough-node .label text,#mermaid-svg-onqz9yUqT1WSRU2a .node .label text,#mermaid-svg-onqz9yUqT1WSRU2a .image-shape .label,#mermaid-svg-onqz9yUqT1WSRU2a .icon-shape .label{text-anchor:middle;}#mermaid-svg-onqz9yUqT1WSRU2a .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-onqz9yUqT1WSRU2a .rough-node .label,#mermaid-svg-onqz9yUqT1WSRU2a .node .label,#mermaid-svg-onqz9yUqT1WSRU2a .image-shape .label,#mermaid-svg-onqz9yUqT1WSRU2a .icon-shape .label{text-align:center;}#mermaid-svg-onqz9yUqT1WSRU2a .node.clickable{cursor:pointer;}#mermaid-svg-onqz9yUqT1WSRU2a .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-onqz9yUqT1WSRU2a .arrowheadPath{fill:#333333;}#mermaid-svg-onqz9yUqT1WSRU2a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-onqz9yUqT1WSRU2a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-onqz9yUqT1WSRU2a .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-onqz9yUqT1WSRU2a .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-onqz9yUqT1WSRU2a .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-onqz9yUqT1WSRU2a .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-onqz9yUqT1WSRU2a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-onqz9yUqT1WSRU2a .cluster text{fill:#333;}#mermaid-svg-onqz9yUqT1WSRU2a .cluster span{color:#333;}#mermaid-svg-onqz9yUqT1WSRU2a 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-onqz9yUqT1WSRU2a .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-onqz9yUqT1WSRU2a rect.text{fill:none;stroke-width:0;}#mermaid-svg-onqz9yUqT1WSRU2a .icon-shape,#mermaid-svg-onqz9yUqT1WSRU2a .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-onqz9yUqT1WSRU2a .icon-shape p,#mermaid-svg-onqz9yUqT1WSRU2a .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-onqz9yUqT1WSRU2a .icon-shape .label rect,#mermaid-svg-onqz9yUqT1WSRU2a .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-onqz9yUqT1WSRU2a .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-onqz9yUqT1WSRU2a .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-onqz9yUqT1WSRU2a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 写入注册表
服务发现
eureka-server :10086

注册中心
user-service :8081

注册:服务名 + IP + 端口
order-service :8080

注册后可拉取 userservice 列表
userservice -> 8081, 8082

改造模块一:user-service

改造前的真实源码长这样,pom.xml 里只有 web、mysql、mybatis 三个依赖,application.yml 只有一个 server.port 和数据源,启动类只有 @SpringBootApplication 和 @MapperScan------跟 Eureka 一点关系都没有。下面三步就是把它接进注册中心。

先看工程结构,确认改哪个文件。

tree 复制代码
cloud-demo
├── pom.xml                       # 父工程,统一管理 spring-cloud 版本
├── user-service
│   ├── pom.xml                   # ① 加 eureka-client 依赖
│   └── 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

第一步:引入客户端依赖

在 user-service/pom.xml 的 <dependencies> 里加 spring-cloud-starter-netflix-eureka-client。

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>user-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--eureka 客户端:同时提供"服务注册"与"服务发现"两项能力-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

这里没写 <version> ,因为父工程 cloud-demo 用 dependencyManagement 导入了 spring-cloud-dependencies(本例版本 Hoxton.SR10,对应 SpringBoot 2.3.9.RELEASE),版本由它统一兜底。注意别手贱把服务端依赖 eureka-server 也引进来------那是注册中心该引的,引错了会平白启动一个 Server 出来。

第二步:配置服务名与注册中心地址

在 user-service/src/main/resources/application.yml 里加两段。原文件已经有 server 和 spring.datasource,spring.application.name 要挂在现有的 spring 节点下,不能新起一个 spring: 跟原来的打架。

yaml 复制代码
server:
  port: 8081
spring:
  application:
    name: userservice          # 服务名,注册到 Eureka 的唯一标识
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
eureka:
  client:
    service-url:
      defaultZone: http://localhost:10086/eureka/   # 注册中心地址,注意 /eureka 后缀
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS

这段配置跟之前配 eureka-server 的几乎一模一样,因为注册中心自己也要「注册到自己」------配服务名和地址就是注册这个动作的全部条件,跟客户端还是服务端身份无关。

YAML 有个很坑的细节:同一个 key 只能出现一次 。如果原来的文件里已经有 spring:,你把拷贝来的 spring: application: name: ... 整段粘上去,就会变成两个 spring: 根节点,SpringBoot 启动直接报解析异常。正确做法是把 application 这段合并到已有的 spring 下面,不要另起炉灶。工程里从 eureka-server 粘贴配置时最容易踩这个。

第三步:启动类要不要加 @EnableDiscoveryClient

先说结论:本例 SpringCloud Hoxton.SR10 下,不加也能注册成功 。看真实源码,改造前 UserApplication 就长这样。

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

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("cn.itcast.user.mapper")
@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

原因在于新版 SpringCloud 通过 spring.factories 的自动装配机制,只要类路径下存在 eureka-client,EurekaClientAutoConfiguration 就会自动生效,@EnableDiscoveryClient 属于可省。加上它也不会出错,语义上更直白------告诉读代码的人「这是个注册中心客户端」。所以两种写法都对:

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

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@MapperScan("cn.itcast.user.mapper")
@SpringBootApplication
@EnableDiscoveryClient   // 新版本可省,加上更明确
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

老版本(如 Dalston、Edgware 早期)必须加这个注解才会开启注册,如果你是跟着老教程学的,别把「不用加」当成通用结论。

spring.application.name 到底管什么

这一步值得单独拎出来讲,因为它是后面所有跨服务调用的地基。

spring.application.name 不是随手起的显示名,它是服务在注册中心里的唯一标识,直接决定三件事:

  1. 控制台 Application 列里显示什么(Eureka 会把它转成大写,userservice 显示为 USERSERVICE)
  2. 网关里 lb://userservice 的 userservice 部分怎么写
  3. Feign 里 @FeignClient("userservice") 的括号里填什么

名字对不对,决定后面能不能调通。举几个会翻车的例子:

写法 问题 后果
userService(驼峰) Eureka 统一转大写存成 USERSERVICE lb://userService 匹配不上,报 Unable to find instance for userService
user_service(下划线) 与 user-service 不是同一个名字 提供者注册的是 USER_SERVICE,消费者找 userservice,找不到
两个服务用同一个 name 同名即同服务 注册表里混成一批实例,负载均衡把请求打到错误的服务上

经验做法:服务名全小写、用中划线或直接连写 ,并且保证提供方和消费方引用的字符串一字不差。改名前先全局搜一遍 lb:// 和 @FeignClient 里的字符串。

改造模块二:order-service

消费者一模一样做三步,只是 name 换成 orderservice,端口沿用源码里的 8080。

依赖段与 user-service 完全相同,照搬即可。

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

order-service/src/main/resources/application.yml:

yaml 复制代码
server:
  port: 8080
spring:
  application:
    name: orderservice     # 消费者自己的服务名
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
eureka:
  client:
    service-url:
      defaultZone: http://localhost:10086/eureka/
mybatis:
  type-aliases-package: cn.itcast.order.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS

order-service 的启动类 OrderApplication 与 user-service 同理,加不加 @EnableDiscoveryClient 都能跑通。

这里又有人会问:order-service 明明是被调用的提供者角色都没有,为什么还要写 name?因为它要按名字去注册表里查别人,查询接口本身不需要自己的名字,但 Eureka 的注册流程无差别对待所有 client------你注册进来,就必须有个名字占位。

验证注册结果

顺序上先起 eureka-server,再起 user-service、order-service。启动顺序不影响最终注册,只影响首次拉取时能不能立刻拿到别人------本机开发先起注册中心最省事。

访问控制台 http://localhost:10086,页面下方 Instances currently registered with Eureka 一节会长这样。

text 复制代码
Instances currently registered with Eureka

Application      AMIs    Availability Zones    Status
USERSERVICE      n/a     (1)                   UP (192.168.1.10:8081)
ORDERSERVICE     n/a     (1)                   UP (192.168.1.10:8080)

General Info
  total-avail-memory   : 256mb
  ...

几个状态要认识:

现象 含义 排查方向
列表出现 USERSERVICE + UP (ip:port) 注册成功 无
服务名显示为 UNKNOWN 没配 spring.application.name 补上服务名,重启
状态是 DOWN 实例认为自己是宕机状态 看服务自身健康检查、端口是否真的在监听
应用名都看不到 client 压根没注册上去 检查依赖是否引入、defaultZone 地址与后缀是否正确
服务名在、实例数为 0 注册过了但实例被剔除 心跳断了,看服务进程是否已退出

重启客户端时控制台可能有十几秒延迟才刷新,因为实例列表默认 30 秒同步一次,别立刻下结论说没注册上。

多实例注册

一个服务只出现一个实例,负载均衡就无从谈起。本机没条件部署两台机器,用 IDEA 复制一个启动配置、换个端口即可模拟。

做法:右键 UserApplication 的启动配置 → Copy Configuration → 起个名字,在 Environment 的 VM options 里加 -Dserver.port=8082(-D 是 JVM 设置系统属性的方式,server.port 正是 yml 里那个键,优先级更高,会覆盖 application.yml 里的 8081),启动第二个实例。

text 复制代码
VM options: -Dserver.port=8082

两个实例用同一份代码、同一套依赖配置,只有运行端口不同。刷新控制台:

text 复制代码
Application      AMIs    Availability Zones    Status
USERSERVICE      n/a     (2)                   UP (192.168.1.10:8082)
                                               UP (192.168.1.10:8081)
ORDERSERVICE     n/a     (1)                   UP (192.168.1.10:8080)

Availability Zones 那一列的 (2) 就是实例数量,下面挂两个 UP 地址。
#mermaid-svg-oISzMBla4zl99W07{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-oISzMBla4zl99W07 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oISzMBla4zl99W07 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oISzMBla4zl99W07 .error-icon{fill:#552222;}#mermaid-svg-oISzMBla4zl99W07 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oISzMBla4zl99W07 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oISzMBla4zl99W07 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oISzMBla4zl99W07 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oISzMBla4zl99W07 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oISzMBla4zl99W07 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oISzMBla4zl99W07 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oISzMBla4zl99W07 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oISzMBla4zl99W07 .marker.cross{stroke:#333333;}#mermaid-svg-oISzMBla4zl99W07 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oISzMBla4zl99W07 p{margin:0;}#mermaid-svg-oISzMBla4zl99W07 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oISzMBla4zl99W07 .cluster-label text{fill:#333;}#mermaid-svg-oISzMBla4zl99W07 .cluster-label span{color:#333;}#mermaid-svg-oISzMBla4zl99W07 .cluster-label span p{background-color:transparent;}#mermaid-svg-oISzMBla4zl99W07 .label text,#mermaid-svg-oISzMBla4zl99W07 span{fill:#333;color:#333;}#mermaid-svg-oISzMBla4zl99W07 .node rect,#mermaid-svg-oISzMBla4zl99W07 .node circle,#mermaid-svg-oISzMBla4zl99W07 .node ellipse,#mermaid-svg-oISzMBla4zl99W07 .node polygon,#mermaid-svg-oISzMBla4zl99W07 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oISzMBla4zl99W07 .rough-node .label text,#mermaid-svg-oISzMBla4zl99W07 .node .label text,#mermaid-svg-oISzMBla4zl99W07 .image-shape .label,#mermaid-svg-oISzMBla4zl99W07 .icon-shape .label{text-anchor:middle;}#mermaid-svg-oISzMBla4zl99W07 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oISzMBla4zl99W07 .rough-node .label,#mermaid-svg-oISzMBla4zl99W07 .node .label,#mermaid-svg-oISzMBla4zl99W07 .image-shape .label,#mermaid-svg-oISzMBla4zl99W07 .icon-shape .label{text-align:center;}#mermaid-svg-oISzMBla4zl99W07 .node.clickable{cursor:pointer;}#mermaid-svg-oISzMBla4zl99W07 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oISzMBla4zl99W07 .arrowheadPath{fill:#333333;}#mermaid-svg-oISzMBla4zl99W07 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oISzMBla4zl99W07 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oISzMBla4zl99W07 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oISzMBla4zl99W07 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oISzMBla4zl99W07 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oISzMBla4zl99W07 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oISzMBla4zl99W07 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oISzMBla4zl99W07 .cluster text{fill:#333;}#mermaid-svg-oISzMBla4zl99W07 .cluster span{color:#333;}#mermaid-svg-oISzMBla4zl99W07 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-oISzMBla4zl99W07 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oISzMBla4zl99W07 rect.text{fill:none;stroke-width:0;}#mermaid-svg-oISzMBla4zl99W07 .icon-shape,#mermaid-svg-oISzMBla4zl99W07 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oISzMBla4zl99W07 .icon-shape p,#mermaid-svg-oISzMBla4zl99W07 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oISzMBla4zl99W07 .icon-shape .label rect,#mermaid-svg-oISzMBla4zl99W07 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oISzMBla4zl99W07 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oISzMBla4zl99W07 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oISzMBla4zl99W07 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拉取列表后负载均衡
eureka-server :10086
USERSERVICE

两个实例
实例 A 8081
实例 B 8082
order-service :8080

这是后面 Ribbon / LoadBalancer 做负载均衡的前提------注册表里只有一个实例,均衡算法再聪明也没得选。

实战坑

defaultZone 漏写 /eureka 后缀。 写成 http://localhost:10086 是最常见的错,客户端会去请求根路径,连不上注册接口,日志里反复刷 Cannot execute request on any known server,而且服务不会报错退出,就那样干耗着。正确值要走 /eureka。末尾多一个 / 无所谓,.../eureka 和 .../eureka/ 都能识别,但中间那段 /eureka 不能少。

服务名带大写或下划线。 Eureka 会把应用名转成大写存储并查询,userService 在控制台变成 USERSERVICE,而你在 lb://userService 里写的是原样字符串,两下一对比就对不上。统一全小写、用中划线,能少掉一类玄学问题。

消费者忘引 eureka-client。 order-service 只加了 web 和 mybatis,RestTemplate 里写 http://userservice/user/1 就报 UnknownHostException: userservice------因为它既没注册自己、也没从注册中心拉列表,userservice 这个主机名它根本不认识。消费者和提供者都要引 client。

localhost 与 127.0.0.1 混用。 注册时用 localhost,访问控制台却用 127.0.0.1,多数情况没问题;但如果父工程 cloud-demo 被人把 hosts 改过,或者某些环境下 localhost 解析到 IPv6 的 ::1 而 Eureka 只监听了 IPv4,就会出现「注册地址是一个、调用地址是另一个」的错位。团队里统一用 127.0.0.1 最稳。

服务启动顺序。 先起 user-service 再起 eureka-server,服务不会崩,客户端会隔一段时间重试注册,只是控制台迟迟看不到。反过来先起注册中心,注册立即可见,排查更爽快。

配置节点写重。 从别处粘贴 eureka: 或 spring: 整段时,同名的根节点叠加会让 YAML 解析失败或后者覆盖前者,表现为「明明配了却不生效」。加配置前先看一遍文件里有没有同名 key。

API 速览

配置项 作用 示例值
spring.application.name 服务名,注册中心的唯一标识 userservice
eureka.client.service-url.defaultZone 注册中心地址,必须带 /eureka http://localhost:10086/eureka/
eureka.client.register-with-eureka 是否把自己注册上去,默认 true false(纯拉取场景可关)
eureka.client.fetch-registry 是否拉取注册表,默认 true false(纯提供者场景可关)
eureka.instance.instance-id 实例在控制台显示的名字,可自定义 ${spring.cloud.client.ip-address}:${server.port}
依赖 引入方 说明
spring-cloud-starter-netflix-eureka-server 注册中心模块 提供 Server 能力,启动类加 @EnableEurekaServer
spring-cloud-starter-netflix-eureka-client 提供者与消费者 注册 + 发现一体,启动类 @EnableDiscoveryClient 可省

官方文档

总结

服务注册的动作就是两步:引客户端依赖 + 配服务名与注册中心地址 。凡是看到「引入 eureka-client 并配上 defaultZone」,就是在做注册这件事,跟它自报家门是提供者还是消费者身份无关。

几个值得记住的点:

  • spring.application.name 是后续 lb://、@FeignClient 匹配的字符串基础,写错了后患无穷,先定名再动代码。
  • 消费者也要注册,否则它没有拉取注册表的身份。
  • @EnableDiscoveryClient 在新版本可省,但老教程必加,别把两种结论混着用。
  • defaultZone 的 /eureka 后缀、两个 UP 实例、控制台里的 USERSERVICE 大写显示,这三个是验证注册是否成功最直观的抓手。

注册完成,下一步就是让 order-service 从注册表里按服务名拉取实例并发起远程调用了。

相关推荐
liangshanbo12151 小时前
请求拦截器相关面试题
java·前端·javascript
她说彩礼65万1 小时前
C语言 const的使用
java·服务器·c语言
开开心心就好1 小时前
视频压缩工具推荐,绿色版免安装双击即用
java·前端·人工智能·spring·智能手机·intellij-idea·excel
Wang's Blog1 小时前
Java框架 SpringCloud 快速入门: Eureka 注册中心的搭建
java·spring cloud·eureka
专业程序开发源1 小时前
springboot古城景区管理系统88564-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
泡茶喝茶写代码1 小时前
A股量化数据工程:从 REST 接口到策略信号(第 7 篇):盈利能力因子:毛利率、净利率与 ROE
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
小蒜学长2 小时前
基于微信小程序的遇见咖啡店点餐系统设计与实现(代码+数据库+LW)
java·spring boot·后端·微信小程序·咖啡店点餐系统
lisanmengmeng2 小时前
NRPE 添加命令(三)
java·linux·服务器
Wang's Blog2 小时前
Java框架 SpringCloud 快速入门: 网关的 CORS 跨域配置
java·开发语言·spring cloud