Spring Cloud 服务调用完全指南
"在微服务架构中,服务间调用是连接各个微服务的桥梁,掌握服务调用机制是构建微服务的基础"
📚 目录
- 服务调用基础 - 理解服务调用的必要性和挑战
- RestTemplate详解 - 传统HTTP客户端工具
- OpenFeign深度解析 - 声明式HTTP客户端
- 其他RPC框架 - Dubbo、gRPC等高性能框架
- 负载均衡机制 - 客户端负载均衡原理
- 服务调用原理 - 底层工作流程
- 超时与重试机制 - 容错处理策略
- 服务调用最佳实践 - 生产环境经验总结
- 实战场景应用 - 电商、金融等真实场景
- 面试核心问题 - 高频面试题汇总
- 避坑指南 - 常见问题和解决方案
服务调用基础
什么是服务调用?
在微服务架构中,服务调用是指一个服务通过网络协议(通常是HTTP)调用另一个服务的接口,以完成业务功能的过程。
为什么需要服务调用?
单体架构 vs 微服务架构:
单体架构:
┌─────────────────────────┐
│ 单体应用 │
│ ┌──────────────────┐ │
│ │ 订单模块 │ │
│ │ 调用 → 库存模块 │ │ ← 方法调用(进程内)
│ │ 调用 → 账户模块 │ │
│ └──────────────────┘ │
└─────────────────────────┘
微服务架构:
┌──────────┐ ┌──────────┐ ┌──────────┐
│订单服务 │ │库存服务 │ │账户服务 │
│ │ │ │ │ │
│ HTTP调用 │───▶│ HTTP调用 │───▶│ HTTP调用 │
└──────────┘ └──────────┘ └──────────┘
↑ 服务1 ↑ 服务2 ↑ 服务3
区别: 微服务需要通过网络进行进程间通信(RPC)
服务调用的核心挑战
微服务调用面临的挑战:
├── 服务发现问题
│ ├── 如何知道服务实例的IP和端口?
│ ├── 服务实例动态变化如何处理?
│ └── 服务下线如何感知?
│
├── 负载均衡问题
│ ├── 如何选择一个健康的服务实例?
│ ├── 如何将请求均匀分发?
│ └── 如何处理实例故障?
│
├── 容错处理
│ ├── 调用失败如何重试?
│ ├── 超时如何设置?
│ ├── 如何避免雪崩效应?
│ └── 是否需要熔断降级?
│
├── 性能问题
│ ├── 网络开销如何降低?
│ ├── 如何提高并发性能?
│ └── 连接池如何配置?
│
└── 可观测性
├── 如何追踪调用链路?
├── 如何记录调用日志?
└── 如何监控调用成功率?
RestTemplate详解
RestTemplate介绍
RestTemplate 是Spring提供的用于访问REST服务的HTTP客户端工具,它简化了与HTTP服务的通信。
// RestTemplate的核心功能
├── HTTP方法支持
│ ├── GET - 查询资源
│ ├── POST - 创建资源
│ ├── PUT - 更新资源
│ ├── DELETE - 删除资源
│ └── PATCH - 部分更新
│
├── 序列化/反序列化
│ ├── 自动JSON转换
│ ├── XML支持
│ └── 自定义序列化
│
└── 拦截器机制
├── 请求拦截
├── 响应拦截
└── 日志记录
基础使用
1. 创建RestTemplate
@Configuration
public class RestTemplateConfig {
/**
* 创建普通的RestTemplate
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 创建带连接池的RestTemplate
*/
@Bean
public RestTemplate restTemplateWithPool() {
// 使用HttpClient作为底层客户端
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(100) // 最大连接数
.setMaxConnPerRoute(20) // 每个路由的最大连接数
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(5000); // 连接超时5秒
factory.setReadTimeout(10000); // 读取超时10秒
return new RestTemplate(factory);
}
}
2. GET请求
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
/**
* GET请求 - 返回简单类型
*/
public String getUserName(Long userId) {
String url = "http://user-service/users/" + userId + "/name";
return restTemplate.getForObject(url, String.class);
}
/**
* GET请求 - 返回对象
*/
public User getUser(Long userId) {
String url = "http://user-service/users/" + userId;
return restTemplate.getForObject(url, User.class);
}
/**
* GET请求 - 带查询参数
*/
public List<User> getUsersByStatus(String status) {
String url = "http://user-service/users?status={status}";
return restTemplate.getForObject(url, List.class, status);
}
}
RestTemplate集成负载均衡
@Configuration
public class RestTemplateConfig {
/**
* 创建支持负载均衡的RestTemplate
* @LoadBalanced注解让RestTemplate支持服务名调用
*/
@Bean
@LoadBalanced // 关键注解 - 启用客户端负载均衡
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
@Qualifier("loadBalancedRestTemplate")
private RestTemplate restTemplate;
/**
* 使用服务名调用 - 自动负载均衡
*/
public User getUser(Long userId) {
// 注意: 使用的是服务名"user-service"而不是具体的IP:端口
// Spring Cloud会自动:
// 1. 从注册中心获取user-service的所有实例
// 2. 使用负载均衡算法选择一个实例
// 3. 发起HTTP调用
String url = "http://user-service/users/" + userId;
return restTemplate.getForObject(url, User.class);
}
}
RestTemplate优缺点
优点:
├── ✅ 使用简单: API直观易学
├── ✅ 灵活性高: 可以精细控制请求
├── ✅ 功能完善: 支持各种HTTP操作
└── ✅ Spring集成: 与Spring生态无缝集成
缺点:
├── ❌ 代码冗余: 每次调用都要写大量代码
├── ❌ 维护困难: URL分散在代码各处
├── ❌ 类型不安全: 容易出现类型转换错误
├── ❌ 测试困难: Mock RestTemplate比较复杂
└── ❌ API过时: Spring 5.0推荐使用WebClient
结论: 适合简单场景,复杂场景推荐使用Feign
OpenFeign深度解析
OpenFeign介绍
OpenFeign 是一个声明式的Web服务客户端,它使得编写HTTP客户端变得更简单。使用Feign只需要创建一个接口并添加注解即可。
Feign vs RestTemplate:
RestTemplate (命令式编程):
┌─────────────────────────────┐
│ String url = "http://..."; │
│ User user = restTemplate. │
│ getForObject(url, User. │
│ class); │
└─────────────────────────────┘
← 需要手动拼接URL、处理参数
Feign (声明式编程):
┌─────────────────────────────┐
│ @FeignClient("user-service") │
│ interface UserService { │
│ @GetMapping("/users/{id}") │
│ User getUser(@PathVariable │
│ Long id); │
│ } │
└─────────────────────────────┘
← 就像调用本地方法一样简单
快速开始
1. 添加依赖
<!-- pom.xml -->
<dependencies>
<!-- Spring Cloud OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 (Spring Cloud 2020+ 需要手动添加) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
2. 启用Feign
@SpringBootApplication
@EnableFeignClients // 启用Feign客户端
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
3. 定义Feign客户端
/**
* 用户服务Feign客户端
*/
@FeignClient(
name = "user-service", // 服务名称
path = "/api/users", // 基础路径
fallback = UserServiceFallback.class // 降级处理类
)
public interface UserService {
/**
* 根据ID查询用户
* 就像定义Spring MVC接口一样简单
*/
@GetMapping("/{id}")
User getUser(@PathVariable("id") Long id);
/**
* 创建用户
*/
@PostMapping
User createUser(@RequestBody User user);
/**
* 查询用户列表
*/
@GetMapping
List<User> getUsers(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size
);
}
Feign工作原理
Feign调用流程:
1. 启动扫描
@EnableFeignClients
└─ 扫描所有@FeignClient注解的接口
└─ 为每个接口生成动态代理对象 (JDK动态代理)
└─ 注册到Spring容器
2. 调用过程
userService.getUser(1L)
└─ 触发动态代理的InvocationHandler
├─ 提取方法元数据
│ ├─ 方法名: getUser
│ ├─ 请求方法: GET
│ ├─ 请求URL: /api/users/{id}
│ └─ 参数: id=1
│
├─ 构建请求模板
│ ├─ 应用拦截器
│ ├─ 添加请求头
│ └─ 替换路径参数
│
├─ 负载均衡
│ ├─ 从注册中心获取实例列表
│ └─ 选择一个实例
│
├─ 发起HTTP请求
│ └─ 通过Client实现 (HttpClient/OkHttp)
│
└─ 解析响应
├─ 解码器解码
├─ 熔断判断
└─ 返回结果
Feign优缺点
优点:
├── ✅ 声明式调用: 代码简洁,易读易维护
├── ✅ 类型安全: 接口定义,编译时检查
├── ✅ 易于测试: Mock接口即可
├── ✅ 集成简单: 几个注解即可
├── ✅ 功能丰富: 支持拦截器、编解码等
└── ✅ 与Spring无缝集成
缺点:
├── ❌ 学习成本: 需要理解各种配置
├── ❌ 灵活性稍差: 不如RestTemplate灵活
├── ❌ 调试困难: 接口抽象层,调试不太直观
└── ❌ 版本兼容: 不同版本配置可能不同
结论: 微服务场景首选Feign
其他RPC框架
除了HTTP协议的RestTemplate和Feign,微服务架构中还有许多其他高性能的RPC框架。
主流RPC框架对比
RPC框架分类:
├── HTTP协议
│ ├── RestTemplate (同步阻塞)
│ ├── Feign (声明式HTTP)
│ └── WebClient (响应式)
│
├── RPC协议
│ ├── Dubbo (阿里巴巴)
│ ├── gRPC (Google)
│ ├── Thrift (Facebook)
│ └── Motan (微博)
│
└── 消息队列
├── RabbitMQ
├── Kafka
└── RocketMQ
Dubbo框架详解
Dubbo介绍
Dubbo 是阿里巴巴开源的高性能、轻量级的RPC框架,提供了完整的服务治理能力。
Dubbo架构:
┌────────────────────────────────────────┐
│ 服务消费者 (Consumer) │
│ ┌──────────────────────────────────┐ │
│ │ 1. 从注册中心订阅服务列表 │ │
│ │ 2. 负载均衡选择服务提供者 │ │
│ │ 3. 发起RPC调用 │ │
│ └──────────────────────────────────┘ │
└──────────────┬─────────────────────────┘
│ RPC调用
↓
┌────────────────────────────────────────┐
│ 服务提供者 (Provider) │
│ ┌──────────────────────────────────┐ │
│ │ 1. 向注册中心注册服务 │ │
│ │ 2. 接收消费者请求 │ │
│ │ 3. 处理业务并返回 │ │
│ └──────────────────────────────────┘ │
└──────────────┬─────────────────────────┘
│
↓
┌────────────────────────────────────────┐
│ 注册中心 (Registry) │
│ - Nacos / Zookeeper / Redis │
│ - 服务注册与发现 │
└────────────────────────────────────────┘
↑
│
┌────────────────────────────────────────┐
│ 监控中心 (Monitor) │
│ - 调用次数统计 │
│ - 性能监控 │
└────────────────────────────────────────┘
Dubbo快速开始
1. 添加依赖
<!-- pom.xml -->
<dependencies>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!-- Nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
2. 服务提供者
/**
* 用户服务接口 - API模块 (单独的jar包)
*/
public interface UserService {
User getUser(Long userId);
List<User> listUsers();
}
/**
* 用户服务实现 - 服务提供者
*/
@DubboService(version = "1.0.0") // Dubbo 3.x使用@DubboService
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUser(Long userId) {
return userMapper.selectById(userId);
}
@Override
public List<User> listUsers() {
return userMapper.selectList(null);
}
}
# application.yml - 服务提供者配置
spring:
application:
name: user-service
dubbo:
application:
name: ${spring.application.name}
protocol:
name: dubbo # 协议名称
port: -1 # 自动分配端口 (-1表示自动)
registry:
address: nacos://localhost:8848 # 注册中心地址
scan:
base-packages: com.example.service # 扫描@DubboService的包
3. 服务消费者
/**
* 订单服务 - 服务消费者
*/
@Service
public class OrderService {
/**
* 引用远程服务 - 就像注入本地Bean一样
*/
@DubboReference(version = "1.0.0")
private UserService userService;
/**
* 创建订单 - 调用远程用户服务
*/
public Order createOrder(OrderRequest request) {
// 调用远程服务 - 完全透明的RPC调用
User user = userService.getUser(request.getUserId());
Order order = new Order();
order.setUserId(user.getId());
order.setUserName(user.getUsername());
// ...
return order;
}
}
# application.yml - 服务消费者配置
spring:
application:
name: order-service
dubbo:
application:
name: ${spring.application.name}
registry:
address: nacos://localhost:8848
consumer:
timeout: 5000 # 超时时间5秒
retries: 2 # 失败重试2次
check: false # 启动时不检查服务是否可用
Dubbo核心特性
/**
* Dubbo核心特性
*/
// 1. 负载均衡策略
@DubboReference(
version = "1.0.0",
loadbalance = "random" // 负载均衡: random/roundrobin/leastactive/consistenthash
)
private UserService userService;
// 2. 集群容错策略
@DubboReference(
version = "1.0.0",
cluster = "failfast" // 集群容错: failfast/failover/failsafe/forking
)
private UserService userService;
// 3. 超时与重试
@DubboReference(
version = "1.0.0",
timeout = 3000, // 超时3秒
retries = 0, // 不重试
check = false // 启动时不检查
)
private UserService userService;
// 4. 异步调用
@DubboReference(
version = "1.0.0",
async = true // 异步调用
)
private UserService userService;
public void asyncCall() {
userService.getUser(1L); // 异步调用
ResultFuture future = RpcContext.getContext().getFuture();
// 获取异步结果
User user = (User) future.get();
}
// 5. 版本控制
@DubboReference(version = "1.0.0") // 指定版本
private UserService userServiceV1;
@DubboReference(version = "2.0.0") // 不同版本
private UserService userServiceV2;
gRPC框架详解
gRPC介绍
gRPC 是Google开源的高性能、通用的开源RPC框架,基于HTTP/2和Protocol Buffers。
gRPC特点:
├── 高性能
│ ├── 使用HTTP/2协议 (支持多路复用)
│ ├── 二进制传输 (Protobuf序列化)
│ └── 比JSON快5-10倍
│
├── 跨语言
│ ├── 自动生成多语言代码
│ ├── 支持Java/Go/Python/C++等
│ └──IDL定义 (Protocol Buffers)
│
├── 流式传输
│ ├── 一元RPC (普通调用)
│ ├── 服务器流RPC
│ ├── 客户端流RPC
│ └─ 双向流RPC
│
└── 双向流
├── 实时通信
├── 推送消息
└─ 长连接
gRPC快速开始
1. 定义.proto文件
// user.proto
syntax = "proto3";
option java_multiple_files = true; // 生成多个Java文件
option java_package = "com.example.grpc"; // Java包名
option java_outer_classname = "UserProto"; // 外部类名
// 用户服务定义
service UserService {
// 一元RPC (普通调用)
rpc GetUser(GetUserRequest) returns (GetUserResponse);
// 服务器流RPC (返回多个结果)
rpc ListUsers(ListUsersRequest) returns (stream User);
// 客户端流RPC (接收多个参数)
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
// 双向流RPC (双向流式传输)
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
// 消息定义
message GetUserRequest {
int64 user_id = 1;
}
message GetUserResponse {
User user = 1;
}
message User {
int64 id = 1;
string username = 2;
string email = 3;
string phone = 4;
}
message ListUsersRequest {
int32 page = 1;
int32 size = 2;
}
message CreateUserRequest {
string username = 1;
string email = 2;
}
message CreateUsersResponse {
int32 count = 1;
repeated User users = 2;
}
message ChatMessage {
string message = 1;
int64 timestamp = 2;
}
2. 生成Java代码
<!-- pom.xml -->
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.21.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.49.0:exe:${os.detected.classifier}</pluginId>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- gRPC依赖 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.49.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.49.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.49.0</version>
</dependency>
</dependencies>
运行mvn clean compile生成Java代码。
3. 实现服务端
/**
* gRPC用户服务实现
*/
@GRpcService // Spring集成注解
public class UserServiceGrpcImpl extends UserServiceGrpc.UserServiceImplBase {
@Autowired
private UserMapper userMapper;
/**
* 一元RPC - 查询单个用户
*/
@Override
public void getUser(GetUserRequest request,
StreamObserver<GetUserResponse> responseObserver) {
try {
// 查询用户
UserPO userPO = userMapper.selectById(request.getUserId());
// 构建响应
UserProto.User user = UserProto.User.newBuilder()
.setId(userPO.getId())
.setUsername(userPO.getUsername())
.setEmail(userPO.getEmail())
.setPhone(userPO.getPhone())
.build();
GetUserResponse response = GetUserResponse.newBuilder()
.setUser(user)
.build();
// 返回结果
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (Exception e) {
responseObserver.onError(e);
}
}
/**
* 服务器流RPC - 查询用户列表 (流式返回)
*/
@Override
public void listUsers(ListUsersRequest request,
StreamObserver<User> responseObserver) {
try {
// 分页查询
Page<UserPO> page = new Page<>(request.getPage(), request.getSize());
List<UserPO> userPOs = userMapper.selectPage(page, null);
// 流式返回每个用户
for (UserPO userPO : userPOs) {
UserProto.User user = UserProto.User.newBuilder()
.setId(userPO.getId())
.setUsername(userPO.getUsername())
.build();
responseObserver.onNext(user); // 流式发送
}
responseObserver.onCompleted();
} catch (Exception e) {
responseObserver.onError(e);
}
}
/**
* 双向流RPC - 聊天功能
*/
@Override
public StreamObserver<ChatMessage> chat(
StreamObserver<ChatMessage> responseObserver) {
return new StreamObserver<ChatMessage>() {
@Override
public void onNext(ChatMessage message) {
// 接收客户端消息
log.info("收到消息: {}", message.getMessage());
// 回复消息
ChatMessage reply = ChatMessage.newBuilder()
.setMessage("Echo: " + message.getMessage())
.setTimestamp(System.currentTimeMillis())
.build();
responseObserver.onNext(reply);
}
@Override
public void onError(Throwable t) {
log.error("聊天出错", t);
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}
4. 实现客户端
/**
* gRPC客户端
*/
@Service
public class OrderService {
/**
* gRPC存根 - 自动注入
*/
@Autowired
private UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub;
/**
* 创建订单 - 调用gRPC服务
*/
public Order createOrder(OrderRequest request) {
// 构建请求
GetUserRequest grpcRequest = GetUserRequest.newBuilder()
.setUserId(request.getUserId())
.build();
// 同步调用gRPC服务
GetUserResponse response = userServiceBlockingStub.getUser(grpcRequest);
UserProto.User user = response.getUser();
// 创建订单
Order order = new Order();
order.setUserId(user.getId());
order.setUserName(user.getUsername());
return order;
}
/**
* 流式调用示例
*/
public List<User> listUsers(int page, int size) {
ListUsersRequest request = ListUsersRequest.newBuilder()
.setPage(page)
.setSize(size)
.build();
// 服务器流返回 - 使用迭代器
Iterator<User> iterator = userServiceBlockingStub.listUsers(request);
List<User> users = new ArrayList<>();
iterator.forEachRemaining(users::add);
return users;
}
}
框架对比总结
HTTP vs RPC对比
| 特性 | HTTP (Feign) | Dubbo | gRPC |
|---|---|---|---|
| 传输协议 | HTTP/1.1 | TCP | HTTP/2 |
| 序列化 | JSON | Hessian2 | Protobuf |
| 性能 | 中 | 高 | 很高 |
| 易用性 | 高 | 中 | 中 |
| 跨语言 | 完美 | 较好 | 完美 |
| 学习曲线 | 平缓 | 适中 | 陡峭 |
| 生态 | 完善 | 阿里系 | Google系 |
| 适用场景 | 通用微服务 | 内部高性能调用 | 跨语言高性能 |
选择建议
框架选择决策树:
是否需要跨语言调用?
├── 是 → 使用gRPC
│ └── 场景: 多语言团队、移动端、IoT设备
│
└── 否 → 是否追求极致性能?
├── 是 → 使用Dubbo
│ └── 场景: 内部微服务、高并发、低延迟
│
└── 否 → 使用Feign
└── 场景: 通用微服务、快速开发、易维护
Dubbo vs gRPC详细对比
Dubbo优势:
├── ✅ 国内生态完善 (阿里系)
├── ✅ 服务治理功能丰富
│ ├── 服务降级
│ ├── 服务限流
│ └── 服务熔断
├── ✅ 与Spring无缝集成
├── ✅ 支持多种协议
│ ├── Dubbo协议 (默认)
│ ├── HTTP协议
│ ├── gRPC协议
│ └── Thrift协议
└── ✅ 社区活跃,文档齐全 (中文)
gRPC优势:
├── ✅ 性能最优 (HTTP/2 + Protobuf)
├── ✅ 跨语言支持最好
├── ✅ 流式传输支持
│ ├── 服务器流
│ ├── 客户端流
│ └── 双向流
├── ✅ Google背书,标准化程度高
├── ✅ IDL定义清晰
└── ✅ 适合异构系统
选择建议:
- 国内Java项目 → Dubbo
- 国际化项目/多语言 → gRPC
- 需要流式传输 → gRPC
- 追求易用性 → Feign
负载均衡机制
客户端负载均衡原理
客户端负载均衡工作流程:
1. 服务发现
OrderService从注册中心获取user-service的所有实例
┌─────────────────────────────┐
│ user-service: [ │
│ "192.168.1.10:8081", │
│ "192.168.1.11:8082", │
│ "192.168.1.12:8083" │
│ ] │
└─────────────────────────────┘
2. 负载均衡
LoadBalancer从3个实例中选择一个 (根据策略)
选择: 192.168.1.11:8082
3. 发起请求
OrderService直接调用选中的实例
POST http://192.168.1.11:8082/api/users
4. 结果返回
实例返回响应结果
负载均衡策略
常见负载均衡策略:
1. RoundRobinLoadBalancer (轮询) - 默认
按顺序依次选择每个实例
实例1 → 实例2 → 实例3 → 实例1 → ...
适用: 实例性能相近的场景
2. RandomLoadBalancer (随机)
随机选择一个实例
适用: 大规模场景,理论上分布均匀
3. WeightedServiceInstanceListSupplier (加权)
根据权重选择实例
适用: 实例性能不一致的场景
超时与重试机制
超时配置
# application.yml
feign:
client:
config:
# 默认配置
default:
connect-timeout: 5000 # 连接超时5秒
read-timeout: 10000 # 读取超时10秒
# 特定服务配置
user-service:
connect-timeout: 3000
read-timeout: 5000
重试机制
/**
* Feign重试配置
*/
@Configuration
public class FeignRetryConfig {
/**
* 自定义重试器
*/
@Bean
public Retryer feignRetryer() {
// 最多重试3次,初始间隔100ms,最大间隔1s
return new Retryer.Default(100, 1000, 3);
}
/**
* 禁用重试 (推荐)
* 大多数场景下不应该重试,而是通过熔断降级处理
*/
@Bean
public Retryer noRetryer() {
return new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e; // 不重试,直接抛出异常
}
@Override
public Retryer clone() {
return this;
}
};
}
}
重试最佳实践
/**
* 重试策略建议
*/
public class RetryBestPractices {
/**
* 1. 幂等操作可以重试
* - GET请求
* - 查询操作
* - 状态更新为固定值的操作
*/
@GetMapping("/users/{id}")
User getUser(@PathVariable Long id); // 可以重试
/**
* 2. 非幂等操作不要重试
* - POST创建资源
* - DELETE删除资源
* - 累加操作
*/
@PostMapping("/orders")
Order createOrder(@RequestBody Order order); // 不要重试
/**
* 3. 使用熔断器代替重试
* 重试可能导致雪崩,熔断可以快速失败
*/
@FeignClient(
name = "user-service",
fallback = UserServiceFallback.class
)
public interface UserService {
// 推荐使用fallback处理失败,而不是重试
}
}
服务调用最佳实践
1. 服务拆分原则
/**
* 服务拆分建议
*/
public class ServiceSplitBestPractices {
/**
* 好的实践: 单一职责
*/
@FeignClient(name = "user-service")
public interface UserService {
// 只包含用户相关的操作
User getUser(Long id);
List<User> listUsers();
}
@FeignClient(name = "order-service")
public interface OrderService {
// 只包含订单相关的操作
Order getOrder(Long id);
List<Order> listOrders(Long userId);
}
}
2. 异常处理
/**
* 统一异常处理
*/
@ControllerAdvice
public class FeignExceptionHandler {
/**
* 处理Feign调用异常
*/
@ExceptionHandler(FeignException.class)
public Result handleFeignException(FeignException e) {
log.error("服务调用失败", e);
if (e.status() == 404) {
return Result.error("服务不存在");
} else if (e.status() == 500) {
return Result.error("服务内部错误");
} else if (e.status() == -1) {
return Result.error("网络超时");
}
return Result.error("服务调用失败: " + e.getMessage());
}
}
3. 性能优化
/**
* Feign性能优化清单
*/
public class FeignPerformanceOptimization {
/**
* 1. 使用连接池
*/
@Bean
public Client feignClient() {
return new ApacheHttpClient();
}
/**
* 2. 启用压缩
*/
// application.yml
// feign.compression.request.enabled=true
// feign.compression.response.enabled=true
/**
* 3. 合理设置超时时间
*/
// 连接超时: 3-5秒
// 读取超时: 根据业务设置,一般5-15秒
/**
* 4. 禁用重试
*/
@Bean
public Retryer noRetryer() {
return new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
}
}
实战场景应用
场景1: 电商下单
/**
* 电商下单场景 - 组合多个服务调用
*/
@Service
public class OrderBusinessService {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private InventoryService inventoryService;
/**
* 下单流程
*/
@GlobalTransactional
public Order placeOrder(OrderRequest request) {
// 1. 查询用户信息
User user = userService.getUser(request.getUserId());
if (!user.isActive()) {
throw new RuntimeException("用户状态异常");
}
// 2. 查询商品信息
Product product = productService.getProduct(
request.getProductId()
);
// 3. 扣减库存
inventoryService.deduct(
request.getProductId(),
request.getCount()
);
// 5. 创建订单
Order order = new Order();
order.setUserId(user.getId());
order.setProductId(product.getId());
order.setCount(request.getCount());
return order;
}
}
面试核心问题
Q1: RestTemplate和Feign有什么区别?
参考答案:
| 特性 | RestTemplate | Feign |
|---|---|---|
| 编程方式 | 命令式 | 声明式 |
| 代码量 | 多 | 少 |
| 可读性 | 一般 | 好 |
| 类型安全 | 弱 | 强 |
| 维护性 | 差 | 好 |
| 灵活性 | 高 | 中 |
| 学习曲线 | 平缓 | 陡峭 |
选择建议:
- 简单场景、需要精细控制 → RestTemplate
- 微服务场景、标准调用 → Feign
Q2: Feign是如何工作的?
参考答案:
Feign的工作原理:
- 启动时: 扫描@FeignClient注解的接口,为每个接口生成动态代理对象
- 调用时: 通过动态代理拦截方法调用
- 构建请求: 根据方法注解(@GetMapping等)构建HTTP请求
- 负载均衡: 从注册中心获取实例列表,选择一个实例
- 发送请求: 通过HttpClient发送HTTP请求
- 解析响应: 解码器解析响应,返回结果
Q3: 什么是客户端负载均衡?
参考答案:
客户端负载均衡是指由调用方决定调用哪个服务实例的机制。
流程:
- 客户端从注册中心获取服务实例列表
- 客户端根据负载均衡策略选择一个实例
- 客户端直接调用选中的实例
与服务端负载均衡的区别:
- 客户端均衡: Ribbon、Spring Cloud LoadBalancer
- 服务端均衡: Nginx、HAProxy
Q4: Spring Cloud LoadBalancer和Ribbon有什么区别?
参考答案:
| 特性 | Ribbon | LoadBalancer |
|---|---|---|
| 状态 | 维护模式 | 活跃开发 |
| 实现 | 阻塞IO | 响应式 |
| 性能 | 中 | 高 |
| Spring支持 | 需要配置 | 原生支持 |
结论: 新项目应该使用Spring Cloud LoadBalancer。
Q5: Feign的超时和重试如何配置?
参考答案:
# 超时配置
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 10000
// 重试配置
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, 1000, 3);
}
注意: 非幂等操作不要重试,推荐使用熔断器而不是重试。
Q6: 如何实现Feign的熔断降级?
参考答案:
@FeignClient(
name = "user-service",
fallback = UserServiceFallback.class
)
public interface UserService {
User getUser(Long id);
}
@Component
public class UserServiceFallback implements UserService {
@Override
public User getUser(Long id) {
// 返回降级数据
User defaultUser = new User();
defaultUser.setId(id);
defaultUser.setName("默认用户");
return defaultUser;
}
}
Q7: 如何优化Feign的性能?
参考答案:
优化措施:
- 使用连接池 (HttpClient)
- 启用请求/响应压缩
- 合理设置超时时间
- 禁用重试 (使用熔断代替)
- 使用缓存
- 异步调用
// 使用连接池
@Bean
public Client feignClient() {
return new ApacheHttpClient();
}
Q8: 如何在Feign中添加统一的请求头?
参考答案:
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
template.header("Authorization", "Bearer token123");
template.header("X-Trace-Id", MDC.get("traceId"));
};
}
}
Q9: Feign调用时如何传递对象参数?
参考答案:
@FeignClient(name = "order-service")
public interface OrderService {
// POST请求 - 直接传对象
@PostMapping("/orders")
Order createOrder(@RequestBody Order order);
// GET请求 - 使用@SpringQueryMap
@GetMapping("/orders/search")
List<Order> searchOrders(@SpringQueryMap OrderSearchQuery query);
}
Q10: 微服务调用如何保证事务一致性?
参考答案:
使用分布式事务方案:
- Seata AT模式 (推荐)
- Seata TCC模式 (高性能)
- 最终一致性 (消息队列)
@GlobalTransactional
public void placeOrder() {
orderService.create();
inventoryService.deduct();
accountService.deduct();
}
避坑指南
1. 超时时间设置
// 错误: 超时时间太短
@FeignClient(name = "user-service")
public interface UserService {
// 默认1秒超时,复杂查询容易超时
}
// 正确: 根据业务设置合理超时
feign:
client:
config:
default:
connect-timeout: 5000 # 连接超时5秒
read-timeout: 10000 # 读取超时10秒
2. 服务名不要写错
// 错误: 服务名拼写错误
@FeignClient(name = "user-servic") // 少了一个e
public interface UserService { }
// 正确: 与注册中心的服务名一致
@FeignClient(name = "user-service")
public interface UserService { }
3. 参数类型要匹配
// 错误: 参数类型不匹配
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") String id); // 应该是Long
}
// 正确: 类型与服务端一致
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
4. 不要忘记路径变量
// 错误: 缺少@PathVariable
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/users/{id}")
User getUser(Long id); // 缺少@PathVariable("id")
}
// 正确: 添加@PathVariable注解
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
5. 避免循环调用
// 错误: 服务A调用服务B,服务B又调用服务A
@ServiceA.callServiceB()
→ @ServiceB.callServiceA()
→ @ServiceA.callServiceB()
... 死循环
// 正确: 重新设计服务边界,避免循环依赖
// 将公共功能抽取为独立的服务C
@ServiceA.callServiceC()
@ServiceB.callServiceC()
6. 大对象不要通过Feign传递
// 错误: 传递大列表
@FeignClient(name = "order-service")
public interface OrderService {
@GetMapping("/orders")
List<Order> getAllOrders(); // 可能有几万条数据
}
// 正确: 使用分页
@FeignClient(name = "order-service")
public interface OrderService {
@GetMapping("/orders")
PageResult<Order> getOrders(
@RequestParam("page") Integer page,
@RequestParam("size") Integer size
);
}
7. 注意Feign的GET请求传对象
// 错误: GET请求使用@RequestBody
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/users/search")
List<User> searchUsers(@RequestBody UserSearch query); // 错误!
}
// 正确: 使用@SpringQueryMap或多个@RequestParam
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/users/search")
List<User> searchUsers(@SpringQueryMap UserSearch query);
}
总结
Spring Cloud服务调用是微服务架构的核心功能,通过本文你应该掌握了:
学习路线:
├── 1. 理解基础概念
│ ├── 服务调用原理
│ ├── 客户端负载均衡
│ └── 服务发现机制
│
├── 2. 掌握调用方式
│ ├── RestTemplate (命令式)
│ └── OpenFeign (声明式,推荐)
│
├── 3. 负载均衡
│ ├── Spring Cloud LoadBalancer
│ ├── 负载均衡策略
│ └── 自定义配置
│
├── 4. 容错处理
│ ├── 超时配置
│ ├── 重试机制
│ └── 熔断降级
│
└── 5. 生产实践
├── 性能优化
├── 日志监控
├── 异常处理
└── 最佳实践
核心要点:
- 优先使用Feign: 声明式调用,代码更优雅
- 合理配置超时: 根据业务设置合理的超时时间
- 慎用重试: 非幂等操作不要重试
- 使用熔断降级: 避免雪崩效应
- 性能优化: 使用连接池、压缩、缓存
掌握Spring Cloud服务调用,能够帮助你在微服务架构中构建高效、稳定的分布式系统!