7.5、服务消费者发现
服务消费者发现
服务消费者需要从 Nacos 发现服务提供者,才能调用服务。本节将学习如何实现服务消费者发现。
本节将学习:服务发现配置、RestTemplate 基础使用,以及服务调用测试。
注意:负载均衡的详细内容将在第 8 章 Spring Cloud LoadBalancer 中深入学习。
在订单服务中实现服务发现
步骤1:添加 LoadBalancer 依赖
文件路径: mall-microservices/order-service/pom.xml
在 <dependencies> 中添加:
<!-- Spring Cloud LoadBalancer(用于服务发现和负载均衡) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
注意:Spring Cloud 2020 版本后,需要显式添加 LoadBalancer 依赖才能使用 @LoadBalanced。
步骤2:配置服务发现
文件路径: mall-microservices/order-service/src/main/resources/application.yml
订单服务已经配置了 Nacos Discovery(在上一节),现在只需要确保配置正确:
spring: application: name: order-service cloud: nacos: discovery: server-addr: localhost:8848 namespace: public group: DEFAULT_GROUP
步骤3:配置 RestTemplate
RestTemplate 配置类
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/config/RestTemplateConfig.java
package com.mall.orderservice.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; @Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
@LoadBalanced 注解说明
@LoadBalanced 注解的作用:
- 启用负载均衡:自动在多个服务实例间进行负载均衡
- 服务名解析:将服务名(如
user-service)解析为实际的服务实例地址 - 集成服务发现:自动从 Nacos 获取服务实例列表
步骤4:实现服务调用
创建用户服务客户端
文件路径: 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; private static final String USER_SERVICE_URL = "http://user-service"; /** * 根据用户ID查询用户信息 */ 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; } } }
创建商品服务客户端
文件路径: 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.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 getProductsByIds(Long[] productIds) { // 这里简化处理,实际可以使用 POST 请求批量查询 // 或者循环调用单个查询接口 return null; } }
更新订单服务实现
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/service/impl/OrderServiceImpl.java
package com.mall.orderservice.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.orderservice.client.ProductServiceClient; import com.mall.orderservice.client.UserServiceClient; import com.mall.orderservice.common.Result; import com.mall.orderservice.entity.Order; import com.mall.orderservice.mapper.OrderMapper; import com.mall.orderservice.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.UUID; @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { @Autowired private UserServiceClient userServiceClient; @Autowired private ProductServiceClient productServiceClient; @Override @Transactional(rollbackFor = Exception.class) public Order createOrder(Order order) { // 1. 验证用户是否存在(通过服务名调用用户服务) if (!userServiceClient.userExists(order.getUserId())) { throw new RuntimeException("User not found: " + order.getUserId()); } // 2. 生成订单号 order.setOrderNo(generateOrderNo()); // 3. 设置订单状态 order.setStatus(0); // 待支付 order.setPaymentStatus(0); // 未支付 // 4. 保存订单 save(order); return order; } private String generateOrderNo() { return "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }
订单 Service 接口
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/service/OrderService.java
package com.mall.orderservice.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mall.orderservice.entity.Order; public interface OrderService extends IService<Order> { Order createOrder(Order order); }
订单 Controller
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/controller/OrderController.java
package com.mall.orderservice.controller; import com.mall.orderservice.common.Result; import com.mall.orderservice.entity.Order; import com.mall.orderservice.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/orders") public class OrderController { @Autowired private OrderService orderService; @PostMapping public Result<Order> createOrder(@RequestBody Order order) { Order created = orderService.createOrder(order); return Result.success("Order created successfully", created); } @GetMapping("/{id}") public Result<Order> getOrder(@PathVariable Long id) { Order order = orderService.getById(id); if (order == null) { return Result.error("Order not found"); } return Result.success(order); } }
负载均衡简介
基础说明
负载均衡基础:
- 使用
@LoadBalanced注解启用负载均衡 - Spring Cloud LoadBalancer 会自动进行服务实例选择
- 详细的负载均衡策略和配置将在第 8 章深入学习
步骤5:服务调用测试
测试准备
启动所有服务:
-
启动 Nacos:
cd mall-microservices/docker/nacos docker-compose -f docker-compose-simple.yml up -d -
启动用户服务(端口 8081):
cd mall-microservices/user-service mvn spring-boot:run -
启动商品服务(端口 8082):
cd mall-microservices/product-service mvn spring-boot:run -
启动订单服务(端口 8083):
cd mall-microservices/order-service mvn spring-boot:run
测试服务调用
测试1:订单服务调用用户服务
# 1. 先创建一个用户(如果还没有) curl -X POST http://localhost:8081/api/users/register \ -H "Content-Type: application/json" \ -d '{ "username": "testuser", "email": "test@example.com", "password": "123456" }' # 2. 创建订单(订单服务会调用用户服务验证用户) curl -X POST http://localhost:8083/api/orders \ -H "Content-Type: application/json" \ -d '{ "userId": 1, "totalAmount": 100.00, "payAmount": 100.00 }'
测试2:验证服务发现
# 查询 Nacos 中的服务列表 curl http://localhost:8848/nacos/v1/ns/service/list # 查询用户服务的实例列表 curl "http://localhost:8848/nacos/v1/ns/instance/list?serviceName=user-service"
服务调用流程
验证负载均衡(可选)
启动多个用户服务实例:
-
启动第一个实例(端口 8081):
cd mall-microservices/user-service mvn spring-boot:run -
启动第二个实例(端口 8082):
# 修改 application.yml 中的端口为 8082 # 或者使用命令行参数 mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8082 -
验证负载均衡:
- 多次调用订单服务
- 查看两个用户服务实例的日志
- 确认请求被分发到不同实例
注意:负载均衡的详细配置和策略选择将在第 8 章学习。
服务发现原理
服务发现流程
服务发现的完整流程:
- 服务注册:服务提供者启动时注册到 Nacos
- 服务发现:服务消费者从 Nacos 获取服务实例列表
- 负载均衡:LoadBalancer 从实例列表中选择一个实例
- 服务调用:使用选中的实例地址进行 HTTP 调用
- 健康检查:Nacos 定期检查服务健康状态,更新实例列表
服务名解析
服务名解析过程:
// 使用服务名调用 String url = "http://user-service/api/users/1"; // LoadBalancer 会: // 1. 从 Nacos 获取 user-service 的实例列表 // 2. 根据负载均衡策略选择一个实例(如:192.168.1.100:8081) // 3. 将服务名替换为实际地址:http://192.168.1.100:8081/api/users/1 // 4. 执行 HTTP 请求
与硬编码 URL 的对比
之前的硬编码方式:
// 硬编码 URL(不推荐) String url = "http://localhost:8081/api/users/1";
使用服务发现的方式:
// 使用服务名(推荐) String url = "http://user-service/api/users/1";
优势:
- 动态发现:自动发现服务实例,无需手动配置
- 负载均衡:自动在多个实例间负载均衡
- 故障转移:实例故障时自动切换到其他实例
- 易于扩展:添加新实例无需修改代码
常见问题
可能遇到的问题:
-
服务调用失败:UnknownHostException:
- 问题:无法解析服务名
- 解决:
- 检查是否添加了
@LoadBalanced注解 - 检查是否添加了
spring-cloud-starter-loadbalancer依赖 - 检查服务是否已注册到 Nacos
- 检查是否添加了
-
服务调用超时:
- 问题:调用服务时超时
- 解决:
- 检查目标服务是否正常运行
- 检查网络连接
- 增加 RestTemplate 的超时时间
-
负载不均衡:
- 问题:请求总是分发到同一个实例
- 解决:
- 检查是否有多个服务实例
- 检查负载均衡策略配置(第 8 章学习)
官方资源
根据 Nacos 服务发现文档 和 Spring Cloud LoadBalancer 文档:
-
服务发现机制:官方文档详细说明了 Nacos 的服务发现机制,包括服务列表获取、服务实例选择、健康检查等。文档强调,Nacos 客户端会定期从服务器获取最新的服务实例列表,并自动剔除不健康的实例。
-
负载均衡集成:官方文档说明了如何将 Nacos 与 Spring Cloud LoadBalancer 集成,实现基于服务名的负载均衡。文档详细介绍了
@LoadBalanced注解的使用方法,以及如何配置不同的负载均衡策略。 -
服务调用方式:官方文档提供了多种服务调用方式的示例,包括 RestTemplate、OpenFeign 等,并说明了如何在这些方式中使用服务发现功能。
参考资源:
- Nacos 服务发现:https://nacos.io/docs/use-nacos-with-spring-cloud.html
- Spring Cloud LoadBalancer:https://spring.io/projects/spring-cloud-loadbalancer
本节小结
在本节中,我们完成了服务消费者发现的实现:
第一个是添加 LoadBalancer 依赖。 在订单服务中添加了 Spring Cloud LoadBalancer 依赖。
第二个是配置 RestTemplate。 创建了 RestTemplate 配置类,使用 @LoadBalanced 注解启用负载均衡。
第三个是实现服务调用。 创建了 UserServiceClient 和 ProductServiceClient,实现了订单服务对用户服务和商品服务的调用。
第四个是更新订单服务实现。 修改了 OrderServiceImpl,使用服务名而非硬编码 URL 调用其他服务。
第五个是服务调用测试。 提供了完整的测试步骤,验证服务发现和调用是否正常工作。
第六个是服务发现原理。 了解了服务发现的完整流程,以及使用服务名相对于硬编码 URL 的优势。
这就是服务消费者发现。现在订单服务可以通过服务名调用用户服务和商品服务了,不再需要硬编码 URL。在下一节,我们将深入学习 Nacos 服务发现的原理。
提示:关于负载均衡的详细内容,包括策略选择、自定义负载均衡器等,请参考第 8 章 Spring Cloud LoadBalancer。