7.5服务消费者发现

分类: Nacos服务注册与发现

服务消费者发现

服务消费者需要从 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 注解的作用:

  1. 启用负载均衡:自动在多个服务实例间进行负载均衡
  2. 服务名解析:将服务名(如 user-service)解析为实际的服务实例地址
  3. 集成服务发现:自动从 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:服务调用测试

测试准备

启动所有服务:

  1. 启动 Nacos

    cd mall-microservices/docker/nacos docker-compose -f docker-compose-simple.yml up -d
  2. 启动用户服务(端口 8081):

    cd mall-microservices/user-service mvn spring-boot:run
  3. 启动商品服务(端口 8082):

    cd mall-microservices/product-service mvn spring-boot:run
  4. 启动订单服务(端口 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"

服务调用流程

验证负载均衡(可选)

启动多个用户服务实例:

  1. 启动第一个实例(端口 8081):

    cd mall-microservices/user-service mvn spring-boot:run
  2. 启动第二个实例(端口 8082):

    # 修改 application.yml 中的端口为 8082 # 或者使用命令行参数 mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8082
  3. 验证负载均衡

    • 多次调用订单服务
    • 查看两个用户服务实例的日志
    • 确认请求被分发到不同实例

注意:负载均衡的详细配置和策略选择将在第 8 章学习。

服务发现原理

服务发现流程

服务发现的完整流程:

  1. 服务注册:服务提供者启动时注册到 Nacos
  2. 服务发现:服务消费者从 Nacos 获取服务实例列表
  3. 负载均衡:LoadBalancer 从实例列表中选择一个实例
  4. 服务调用:使用选中的实例地址进行 HTTP 调用
  5. 健康检查: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";

优势:

  1. 动态发现:自动发现服务实例,无需手动配置
  2. 负载均衡:自动在多个实例间负载均衡
  3. 故障转移:实例故障时自动切换到其他实例
  4. 易于扩展:添加新实例无需修改代码

常见问题

可能遇到的问题:

  1. 服务调用失败:UnknownHostException

    • 问题:无法解析服务名
    • 解决:
      • 检查是否添加了 @LoadBalanced 注解
      • 检查是否添加了 spring-cloud-starter-loadbalancer 依赖
      • 检查服务是否已注册到 Nacos
  2. 服务调用超时

    • 问题:调用服务时超时
    • 解决:
      • 检查目标服务是否正常运行
      • 检查网络连接
      • 增加 RestTemplate 的超时时间
  3. 负载不均衡

    • 问题:请求总是分发到同一个实例
    • 解决:
      • 检查是否有多个服务实例
      • 检查负载均衡策略配置(第 8 章学习)

官方资源

根据 Nacos 服务发现文档Spring Cloud LoadBalancer 文档

  1. 服务发现机制:官方文档详细说明了 Nacos 的服务发现机制,包括服务列表获取、服务实例选择、健康检查等。文档强调,Nacos 客户端会定期从服务器获取最新的服务实例列表,并自动剔除不健康的实例。

  2. 负载均衡集成:官方文档说明了如何将 Nacos 与 Spring Cloud LoadBalancer 集成,实现基于服务名的负载均衡。文档详细介绍了 @LoadBalanced 注解的使用方法,以及如何配置不同的负载均衡策略。

  3. 服务调用方式:官方文档提供了多种服务调用方式的示例,包括 RestTemplate、OpenFeign 等,并说明了如何在这些方式中使用服务发现功能。

参考资源

本节小结

在本节中,我们完成了服务消费者发现的实现:

第一个是添加 LoadBalancer 依赖。 在订单服务中添加了 Spring Cloud LoadBalancer 依赖。

第二个是配置 RestTemplate。 创建了 RestTemplate 配置类,使用 @LoadBalanced 注解启用负载均衡。

第三个是实现服务调用。 创建了 UserServiceClient 和 ProductServiceClient,实现了订单服务对用户服务和商品服务的调用。

第四个是更新订单服务实现。 修改了 OrderServiceImpl,使用服务名而非硬编码 URL 调用其他服务。

第五个是服务调用测试。 提供了完整的测试步骤,验证服务发现和调用是否正常工作。

第六个是服务发现原理。 了解了服务发现的完整流程,以及使用服务名相对于硬编码 URL 的优势。

这就是服务消费者发现。现在订单服务可以通过服务名调用用户服务和商品服务了,不再需要硬编码 URL。在下一节,我们将深入学习 Nacos 服务发现的原理。

提示:关于负载均衡的详细内容,包括策略选择、自定义负载均衡器等,请参考第 8 章 Spring Cloud LoadBalancer。