一、核心问题:加了 Nacos,能不能直接用微服务名?

答案是:需要看你用什么方式调用


场景 1:使用 RestTemplate(需要 @LoadBalanced)

❌ 错误做法(只加 Nacos,不加 @LoadBalanced)

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public void placeOrder() {
        // ❌ 这样会报错!找不到这个 host
        String url = "http://user-service/user/1";
        User user = restTemplate.getForObject(url, User.class);
    }
}

为什么错?

RestTemplate 不知道 "user-service" 是什么
↓
它会当做一个真实的 hostname
↓
尝试 DNS 解析 "user-service"
↓
找不到!报错

✅ 正确做法(加上 @LoadBalanced)

@Bean
@LoadBalanced  // ⭐️ 关键!这个注解很重要
public RestTemplate restTemplate() {
    return new RestTemplate();
}

@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public void placeOrder() {
        // ✅ 这样就可以了!
        String url = "http://user-service/user/1";
        User user = restTemplate.getForObject(url, User.class);
    }
}

为什么对了?

@LoadBalanced 做了什么?
↓
1. 它给 RestTemplate 加上了一个拦截器
↓
2. 当 RestTemplate 发请求时,拦截器会:
   - 截获请求 URL:"http://user-service/user/1"
   - 从 Nacos 查询 "user-service" 的所有实例
   - 例如找到:192.168.1.10:8081, 192.168.1.11:8081
   - 用负载均衡算法选一个(轮询、随机等)
   - 例如选中 192.168.1.10:8081
   - 修改 URL 为:"http://192.168.1.10:8081/user/1"
   - 继续发送请求
↓
3. 请求成功!

场景 2:使用 OpenFeign(自动有负载均衡)

✅ 自动支持(Feign 内置支持)

@FeignClient(name = "user-service")  // 微服务名
public interface UserServiceClient {
    @GetMapping("/user/{id}")
    User getUser(@PathVariable Long id);
}

@Service
public class OrderService {
    @Autowired
    private UserServiceClient userServiceClient;
    
    public void placeOrder() {
        // ✅ 直接用微服务名!无需 @LoadBalanced
        User user = userServiceClient.getUser(1L);
    }
}

为什么 Feign 不需要 @LoadBalanced?

因为 Feign 内部已经集成了负载均衡!当你定义 @FeignClient(name = "user-service") 时:

Feign 自动做这些事:
├─ 1. 拦截你的方法调用
├─ 2. 从 Nacos 查询 "user-service" 的实例列表
├─ 3. 用 Ribbon(负载均衡器)选一个实例
├─ 4. 发送 HTTP 请求到那个实例
└─ 5. 返回结果给你

所以 Feign 自动支持。


场景 3:使用 WebClient(Spring WebFlux)

✅ 支持,但需要特殊配置

@Bean
public WebClient webClient(LoadBalancerClient loadBalancerClient) {
    return WebClient.builder()
        .filter(new LoadBalancerExchangeFilterFunction(loadBalancerClient))
        .build();
}

@Service
public class OrderService {
    @Autowired
    private WebClient webClient;
    
    public Mono<User> placeOrder() {
        // ✅ 可以用微服务名
        return webClient.get()
            .uri("http://user-service/user/1")
            .retrieve()
            .bodyToMono(User.class);
    }
}

二、核心原理:@LoadBalanced 干了什么?

1、它给 RestTemplate 加了一个拦截器

// 简化的原理
public class LoadBalancedInterceptor implements ClientHttpRequestInterceptor {
    
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                       ClientHttpRequestExecution execution) {
        // 1. 获取请求 URL
        String url = request.getURI().toString();
        // 例如:"http://user-service/user/1"
        
        // 2. 从 URL 中提取服务名
        String serviceName = extractServiceName(url);
        // 结果:"user-service"
        
        // 3. 从 Nacos 查询实例
        List<ServiceInstance> instances = 
            discoveryClient.getInstances(serviceName);
        // 结果:[192.168.1.10:8081, 192.168.1.11:8081, ...]
        
        // 4. 负载均衡选一个
        ServiceInstance chosenInstance = 
            loadBalancerClient.choose(serviceName);
        
        // 5. 替换 URL 中的服务名为 IP:PORT
        String newUrl = url.replace(serviceName, 
            chosenInstance.getHost() + ":" + chosenInstance.getPort());
        // 结果:"http://192.168.1.10:8081/user/1"
        
        // 6. 发送修改后的请求
        request = createRequest(newUrl);
        return execution.execute(request, body);
    }
}

关键点:URL 的转换

原始 URL:     http://user-service/user/1
                        ↓ (替换服务名为 IP:PORT)
最终 URL:     http://192.168.1.10:8081/user/1
                        ↓ (真实的网络请求)
发送到实例

三、完整对比表

方式 需要 @LoadBalanced 需要 Nacos 能用微服务名 说明
RestTemplate ✅ 需要 ✅ 需要 ✅ 可以 加注解激活拦截器
OpenFeign ❌ 不需要 ✅ 需要 ✅ 可以 内置支持
WebClient ✅ 需要(Filter) ✅ 需要 ✅ 可以 需要配置 Filter
硬编码 IP ❌ 不需要 ❌ 不需要 ❌ 不能 直接用 IP,无法自动发现

四、实际项目示例

项目结构

分布式系统
├─ order-service(订单服务)
│  ├─ pom.xml(引入 nacos-discovery、spring-cloud-starter-loadbalancer)
│  └─ OrderService.java(需要调用 user-service)
├─ user-service(用户服务,在 Nacos 注册为"user-service")
└─ Nacos 服务器

pom.xml 依赖

<!-- 共同的依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 方式 1:RestTemplate 需要这个 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- 方式 2:OpenFeign 需要这个 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

方式 1:RestTemplate + @LoadBalanced

// 配置类
@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced  // ⭐️ 关键
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

// 使用
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public Order createOrder(Long userId) {
        // ✅ 直接用微服务名
        User user = restTemplate.getForObject(
            "http://user-service/user/" + userId, 
            User.class
        );
        
        Order order = new Order();
        order.setUserId(user.getId());
        return order;
    }
}

方式 2:OpenFeign(推荐)

// 1. 定义 Feign 客户端
@FeignClient(name = "user-service")
public interface UserServiceClient {
    @GetMapping("/user/{id}")
    User getUser(@PathVariable("id") Long id);
}

// 2. 启用 Feign
@SpringBootApplication
@EnableFeignClients  // 不加这个不行
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

// 3. 使用
@Service
public class OrderService {
    @Autowired
    private UserServiceClient userServiceClient;
    
    public Order createOrder(Long userId) {
        // ✅ 直接用微服务名,自动负载均衡
        User user = userServiceClient.getUser(userId);
        
        Order order = new Order();
        order.setUserId(user.getId());
        return order;
    }
}

五、常见错误和调试

错误 1:注册了 Nacos,但找不到服务

错误日志:
java.net.UnknownHostException: user-service

原因:
1. RestTemplate 没有加 @LoadBalanced
2. 或者服务名在 Nacos 中注册的名字不对
3. 或者实例根本没启动

解决:

// 1. 检查是否加了 @LoadBalanced
@Bean
@LoadBalanced  // ✅ 必须有
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// 2. 检查 Nacos 中的服务名
// 应用配置:spring.application.name=user-service
// 对应 Nacos 中注册的服务名就是:user-service

// 3. 验证服务是否真的启动了
// 访问 Nacos 控制台,看服务列表

错误 2:OpenFeign 找不到依赖

错误:
HystrixFeign is no longer the default
或者
FeignClient 无法注入

原因:
- 没有加 @EnableFeignClients
- 依赖版本不兼容

解决:

@SpringBootApplication
@EnableFeignClients  // ✅ 必须加
@EnableDiscoveryClient  // 可选,更明确
public class Application {
}

六、性能对比

调用 100 次 user-service:

硬编码 IP:
时间:100ms
问题:实例挂了就崩

RestTemplate + @LoadBalanced:
时间:105ms(多了拦截、查询的开销)
好处:自动发现,负载均衡,失败转移

OpenFeign:
时间:102ms(Feign 优化得很好)
好处:代码最简洁,内置完整支持

推荐:使用 OpenFeign,性能好代码简洁


七、总结

问题 答案
加了 Nacos 能不能直接用微服务名? 需要激活负载均衡器才能用
RestTemplate 需要什么? @LoadBalanced 注解
OpenFeign 需要什么? @EnableFeignClients + @FeignClient
WebClient 需要什么? LoadBalancerExchangeFilterFunction
推荐用哪个? OpenFeign(最方便)

记住这个流程:

微服务名 → 负载均衡器拦截 → Nacos 查询实例 → 选择一个实例 → 替换为 IP:PORT → 发送请求
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐