在微服务架构日益普及的今天,掌握服务注册与发现、远程调用以及负载均衡等核心技能,已成为 Java 开发者进阶的必经之路。本文将带你从零开始,实战搭建一个基于 Spring Cloud 的微服务项目,深入理解 Eureka、Ribbon 与 MySQL 的整合之道。无论你是初学者还是有一定经验的开发者,都能从中获得实用的架构设计思路与编码技巧。

一、项目背景与架构设计:从单体到微服务的演进

想象一下,如果我们将一个传统的单体应用拆分为多个独立部署的服务,它们之间如何互相通信?如何找到彼此?这正是 Spring Cloud 微服务要解决的核心问题。本文的实战项目将采用单父工程 + 多子模块的标准企业级结构,将职责清晰地划分为三个部分:服务注册中心、数据服务生产者、用户服务消费者。

这种设计模式的好处显而易见:模块间解耦、独立部署、易于扩展。在业务场景上,我们以实现一个简单的班级信息查询功能为切入点,串联起整个微服务链路。这种从简入繁的方式,能帮助你快速建立对微服务组件协作关系的整体认知。

springcloud-demo(父工程)
├─ servicecenter1(Eureka注册中心,端口8088):管理所有服务实例的注册、发现与状态监控
├─ dbservice(数据服务生产者,端口9091):操作MySQL数据库,提供标准化数据查询接口
└─ users(用户服务消费者,端口8025):远程调用dbservice,对外提供统一业务访问入口
  • 连接 MySQL,查询表数据,暴露接口;
  • 通过(整合 Ribbon)以服务名远程调用;
  • 所有服务均注册到,实现服务解耦与动态发现。

二、搭建 Eureka 服务注册中心:微服务的“通讯录”

Eureka 在微服务架构中扮演着服务注册中心的角色,它就像一本“通讯录”,记录着所有可用服务实例的 IP 和端口。当服务提供者启动时,会向 Eureka 登记自己的信息;当服务消费者需要调用某个服务时,只需查询这本“通讯录”即可,无需硬编码具体的地址。

首先,我们在 IDE 中创建一个空的父工程,并新建名为 servercenter1 的子模块作为注册中心。在 pom.xml 中,我们需要引入 Spring Cloud 的 Eureka Server 依赖,并配置 application.properties 文件。这里有一个关键配置:注册中心通常不需要将自己注册到服务列表,因此要设置 register-with-eureka=falsefetch-registry=false



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.18
        
    
    com.hy
    servercenter1
    0.0.1-SNAPSHOT
    servercenter1
    Eureka服务注册中心
    
        17
        2021.0.8
    
    
        
        
            org.springframework.boot
            spring-boot-starter
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-server
        
    
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
# 服务名称,唯一标识注册中心
spring.application.name=servercenter1
# Eureka服务端口
server.port=8088
# Eureka实例主机名
eureka.instance.hostname=localhost
# 关闭注册中心自身注册到Eureka(核心配置)
eureka.client.register-with-eureka=false
# 关闭注册中心从Eureka发现其他服务(核心配置)
eureka.client.fetch-registry=false
# Eureka注册地址,供其他服务注册使用
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
# 关闭Eureka自我保护机制(开发环境建议开启,生产环境关闭)
eureka.server.enable-self-preservation=false

启动类编写十分简洁,只需添加 @EnableEurekaServer 注解即可开启服务端功能。启动项目后,访问控制台地址,看到 No instances available 的提示,就说明注册中心已经准备就绪,正等待服务实例的加入。

@EnableEurekaServer@SpringBootApplication
package com.hy.servercenter1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
 * Eureka服务注册中心启动类
 * @EnableEurekaServer 开启Eureka服务端功能
 */
@EnableEurekaServer
@SpringBootApplication
public class Servercenter1Application {
    public static void main(String[] args) {
        SpringApplication.run(Servercenter1Application.class, args);
    }
}
http://localhost:8088

三、构建数据服务生产者(dbservice):连接数据库的桥梁

接下来,我们创建数据服务生产者 dbservice。它是整个链路中的数据层服务,负责与 MySQL 数据库进行交互。在开始编码前,请确保你的 MySQL 中已准备好相应的数据库表 t_classes 并插入了测试数据。这就像我们盖房子前要先打好地基一样,是数据交互的基础。

dbservice
-- 创建表 --
CREATE TABLE t_classes (
    cid INT PRIMARY KEY AUTO_INCREMENT,
    cname VARCHAR(20) NOT NULL,
    cphone VARCHAR(200)
);
-- 删除数据库表,用于先前如果创建好的表 --
DROP TABLE t_classes
-- 插入数据 --
INSERT INTO t_classes(cname,cphone) VALUES("鸿蒙班","13800013800");
INSERT INTO t_classes(cname,cphone) VALUES("Java班","13700013700");
INSERT INTO t_classes(cname,cphone) VALUES("仓颉班","13600013600");
-- 查询表 --
SELECT * FROM t_classes

pom.xml 中,我们需要引入 Eureka Client、Spring Web、MyBatis 和 MySQL 驱动等依赖。所有版本均由 Spring Cloud 父级依赖统一管理,避免了版本冲突的烦恼。配置文件中则需整合服务注册、数据库连接、MyBatis 映射三大核心配置。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.18
        
    
    com.hy
    dbservice
    0.0.1-SNAPSHOT
    dbservice
    数据服务生产者,操作MySQL数据库
    
        17
        2021.0.8
        2.2.2
        8.0.33
    
    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-ribbon
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            ${mybatis.version}
        
        
        
            com.mysql
            mysql-connector-j
            ${mysql.connector.version}
            runtime
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
# 服务名称,消费者通过该名称调用(核心,必须与调用名一致)
spring.application.name=dbservice
# 服务端口
server.port=9091
# 注册到Eureka的地址(与注册中心保持一致)
eureka.client.service-url.defaultZone=http://127.0.0.1:8088/eureka
# 服务实例过期时间,加快服务剔除
eureka.instance.lease-expiration-duration-in-seconds=30
# MySQL数据库连接配置(适配8.0+)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mysql2026
spring.datasource.username=root
spring.datasource.password=yourpassword
# 连接池大小
spring.datasource.hikari.maximum-pool-size=10
# MyBatis配置
# 实体类包扫描
mybatis.type-aliases-package=com.hy.dbservice.model
# 开启DAO层日志,方便调试SQL
logging.level.com.hy.dbservice.dao=debug

代码编写遵循经典的 MVC 分层思想。首先定义与数据库表字段一一对应的实体类 Classes,然后创建 MyBatis 的 DAO 层接口,这里我们使用注解方式实现 SQL 查询,无需编写繁琐的 XML 文件,非常适合简单查询场景。最后,在 Controller 层暴露一个 HTTP 接口,供外部调用。

package com.hy.dbservice.model;
/**
 * 班级实体类,映射t_classes表
 */
public class Classes {
    private int cid; // 班级ID
    private String cname; // 班级名称
    private String cphone; // 班级联系电话
    // Getter和Setter方法
    public int getCid() {
        return cid;
    }
    public void setCid(int cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public String getCphone() {
        return cphone;
    }
    public void setCphone(String cphone) {
        this.cphone = cphone;
    }
}
@Mapper
package com.hy.dbservice.dao;
import com.hy.dbservice.model.Classes;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * 数据访问层,操作t_classes表
 */
@Mapper
public interface DBDao {
    // 查询所有班级信息
    @Select("select * from t_classes")
    List queryClassesInfo();
}
/querydata
package com.hy.dbservice.controller;
import com.hy.dbservice.dao.DBDao;
import com.hy.dbservice.model.Classes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * 数据服务控制器,提供数据查询接口
 * @RestController 标记为REST风格控制器,返回JSON数据
 */
@RestController
public class DBController {
    // 自动注入DAO层对象
    @Autowired
    private DBDao dbDao;
    // 对外提供的班级信息查询接口
    @RequestMapping("/querydata")
    public List queryData() {
        System.out.println("DBController is queryData start... ");
        // 调用DAO层查询数据库
        List lists = dbDao.queryClassesInfo();
        return lists;
    }
}
package com.hy.dbservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
 * 数据服务生产者启动类
 * @EnableDiscoveryClient 开启Eureka客户端,实现服务注册与发现
 */
@EnableDiscoveryClient
@SpringBootApplication
public class DbserviceApplication {
    public static void main(String[] args) {
        SpringApplication.run(DbserviceApplication.class, args);
    }
}

启动生产者服务,观察控制台日志,当看到注册成功的提示后,说明数据服务已经成功挂载到 Eureka 注册中心,随时准备响应消费者的请求。

  • 确保 Eureka 注册中心已启动;
  • 运行,无报错即启动成功;
  • 访问 Eureka 控制台,可看到DBSERVICE服务状态为UP,说明注册成功;
  • 直接访问接口,页面返回 JSON 格式的班级数据,说明数据库交互和接口正常。

四、打造用户服务消费者(users):远程调用的发起者

最后,我们来创建用户服务消费者 users。这个服务扮演着应用层服务的角色,它不直接操作数据库,而是通过 RestTemplate 以服务名的方式远程调用 dbservice 的接口。这种设计实现了服务的完全解耦,消费者无需关心提供者的具体 IP 和端口。

usersRestTemplatedbservice

在消费者项目中,依赖配置相对简单,只需 Eureka Client 和 Spring Web。这里有个关键的配置类 RestTemplateConfig,它将 RestTemplate 的 Bean 定义独立出来,并添加 @LoadBalanced 注解,这不仅是解决循环依赖的标准写法,更是开启 Ribbon 客户端负载均衡的钥匙。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.18
        
    
    com.hy
    users
    0.0.1-SNAPSHOT
    users
    用户服务消费者,远程调用数据服务
    
        17
        2021.0.8
    
    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-ribbon
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
# 服务名称
spring.application.name=users
# 服务端口
server.port=8025
# 注册到Eureka的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8088/eureka
# 开启服务发现
eureka.client.fetch-registry=true
package com.hy.users.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
 * 全局Bean配置类,避免Bean循环依赖
 */
@Configuration // 标记为配置类,Spring会扫描并执行内部@Bean方法
public class RestTemplateConfig {
    // 定义RestTemplate Bean,与UserController解耦
    @Bean
    @LoadBalanced // 保留负载均衡功能
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
RestTemplate@LoadBalanced

消费者需要创建一个与生产者字段完全一致Classes 实体类,用于接收远程调用返回的 JSON 数据。在 Controller 层,我们注入 RestTemplate,通过服务名 dbservice 拼接 URL 发起调用,对外暴露统一的访问入口。

package com.hy.users.model;
/**
 * 班级实体类,与dbservice中的实体类保持一致
 */
public class Classes {
    private int cid;
    private String cname;
    private String cphone;
    // Getter和Setter方法
    public int getCid() {
        return cid;
    }
    public void setCid(int cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public String getCphone() {
        return cphone;
    }
    public void setCphone(String cphone) {
        this.cphone = cphone;
    }
}
package com.hy.users.controller;
import com.hy.users.model.Classes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
 * 用户服务控制器,远程调用dbservice并对外提供接口
 */
@RestController
public class UserController {
    // 注入配置好的RestTemplate
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 配置RestTemplate Bean
     * @LoadBalanced 开启客户端负载均衡,支持通过服务名调用
     */
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    /**
     * 对外提供的统一查询接口,内部远程调用dbservice
     * @return 班级信息列表
     */
    @RequestMapping("/query")
    public List loadData() {
        System.out.println("UserController is loadData start...");
        // 核心:通过服务名dbservice远程调用,由Eureka发现服务,Ribbon实现负载均衡
        List lists = this.restTemplate.getForObject("http://dbservice/querydata", List.class);
        return lists;
    }
}
/query
package com.hy.users;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
 * 用户服务消费者启动类
 */
@EnableDiscoveryClient
@SpringBootApplication
public class UsersApplication {
    public static void main(String[] args) {
        SpringApplication.run(UsersApplication.class, args);
    }
}
@EnableDiscoveryClientdbservice

五、全流程测试与 Ribbon 负载均衡验证

所有模块开发完成后,我们需要进行整体联调。⚠️ 请注意严格的启动顺序:先启动 Eureka 注册中心,再启动生产者,最后启动消费者。这样可以确保每个服务在启动时都能顺利找到注册中心并完成注册,避免因顺序问题导致的注册失败。

  • 第一步:启动 Eureka 注册中心 (8088)
  • 第二步:启动数据服务生产者 (9091)
  • 第三步:启动用户服务消费者 (8025)

启动完毕后,访问 Eureka 控制台,你应该能看到 DBSERVICEUSERS 两个服务均处于 UP 状态。此时,通过浏览器访问消费者提供的接口,即可看到从 MySQL 数据库返回的班级信息 JSON 数据,这标志着从消费者接口到生产者接口再到数据库的完整链路已成功打通

http://localhost:8088http://localhost:8025/query
  • 消费者控制台:打印
  • 生产者控制台:打印 + DAO 层 SQL 执行日志

为了验证 Ribbon 的负载均衡能力,我们可以将 dbservice 启动多个实例(通过修改启动配置中的端口号)。再次多次访问消费者接口,观察控制台日志,你会发现请求被轮询分发到了不同的生产者实例上,这正是客户端负载均衡的魅力所在。 这种机制极大地提升了系统的吞吐量和可用性,是微服务架构中不可或缺的一环。

  • 打开的 IDEA 运行配置,勾选Allow multiple instances(允许多实例运行);
  • 修改的中为 9092,再次启动;
  • 访问 Eureka 控制台,可看到有2 个实例(9091/9092),均为 UP 状态;
  • 多次刷新消费者接口,生产者 9091 和 9092 的控制台会交替打印日志,说明 Ribbon 负载均衡生效,请求在多个生产者实例间轮询分发。

至此,一个完整的 Spring Cloud 微服务实战项目已全部完成。从架构设计到编码实现,再到最后的负载均衡验证,相信你已经对 Eureka 服务治理、RestTemplate 远程调用以及 Ribbon 负载均衡有了更深刻的理解。这些技能不仅在 Java 技术栈中至关重要,其思想也与 TypeScript、Go 等语言中的微服务框架有异曲同工之妙。希望你能动手实践,将这些知识真正内化为自己的竞争力。

dbservicet_classes/querydatausersRestTemplatedbserviceservicecenter1DbserviceApplicationhttp://localhost:8088http://localhost:9091/querydataservercenter1dbserviceusersusersUserController is loadData start...dbserviceDBController is queryData start... dbservicedbserviceapplication.propertiesserver.portdbservicedbservicehttp://localhost:8025/query