一、负载均衡 Ribbon
1.1 什么是负载均衡
通俗来讲,负载均衡就是将工作任务、访问请求等负担,分摊到多个服务器、组件等操作单元上执行,核心目标是提升系统吞吐量、降低单节点压力。
举个简单例子:将服务消费者(consumer)的请求平均分发到多台服务提供者(provider)上,避免单一提供者过载。
1.2 自定义实现负载均衡
1.2.1 创建服务提供者
(1)创建工程
拷贝已有的nacos_provider工程,准备搭建多实例的服务提供者。
(2)配置 application.yml
创建两个不同端口的配置文件,注册到同一个 Nacos 服务中心:
- 实例 1(9090 端口):
yaml:
server:
port: 9090
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
application:
name: ribbon-provider
- 实例 2(9091 端口):
yaml:
server:
port: 9091
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
application:
name: ribbon-provider
1.2.2 创建服务消费者
(1)创建工程
拷贝已有的nacos_consumer工程,作为负载均衡的消费端。
(2)配置 application.yml
yaml:
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
application:
name: ribbon-consumer
(3)编写 Controller 实现负载均衡
通过DiscoveryClient获取服务列表,手动实现随机 / 轮询策略:
java
package com.hg.controller;
import com.hg.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
private int currentIndex;
@RequestMapping(value="/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
// 获取服务提供者列表
List<ServiceInstance> serviceList = discoveryClient.getInstances("ribbon-provider");
// 随机策略:int currentIndex = new Random().nextInt(serviceList.size());
// 轮询策略
currentIndex = (currentIndex + 1) % serviceList.size();
ServiceInstance instance = serviceList.get(currentIndex);
String serviceUrl = instance.getHost() + ":" + instance.getPort();
System.out.println("serviceUrl:"+serviceUrl);
String url = "http://"+serviceUrl+"/provider/getUserById/"+id;
return restTemplate.getForObject(url, User.class);
}
}
1.2.3 测试
分别启用随机 / 轮询策略,调用接口可看到请求被分发到 9090/9091 不同端口的提供者实例。
1.3 Ribbon 核心介绍
1.3.1 什么是 Ribbon
- Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的消费端负载均衡工具,无需手动引入依赖(Nacos 已集成)。
- Ribbon 默认提供多种负载均衡算法(轮询、随机等),开箱即用。
1.3.2 负载均衡策略
Ribbon 的核心负载均衡接口为com.netflix.loadbalancer.IRule,核心实现类:
| 策略类 | 功能说明 |
|---|---|
RandomRule |
从服务清单中随机选择一个实例 |
RoundRobinRule |
按照线性轮询方式依次选择实例 |
1.4 基于 Ribbon 实现负载均衡
1.4.1 修改消费者配置
(1)配置 RestTemplate(开启 Ribbon)
java
package com.hg.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced // 开启Ribbon负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
// 自定义负载均衡策略(示例:随机)
@Bean
public IRule iRule() {
return new RandomRule();
}
}
(2)简化 Controller 调用
无需手动获取服务列表,直接通过服务名调用:
java
package com.hg.controller;
import com.hg.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable Integer id) {
// 直接使用服务名,Ribbon自动解析并负载均衡
String serviceUrl = "ribbon-provider";
return restTemplate.getForObject("http://" + serviceUrl + "/provider/getUserById/" + id, User.class);
}
}
1.4.2 测试
启动多实例提供者和消费者,调用接口可看到 Ribbon 自动按配置的策略(随机 / 轮询)分发请求。
二、声明式服务调用 Feign
2.1 背景
使用RestTemplate调用远程接口时,需手动拼接 URL 和参数,参数多时代码繁琐、易出错。Feign 解决了这一问题,让远程调用像调用本地方法一样简单。
2.2 Feign 概述
- Feign 是 Spring Cloud 提供的声明式、模板化 HTTP 客户端,支持 Spring MVC 注解。
- Feign 默认集成 Ribbon,开箱即支持负载均衡。
2.3 Feign 快速入门
2.3.1 搭建服务提供者
复用 Ribbon 的提供者工程,修改服务名为feign-provider(9090 端口):
yaml:
server:
port: 9090
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
application:
name: feign-provider
2.3.2 创建 Feign 接口工程
(1)pom.xml 依赖
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>springcloud_parent</artifactId>
<groupId>com.hg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign_interface</artifactId>
<dependencies>
<!-- OpenFeign核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 公共实体类依赖 -->
<dependency>
<groupId>com.hg</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(2)编写 Feign 接口
java
package com.hg.feign;
import com.hg.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
// 指定要调用的服务名
@FeignClient(value="feign-provider")
@RequestMapping(value = "/provider")
public interface UserFeign {
// 与提供者接口保持一致
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable(value="id") Integer id);
}
2.3.3 搭建 Feign 消费者
(1)pom.xml 依赖
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>springcloud_parent</artifactId>
<groupId>com.hg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入Feign接口工程 -->
<dependency>
<groupId>com.hg</groupId>
<artifactId>feign_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(2)application.yml 配置
yaml:
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
application:
name: feign-consumer
(3)编写 Controller
java
package com.hg.controller;
import com.hg.feign.UserFeign;
import com.hg.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private UserFeign userFeign;
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable Integer id) {
// 像调用本地方法一样调用远程接口
return userFeign.getUserById(id);
}
}
(4)启动类开启 Feign
java
package com.hg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现
@EnableFeignClients // 开启Feign扫描
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class);
}
}
2.3.4 测试
启动提供者、消费者,调用http://localhost/consumer/getUserById/1,可正常获取数据,且 Feign 自动集成 Ribbon 实现负载均衡。
2.4 Feign 核心原理
- 接口注入容器 :
@EnableFeignClients触发 Feign 扫描,FeignClientsRegistrar扫描@FeignClient注解的接口,生成动态代理类并注入 Spring 容器。 - 封装请求模板 :调用 Feign 接口方法时,JDK 动态代理创建
RequestTemplate,封装 URL、参数、请求方式等信息。 - 发起请求 :
SynchronousMethodHandler将RequestTemplate转为Request,结合 Ribbon 负载均衡,通过 Client(URLConnection/HttpClient 等)发起远程调用。
2.5 Feign 参数传递
| 参数类型 | 注解使用 | 示例 |
|---|---|---|
| Restful 风格 | @PathVariable |
@RequestMapping("/getUserById/{id}") User getUserById(@PathVariable("id") Integer id); |
| URL 拼接参数 | @RequestParam |
@RequestMapping("/getUser") User getUser(@RequestParam("name") String name); |
| POJO 参数 | @RequestBody |
提供者:@PostMapping("/addUser") void addUser(@RequestBody User user);Feign 接口:同提供者注解 |
2.6 Feign 请求超时配置
Feign 默认超时时间较短(1 秒),需手动配置:
yaml:
ribbon:
ConnectTimeout: 5000 # 连接超时时间(ms)
ReadTimeout: 5000 # 数据读取超时时间(ms)
三、总结
- Ribbon :消费端负载均衡工具,支持自定义策略,通过
@LoadBalanced整合RestTemplate。 - Feign:声明式 HTTP 客户端,简化远程调用,默认集成 Ribbon,让接口调用像本地方法一样简单。
两者结合使用,可高效实现微服务间的负载均衡调用,是 Spring Cloud 微服务架构中服务消费的核心组件。