Nacos + 微服务名调用 - 负载均衡详解
问题答案加了 Nacos 能不能直接用微服务名?需要激活负载均衡器才能用RestTemplate 需要什么?@LoadBalanced 注解OpenFeign 需要什么?WebClient 需要什么?推荐用哪个?OpenFeign(最方便)微服务名 → 负载均衡器拦截 → Nacos 查询实例 → 选择一个实例 → 替换为 IP:PORT → 发送请求。
·
一、核心问题:加了 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 → 发送请求
更多推荐
所有评论(0)