跳到主要内容

Spring Cloud 服务调用完全指南

"在微服务架构中,服务间调用是连接各个微服务的桥梁,掌握服务调用机制是构建微服务的基础"

📚 目录


服务调用基础

什么是服务调用?

在微服务架构中,服务调用是指一个服务通过网络协议(通常是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)DubbogRPC
传输协议HTTP/1.1TCPHTTP/2
序列化JSONHessian2Protobuf
性能很高
易用性
跨语言完美较好完美
学习曲线平缓适中陡峭
生态完善阿里系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有什么区别?

参考答案:

特性RestTemplateFeign
编程方式命令式声明式
代码量
可读性一般
类型安全
维护性
灵活性
学习曲线平缓陡峭

选择建议:

  • 简单场景、需要精细控制 → RestTemplate
  • 微服务场景、标准调用 → Feign

Q2: Feign是如何工作的?

参考答案:

Feign的工作原理:

  1. 启动时: 扫描@FeignClient注解的接口,为每个接口生成动态代理对象
  2. 调用时: 通过动态代理拦截方法调用
  3. 构建请求: 根据方法注解(@GetMapping等)构建HTTP请求
  4. 负载均衡: 从注册中心获取实例列表,选择一个实例
  5. 发送请求: 通过HttpClient发送HTTP请求
  6. 解析响应: 解码器解析响应,返回结果

Q3: 什么是客户端负载均衡?

参考答案:

客户端负载均衡是指由调用方决定调用哪个服务实例的机制。

流程:

  1. 客户端从注册中心获取服务实例列表
  2. 客户端根据负载均衡策略选择一个实例
  3. 客户端直接调用选中的实例

与服务端负载均衡的区别:

  • 客户端均衡: Ribbon、Spring Cloud LoadBalancer
  • 服务端均衡: Nginx、HAProxy

Q4: Spring Cloud LoadBalancer和Ribbon有什么区别?

参考答案:

特性RibbonLoadBalancer
状态维护模式活跃开发
实现阻塞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的性能?

参考答案:

优化措施:

  1. 使用连接池 (HttpClient)
  2. 启用请求/响应压缩
  3. 合理设置超时时间
  4. 禁用重试 (使用熔断代替)
  5. 使用缓存
  6. 异步调用
// 使用连接池
@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. 生产实践
├── 性能优化
├── 日志监控
├── 异常处理
└── 最佳实践

核心要点:

  1. 优先使用Feign: 声明式调用,代码更优雅
  2. 合理配置超时: 根据业务设置合理的超时时间
  3. 慎用重试: 非幂等操作不要重试
  4. 使用熔断降级: 避免雪崩效应
  5. 性能优化: 使用连接池、压缩、缓存

掌握Spring Cloud服务调用,能够帮助你在微服务架构中构建高效、稳定的分布式系统!