Spring Cloud 负载均衡(LoadBalancer)与服务调用(OpenFeign)详解

Spring Cloud 负载均衡(LoadBalancer)与服务调用(OpenFeign)详解(初学者指南)

本文将聚焦 "多实例分流"(LoadBalancer)"简化服务调用"(OpenFeign) 两大核心能力 ------ 这两项是微服务从 "单实例运行" 走向 "高可用、易维护" 的关键。本文会从 "为什么需要""是什么""怎么实现""背后逻辑" 逐步拆解,同时关联之前熟悉的 ServiceOne、ServiceThree 等案例,帮你建立完整的微服务调用链路认知。

1. 负载均衡 LoadBalancer:解决多实例 "分流" 问题

当一个服务(如 ServiceOne)单实例扛不住压力(比如双 11 订单量激增)时,我们会部署多个实例(比如 ServiceOne+ServiceOneCopy),而LoadBalancer 的作用就是把用户请求 "均匀分给" 这些实例,避免单个实例忙死、其他实例空闲

1.1 先搞懂:为什么需要负载均衡?

回顾之前的案例,ServiceOne 只有 1 个实例(端口 8001),所有请求都会打到这台机器上 ------ 这会有两个致命问题:

  1. 性能瓶颈:如果每秒有 1000 个请求,单实例只能处理 500 个,剩下的 500 个会超时失败;

  2. 单点故障:这个实例宕机了,所有依赖 ServiceOne 的服务(如 ServiceThree)都会调用失败。

而负载均衡就是解决这两个问题的 "方案":

  • 部署多个实例(如 ServiceOne:8001、ServiceOneCopy:8004),总处理能力提升到 1000+;

  • 用 LoadBalancer 把请求 "轮询" 或 "随机" 分给多个实例,即使一个实例宕机,其他实例还能继续工作。

生活类比:火车站检票 ------ 单个人工窗口(单实例)会排长队,开多个窗口(多实例),再安排引导员(LoadBalancer)分流,乘客就能快速检票(请求快速处理)。

1.2 什么是 Spring Cloud LoadBalancer?

  • 定位 :Spring Cloud 官方开发的客户端负载均衡器,用来替代停更的 Netflix Ribbon;

  • "客户端" 含义:负载均衡逻辑在 "服务消费者"(如 ServiceThree)这边,而不是在独立的服务器上;

  • 核心能力:

    1. 从 Nacos 获取目标服务的所有实例列表(如 service-one 的 8001 和 8004 实例);

    2. 按默认算法(轮询:依次分给每个实例)对请求进行分流;

    3. 自动排除不健康的实例(如 8001 宕机,就只分给 8004)。

1.3 LoadBalancer 实战:从配置到验证(基于之前的案例扩展)

该案例是在之前的 "父项目 + ServiceOne/Two/Three" 基础上,新增 ServiceOneCopy 实例,修改 ServiceThree 实现负载均衡。我们一步步拆解:

步骤 1:新建 "分流实例" ServiceOneCopy(关键:服务名必须和 ServiceOne 一致)

要实现负载均衡,多个实例必须用同一个服务名(Nacos 按服务名找实例列表),所以需要复制 ServiceOne 生成 ServiceOneCopy,并做 3 处关键修改:

注意:拷贝module模块文件夹比较麻烦,建议直接新建module模块再逐步拷贝相关代码

1.1 复制 ServiceOne 的所有内容

把 ServiceOne 的pom.xmlapplication.yml、主类(ServiceOneApplication)、控制器(ServiceOneController)全部复制到 ServiceOneCopy 子项目。

1.2 修改 ServiceOneCopy 的 application.yml(仅改端口,服务名不变)
复制代码
 server:
   port: 8004  # 关键:同一台机器部署,端口必须和ServiceOne(8001)不同,避免冲突
 spring:
   application:
     name: service-one  # 核心:服务名必须和ServiceOne完全一致!否则Nacos不认作同一服务
   cloud:
     nacos:
       discovery:
         server-addr: 127.0.0.1:8848  # 和ServiceOne一致,指向同一个Nacos
         service: ${spring.application.name}  # 复用服务名,不用改
  • 为什么服务名必须一致?

    LoadBalancer 的逻辑是:"根据服务名从 Nacos 拉取所有实例"------ 如果 ServiceOneCopy 的服务名是service-one-copy,Nacos 会把它当成另一个服务,无法和 ServiceOne 一起分流。

1.3 修改主类名(方便 IDEA 区分启动项)

把复制来的ServiceOneApplication改名为ServiceOneCopyApplication(仅名称变,代码不变):

复制代码
@SpringBootApplication
 @EnableDiscoveryClient
 public class ServiceOneCopyApplication {  // 改这里的类名
     public static void main(String[] args) {
         SpringApplication.run(ServiceOneCopyApplication.class, args);
     }
 }
1.4 修改控制器返回值(方便验证分流效果)

ServiceOneCopyControllerserviceOne方法中,给message加 "COPY" 字样,这样调用时能区分是哪个实例返回的:

复制代码
 @RestController
 public class ServiceOneController {
     @RequestMapping("/serviceOne")
     public JSONObject serviceOne(){
         JSONObject ret = new JSONObject();
         ret.put("code", 0);
         // 关键:加"COPY"标识,和ServiceOne的返回区分
         ret.put("message", "Service one \"COPY\" method return!");
         return ret;
     }
 }
步骤 2:修改 ServiceThree(开启负载均衡能力)

ServiceThree 是调用方,需要新增负载均衡依赖、配置 RestTemplate、修改调用方式:

2.1 引入 LoadBalancer 依赖(pom.xml)

在 ServiceThree 的pom.xml中添加负载均衡 starter,让 Spring Cloud 提供负载均衡能力:

html 复制代码
 <!-- 负载均衡核心依赖:给RestTemplate赋予分流能力 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-loadbalancer</artifactId>
 </dependency>
2.2 给 RestTemplate 加 @LoadBalanced 注解(主类)

之前我们在 ServiceThree 主类中配置了RestTemplate,现在需要加@LoadBalanced注解 ------ 这个注解会 "增强" RestTemplate,让它能自动通过服务名找实例并分流:

java 复制代码
 @SpringBootApplication
 @EnableDiscoveryClient
 public class ServiceThreeApplication {
     @Bean
     @LoadBalanced  // 关键:开启RestTemplate的负载均衡能力
     public RestTemplate getRestTemplate(){
         return new RestTemplate();
     }
 ​
     public static void main(String[] args) {
         SpringApplication.run(ServiceThreeApplication.class, args);
     }
 }
  • @LoadBalanced 的作用:

    没有这个注解时,RestTemplate 只能通过 "IP + 端口" 调用(如http://127.0.0.1:8001/serviceOne);加了之后,它能解析 "服务名"(如http://service-one/serviceOne),自动去 Nacos 查service-one的所有实例,然后按轮询算法选一个实例调用。

2.3 修改调用方式:从 "IP + 端口" 改为 "服务名"(控制器)

之前我们用DiscoveryClient获取实例 IP + 端口,现在不用了 ------ 直接用服务名service-one拼接 URL,@LoadBalanced会帮你处理剩下的:

复制代码
 
java 复制代码
@RestController
 public class ServiceThreeController {
     @Autowired
     private RestTemplate restTemplate;
 ​
     @RequestMapping("/serviceThree_toOne")
     public JSONObject serviceThree_toOne(){
         // 关键:URL用服务名"service-one"替代IP+端口
         String serviceOneUrl = "http://service-one/serviceOne";
         // RestTemplate会自动找service-one的实例,分流调用
         String strRet = restTemplate.getForObject(serviceOneUrl, String.class);
 ​
         JSONObject ret = new JSONObject();
         ret.put("code", 0);
         ret.put("message", "Service three to one method return!");
         ret.put("data", strRet);
         return ret;
     }
 ​
     // 其他方法(serviceThree_toTwo、serviceThree)不变...
 }
  • 为什么不用 DiscoveryClient 了?

    因为@LoadBalanced已经封装了 "从 Nacos 获取实例列表" 的逻辑,不用自己写discoveryClient.getInstances("service-one")

    和选实例的代码,简化开发。

步骤 3:结果验证:看负载均衡是否生效

按以下顺序启动服务,然后验证:

3.1 启动顺序(重要)
  1. 启动 Nacos Server(确保 8848 端口可用);

  2. 启动 ServiceOne(8001)和 ServiceOneCopy(8004);

  3. 启动 ServiceThree(8003)。

3.2 查看 Nacos 服务列表

访问http://127.0.0.1:8848/nacos,在 "服务列表" 中查看service-one------实例数会变成 2(一个 8001,一个 8004),说明两个实例都注册成功了:

服务名 实例数 健康实例数
service-one 2 2
service-three 1 1
3.3 调用接口验证分流

访问 ServiceThree 的/serviceThree_toOne接口(http://localhost:8003/serviceThree_toOne),连续调用两次:

  • 第一次返回(来自 ServiceOne:8001):

    复制代码
     {
       "code": 0,
       "message": "Service three to one method return!",
       "data": "{\"code\":0,\"message\":\"Service one method return!\"}"
     }
  • 第二次返回(来自 ServiceOneCopy:8004):

    复制代码
     {
       "code": 0,
       "message": "Service three to one method return!",
       "data": "{\"code\":0,\"message\":\"Service one \\\"COPY\\\" method return!\"}"
     }
  • 结论:两次调用返回不同结果,证明 LoadBalancer 按 "轮询" 算法把请求分给了两个实例,负载均衡生效!

1.4 LoadBalancer 核心逻辑:它是怎么工作的?

用流程图帮你理解 ServiceThree 调用 ServiceOne 时,LoadBalancer 的完整流程:

  • 默认算法:轮询(依次选实例),Spring Cloud LoadBalancer 目前还支持 "随机" 算法,可通过配置修改。

2. OpenFeign:让服务调用像 "调用本地方法" 一样简单

虽然 LoadBalancer 解决了分流问题,但用RestTemplate调用仍有痛点:

  • 硬编码 URL(如http://service-one/serviceOne),接口改了要手动改 URL;

  • 没有统一的接口管理,多个地方调用同一个接口时,改接口要改多处;

  • 需要自己处理请求参数和响应解析(如把 String 转 JSONObject)。

OpenFeign 的出现就是为了解决这些问题 ------ 它是 "声明式服务调用框架",让你通过 "接口 + 注解" 的方式调用远程服务,就像调用本地方法一样,不用写RestTemplate的代码。

2.1 为什么需要 OpenFeign?(对比 RestTemplate)

用表格直观展示两者的差异,你就懂 OpenFeign 的优势了:

对比维度 RestTemplate(之前用的) OpenFeign(现在要学的)
调用方式 硬编码 URL,手动调用getForObject 定义接口,注入后像本地方法一样调用
接口管理 无统一管理,改接口要改所有调用处 接口集中定义,改接口只改一处
参数 / 响应处理 手动处理(如 String 转 JSONObject) 自动解析,支持 JSON/JavaBean
负载均衡集成 需要手动加@LoadBalanced 默认集成 LoadBalancer,不用额外配置
代码简洁度 代码多,冗余 代码少,仅需定义接口

举个例子:之前用 RestTemplate 调用 ServiceOne:

java 复制代码
 // 硬编码URL
 String url = "http://service-one/serviceOne";
 // 手动调用+解析
 String strRet = restTemplate.getForObject(url, String.class);
 JSONObject data = JSONObject.parseObject(strRet);

用 OpenFeign 调用 ServiceOne:

java 复制代码
 // 注入Feign接口
 @Autowired
 private ServiceOneFeign serviceOneFeign;
 ​
 // 像调用本地方法一样调用
 JSONObject data = serviceOneFeign.serviceOne();

2.2 什么是 OpenFeign?

  • 定位 :Spring Cloud 官方推出的声明式服务调用框架,替代停更的 Netflix Feign;

  • 核心特点:

    1. 声明式:只定义接口,不用写实现类(Spring 会动态生成代理类);

    2. 兼容 Spring MVC 注解:支持@RequestMapping@GetMapping等,不用学新注解;

    3. 自动集成负载均衡:默认用 Spring Cloud LoadBalancer,不用额外加@LoadBalanced

    4. 自动解析请求 / 响应:支持 JSON、JavaBean 等格式,不用手动转。

2.3 OpenFeign 实战:从配置到调用(基于 ServiceThree 改造)

案例是在 LoadBalancer 的基础上,用 OpenFeign 替代 RestTemplate,简化 ServiceThree 的调用逻辑:

步骤 1:修改 ServiceThree 的 pom.xml(引入 OpenFeign 依赖)

在 ServiceThree 的pom.xml中添加 OpenFeign starter:

java 复制代码
 <!-- OpenFeign核心依赖:提供声明式调用能力 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>
  • 注意 :OpenFeign 默认集成了 LoadBalancer,所以不用再单独引入spring-cloud-starter-loadbalancer(如果之前加了也没关系,不冲突)。
步骤 2:开启 OpenFeign 功能(主类加注解)

在 ServiceThree 的主类上添加@EnableFeignClients注解 ------ 这个注解会让 Spring 扫描所有带@FeignClient的接口,生成代理类并交给 Spring 容器管理:

java 复制代码
 @SpringBootApplication
 @EnableDiscoveryClient
 @EnableFeignClients  // 关键:开启OpenFeign功能
 public class ServiceThreeApplication {
     // 注意:用了OpenFeign后,RestTemplate不用了,可以注释掉
     // @Bean
     // @LoadBalanced
     // public RestTemplate getRestTemplate(){
     //     return new RestTemplate();
     // }
 ​
     public static void main(String[] args) {
         SpringApplication.run(ServiceThreeApplication.class, args);
     }
 }
  • 为什么注释 RestTemplate?

    OpenFeign 已经封装了服务调用和负载均衡的逻辑,不用再手动用 RestTemplate 了,代码更简洁。

步骤 3:编写 Feign 接口(核心:映射服务提供者的接口)

Feign 接口的作用是 "映射服务提供者的接口"------ 接口名、路径、参数、返回值必须和服务提供者(如 ServiceOne)完全一致,这样 Spring 才能生成正确的 HTTP 请求。

3.1 新建 Feign 接口包和接口

在 ServiceThree 的com.zh.three包下新建feign子包,然后创建两个接口:ServiceOneFeign(映射 ServiceOne)和ServiceTwoFeign(映射 ServiceTwo)。

3.2 编写 ServiceOneFeign 接口(映射 ServiceOne 的 /serviceOne)
java 复制代码
 package com.zh.three.feign;
 ​
 import com.alibaba.fastjson.JSONObject;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.RequestMapping;
 ​
 // @FeignClient:指定要调用的服务名(必须和ServiceOne的spring.application.name一致)
 @FeignClient("service-one")
 public interface ServiceOneFeign {
     // 方法定义:必须和ServiceOne的Controller方法完全一致(路径、参数、返回值)
     @RequestMapping("/serviceOne")  // 路径和ServiceOne的接口路径一致
     JSONObject serviceOne();  // 返回值和ServiceOne的接口返回值一致
 }
  • @FeignClient("service-one"):告诉 OpenFeign "这个接口对应 Nacos 中服务名为 service-one 的服务"。

  • 方法必须和服务提供者一致 :如果 ServiceOne 的接口路径改了(如从/serviceOne改成/api/serviceOne),这里的@RequestMapping也要跟着改,否则会 404。

3.3 编写 ServiceTwoFeign 接口(映射 ServiceTwo 的 /serviceTwo)

逻辑和ServiceOneFeign完全一致,只是服务名和接口路径对应 ServiceTwo:

java 复制代码
package com.zh.three.feign;
 ​
 import com.alibaba.fastjson.JSONObject;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.RequestMapping;
 ​
 @FeignClient("service-two")  // 服务名对应ServiceTwo的spring.application.name
 public interface ServiceTwoFeign {
     @RequestMapping("/serviceTwo")  // 路径对应ServiceTwo的接口路径
     JSONObject serviceTwo();  // 返回值对应ServiceTwo的接口返回值
 }
步骤 4:在 Controller 中注入 Feign 接口调用(替代 RestTemplate)

修改 ServiceThree 的 Controller,删除 RestTemplate 相关代码,直接注入 Feign 接口调用:

java 复制代码
@RestController  // 之前是@Controller,这里用@RestTemplate更简洁,不用加@ResponseBody
 public class ServiceThreeController {
     // 1. 注入ServiceOneFeign接口(Spring自动生成代理类)
     @Autowired
     private ServiceOneFeign serviceOneFeign;
 ​
     // 2. 注入ServiceTwoFeign接口
     @Autowired
     private ServiceTwoFeign serviceTwoFeign;
 ​
     // 调用ServiceOne:像调用本地方法一样
     @RequestMapping("/serviceThree_toOne")
     public JSONObject serviceThree_toOne(){
         // 关键:直接调用Feign接口方法,不用写RestTemplate和URL
         JSONObject serviceOneRet = serviceOneFeign.serviceOne();
 ​
         JSONObject ret = new JSONObject();
         ret.put("code", 0);
         ret.put("message", "Service three to one method return!");
         ret.put("data", serviceOneRet);
         return ret;
     }
 ​
     // 调用ServiceTwo:同理
     @RequestMapping("/serviceThree_toTwo")
     public JSONObject serviceThree_toTwo(){
         JSONObject serviceTwoRet = serviceTwoFeign.serviceTwo();
 ​
         JSONObject ret = new JSONObject();
         ret.put("code", 0);
         ret.put("message", "Service three to two method return!");
         ret.put("data", serviceTwoRet);
         return ret;
     }
 ​
     // 自身接口不变
     @RequestMapping("/serviceThree")
     public JSONObject serviceThree(){
         JSONObject ret = new JSONObject();
         ret.put("code", 0);
         ret.put("message", "Service three method return!");
         return ret;
     }
 }
步骤 5:结果验证:OpenFeign 是否生效?

和 LoadBalancer 的验证步骤一致,启动所有服务后调用接口:

5.1 验证负载均衡(依然生效)

访问http://localhost:8003/serviceThree_toOne,连续两次:

  • 第一次返回 ServiceOne(8001)的结果;

  • 第二次返回 ServiceOneCopy(8004)的结果;

  • 结论:OpenFeign 默认集成了 LoadBalancer,所以负载均衡依然生效,不用额外配置。

5.2 验证 ServiceTwo 调用

访问http://localhost:8003/serviceThree_toTwo,返回 ServiceTwo 的结果:

复制代码
 {
   "code": 0,
   "message": "Service three to two method return!",
   "data": {
     "code": 0,
     "message": "Service two method return!"
   }
 }
  • 结论:Feign 接口正确映射 ServiceTwo 的接口,调用成功。

2.4 OpenFeign 核心逻辑:动态代理是关键

你可能会好奇:Feign 接口没有实现类,为什么能调用?核心是动态代理------Spring 启动时会给 Feign 接口生成一个 "代理实现类",代理类里封装了所有服务调用的逻辑:

3. 重点 & 易错点总结(初学者必看)

这部分是我们后续编码时最容易踩坑的地方,务必牢记:

3.1 LoadBalancer 重点 & 易错点

重点 / 易错点 说明 解决方案
多实例服务名必须一致 负载均衡按服务名找实例,若 ServiceOne 和 ServiceOneCopy 的服务名不同,Nacos 会当两个服务,无法分流 确保所有实例的spring.application.name完全一致(如都为service-one
@LoadBalanced 注解不能漏 没加这个注解,RestTemplate 无法解析服务名,会报 "未知主机" 错误 给 RestTemplate 的 @Bean 方法加@LoadBalanced
端口不能冲突 同一台机器部署多个实例,端口必须不同,否则启动失败 每个实例的server.port单独设置(如 8001、8004)
实例必须健康 Nacos 会剔除不健康的实例,若实例宕机,LoadBalancer 不会分给它请求 确保所有实例启动成功,Nacos 中 "健康实例数" 等于 "实例数"

3.2 OpenFeign 重点 & 易错点

重点 / 易错点 说明 解决方案
@EnableFeignClients 必须加 没加这个注解,Spring 不会扫描 Feign 接口,注入时会报 "找不到 Bean" 错误 在 ServiceThree 主类上添加@EnableFeignClients
Feign 接口方法必须和服务提供者一致 路径、参数、返回值有一个不一致,会报 404 或解析错误 严格对照服务提供者的 Controller 方法,确保:1. @RequestMapping路径一致;2. 参数个数和类型一致;3. 返回值类型一致(如都是 JSONObject)
@FeignClient 的服务名不能错 服务名错了,OpenFeign 找不到对应的服务,会报 "服务不可用" 错误 确保@FeignClient的值和服务提供者的spring.application.name一致(如service-one
不用手动加 LoadBalancer 依赖 OpenFeign 默认集成了 LoadBalancer,重复加依赖不报错,但冗余 只加spring-cloud-starter-openfeign即可,不用额外加spring-cloud-starter-loadbalancer

4. 与之前案例的关联关系(梳理完整链路)

结合我们之前学的 Nacos、ServiceOne/Two/Three,现在整个微服务调用链路已经完整了:

  1. 服务注册 :ServiceOne、ServiceOneCopy、ServiceTwo 启动后,注册到 Nacos(服务名分别为service-oneservice-oneservice-two);

  2. 服务发现与分流:ServiceThree 通过 OpenFeign(集成 LoadBalancer),按服务名从 Nacos 获取实例列表,然后轮询分流;

  3. 服务调用 :ServiceThree 调用service-one时,OpenFeign 自动找实例并发起请求,像调用本地方法一样简单;

  4. 动态配置:后续若要修改 ServiceOne 的配置(如数据库地址),可放到 Nacos 配置中心,不用重启实例(之前学的 Nacos 配置中心能力)。

完整链路图

5. 后续学习方向

掌握了 LoadBalancer 和 OpenFeign 后,下一步可以学习:

  1. 服务熔断降级(Sentinel):当 ServiceOne 所有实例都宕机时,ServiceThree 调用不会一直等,而是返回 "友好提示"(如 "服务暂时不可用"),避免 ServiceThree 也挂掉;

  2. 服务网关(Spring Cloud Gateway):所有客户端请求先过网关,统一处理鉴权、路由、限流,不用每个服务都写鉴权逻辑;

  3. 分布式事务(Seata):当 ServiceThree 调用 ServiceOne 和 ServiceTwo 时,确保两个服务的操作要么全成功,要么全失败(如下单时扣库存和创建订单必须同时成功)。

通过此次学习,希望你能理解:LoadBalancer 解决 "多实例分流" 的性能问题,OpenFeign 解决 "服务调用繁琐" 的开发效率问题 ------ 两者结合,让微服务既高效又易用。

相关推荐
龙茶清欢3 小时前
4、除了常见的 services(业务微服务)和 gateway(API 网关)模块外,还必须建立一系列支撑性、平台级、基础设施类模块
微服务·架构·gateway
pccai-vip6 小时前
系分论文《论微服务架构在电商平台重构项目中的应用》
微服务·重构·架构
懒惰蜗牛7 小时前
Day24 | Java泛型通配符与边界解析
java·后端·java-ee
纯真时光8 小时前
微服务(Spring Cloud)-- 02
spring cloud
熙客9 小时前
Session与JWT安全对比
java·分布式·安全·web安全·spring cloud
我命由我1234510 小时前
Android 实例 - Android 圆形蒙版(Android 圆形蒙版实现、圆形蒙版解读)
android·java·java-ee·android studio·安卓·android-studio·android runtime
王道长服务器 | 亚马逊云11 小时前
AWS Route 53 详解:不只是 DNS,还能做智能流量调度
服务器·网络·微服务·云原生·架构·云计算·aws
纯真时光11 小时前
微服务(Spring Cloud)-- 01
微服务
程序员小潘11 小时前
Spring Gateway动态路由实现方案
后端·spring cloud