8.4、RestTemplate与LoadBalancer
RestTemplate + LoadBalancer
RestTemplate 与 LoadBalancer 集成可以实现服务调用的负载均衡。本节将学习 RestTemplate + LoadBalancer。
本节将学习:@LoadBalanced 注解、RestTemplate 配置、服务调用示例,以及负载均衡验证。
@LoadBalanced 注解
注解说明
@LoadBalanced 注解:
- 标记 RestTemplate Bean 启用负载均衡
- 自动注入 LoadBalancerClient
- 拦截 RestTemplate 请求
- 替换服务名为实际实例地址
使用方式
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
RestTemplate 配置
基础配置
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
自定义配置
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .setConnectTimeout(Duration.ofSeconds(5)) .setReadTimeout(Duration.ofSeconds(10)) .build(); } }
多个 RestTemplate
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate loadBalancedRestTemplate() { return new RestTemplate(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
在商城项目中的应用
订单服务中的实际应用
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/client/UserServiceClient.java
package com.mall.orderservice.client; import com.mall.orderservice.common.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class UserServiceClient { @Autowired private RestTemplate restTemplate; // 已配置 @LoadBalanced private static final String USER_SERVICE_URL = "http://user-service"; /** * 根据用户ID查询用户信息 * 使用服务名 user-service,LoadBalancer 会自动解析为实际地址 */ public Result getUserById(Long userId) { String url = USER_SERVICE_URL + "/api/users/" + userId; return restTemplate.getForObject(url, Result.class); } /** * 验证用户是否存在 */ public boolean userExists(Long userId) { try { Result result = getUserById(userId); return result != null && result.getCode() == 200; } catch (Exception e) { return false; } } /** * 根据用户名查询用户 */ public Result getUserByUsername(String username) { String url = USER_SERVICE_URL + "/api/users/username/" + username; return restTemplate.getForObject(url, Result.class); } }
商品服务客户端
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/client/ProductServiceClient.java
package com.mall.orderservice.client; import com.mall.orderservice.common.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class ProductServiceClient { @Autowired private RestTemplate restTemplate; private static final String PRODUCT_SERVICE_URL = "http://product-service"; /** * 根据商品ID查询商品信息 */ public Result getProductById(Long productId) { String url = PRODUCT_SERVICE_URL + "/api/products/" + productId; return restTemplate.getForObject(url, Result.class); } /** * 查询商品列表(带分页) */ public Result getProducts(Integer page, Integer size, Long categoryId) { String url = PRODUCT_SERVICE_URL + "/api/products?page=" + page + "&size=" + size; if (categoryId != null) { url += "&categoryId=" + categoryId; } return restTemplate.getForObject(url, Result.class); } }
POST 请求
public User createUser(User user) { String url = "http://user-service/api/users"; return restTemplate.postForObject(url, user, User.class); }
PUT 请求
public void updateUser(Long id, User user) { String url = "http://user-service/api/users/" + id; restTemplate.put(url, user); }
DELETE 请求
public void deleteUser(Long id) { String url = "http://user-service/api/users/" + id; restTemplate.delete(url); }
带参数的请求
public List<User> getUsers(String name, Integer age) { String url = "http://user-service/api/users?name={name}&age={age}"; Map<String, Object> params = new HashMap<>(); params.put("name", name); params.put("age", age); return restTemplate.getForObject(url, List.class, params); }
在商城项目中验证负载均衡
验证步骤
步骤1:启动多个用户服务实例
启动第一个实例(端口 8081):
cd mall-microservices/user-service mvn spring-boot:run
启动第二个实例(端口 8082):
修改 application.yml 或使用命令行参数:
# 方式1:修改 application.yml 中的 server.port 为 8082 # 方式2:使用命令行参数 mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8082
步骤2:添加测试接口
文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/controller/UserController.java
添加测试接口:
@GetMapping("/test/loadbalance") public Result<String> testLoadBalance() { String port = environment.getProperty("server.port"); String message = "Response from user-service, port: " + port; return Result.success(message); }
步骤3:创建测试 Controller
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/controller/LoadBalanceTestController.java
package com.mall.orderservice.controller; import com.mall.orderservice.client.UserServiceClient; import com.mall.orderservice.common.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/api/test") public class LoadBalanceTestController { @Autowired private RestTemplate restTemplate; @Autowired private UserServiceClient userServiceClient; /** * 测试负载均衡 * 多次调用用户服务,观察请求分发情况 */ @GetMapping("/loadbalance") public Result<List<String>> testLoadBalance() { List<String> results = new ArrayList<>(); String url = "http://user-service/api/users/test/loadbalance"; // 调用10次,观察负载均衡效果 for (int i = 0; i < 10; i++) { try { Result result = restTemplate.getForObject(url, Result.class); if (result != null && result.getData() != null) { results.add("Request " + (i + 1) + ": " + result.getData()); } } catch (Exception e) { results.add("Request " + (i + 1) + ": Error - " + e.getMessage()); } } return Result.success(results); } }
步骤4:执行测试
# 调用测试接口 curl http://localhost:8083/api/test/loadbalance # 预期结果:应该看到请求被分发到不同的端口(8081 和 8082)
验证结果分析
预期结果:
如果负载均衡正常工作,10次请求应该大致均匀地分发到两个实例:
- 大约5次请求到端口 8081
- 大约5次请求到端口 8082
查看日志:
分别查看两个用户服务实例的日志,确认请求分发情况。
负载均衡策略验证
默认策略(轮询):
- 第一次请求 → 实例1
- 第二次请求 → 实例2
- 第三次请求 → 实例1
- 第四次请求 → 实例2
- ...
验证方法:
多次调用测试接口,观察返回的端口号,应该看到端口号交替出现。
工作原理
请求拦截
请求拦截流程:
- RestTemplate 发起请求
- LoadBalancerInterceptor 拦截请求
- 从服务名解析服务实例列表
- 根据负载均衡策略选择实例
- 替换服务名为实际 IP:Port
- 执行 HTTP 请求
服务名解析
服务名解析:
http://user-service/api/users→http://192.168.1.100:8080/api/users- 服务名必须与 Nacos 注册的服务名一致
- 自动从服务发现组件获取实例列表
官方资源
- Spring Cloud LoadBalancer 官方文档:https://spring.io/projects/spring-cloud-loadbalancer
- RestTemplate 文档:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-resttemplate
本节小结
在本节中,我们学习了:
第一个是 @LoadBalanced 注解。 使用 @LoadBalanced 注解启用负载均衡。
第二个是 RestTemplate 配置。 如何配置 RestTemplate。
第三个是服务调用示例。 如何使用 RestTemplate 调用服务。
第四个是负载均衡验证。 如何验证负载均衡效果。
这就是 RestTemplate + LoadBalancer。使用 RestTemplate 和 LoadBalancer 可以实现服务调用的负载均衡。
在下一节,我们将学习自定义负载均衡器。