Spring Cloud 服务调用
1. 服务调用概述
1.1 服务调用方式
| 方式 | 说明 | 特点 |
|---|---|---|
| RestTemplate | 传统 HTTP 客户端 | 简单直接 |
| OpenFeign | 声明式 HTTP 客户端 | 接口化、易用 |
| WebClient | 响应式 HTTP 客户端 | 非阻塞、响应式 |
| gRPC | 高性能 RPC | 二进制协议、高性能 |
2. OpenFeign
2.1 添加依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>2.2 启用 OpenFeign
java
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}2.3 定义 Feign 客户端
java
@FeignClient(
name = "user-service",
path = "/api/users",
configuration = FeignConfig.class,
fallbackFactory = UserClientFallback.class
)
public interface UserClient {
@GetMapping("/{id}")
User getUser(@PathVariable Long id);
@GetMapping
List<User> getUsers(@RequestParam List<Long> ids);
@PostMapping
User createUser(@RequestBody UserRequest request);
@PutMapping("/{id}")
User updateUser(@PathVariable Long id, @RequestBody UserRequest request);
@DeleteMapping("/{id}")
void deleteUser(@PathVariable Long id);
@GetMapping("/search")
Page<User> search(@RequestParam String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size);
}2.4 Feign 配置
java
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public RequestInterceptor authInterceptor() {
return template -> {
String token = SecurityContextHolder.getContext()
.getAuthentication()
.getCredentials()
.toString();
template.header("Authorization", "Bearer " + token);
};
}
@Bean
public Encoder feignEncoder(ObjectMapper objectMapper) {
return new SpringEncoder(() -> new HttpMessageConverters(
new MappingJackson2HttpMessageConverter(objectMapper)
));
}
@Bean
public Decoder feignDecoder(ObjectMapper objectMapper) {
return new SpringDecoder(() -> new HttpMessageConverters(
new MappingJackson2HttpMessageConverter(objectMapper)
));
}
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, 1000, 3);
}
}2.5 全局配置
yaml
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: FULL
user-service:
connectTimeout: 3000
readTimeout: 5000
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
circuitbreaker:
enabled: true3. 熔断降级
3.1 添加依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>3.2 Fallback 实现
java
@Component
public class UserClientFallback implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getUser(Long id) {
log.warn("Fallback: getUser({})", id, cause);
return new User(id, "默认用户", "default@example.com");
}
@Override
public List<User> getUsers(List<Long> ids) {
log.warn("Fallback: getUsers({})", ids, cause);
return Collections.emptyList();
}
@Override
public User createUser(UserRequest request) {
log.warn("Fallback: createUser", cause);
throw new RuntimeException("服务暂时不可用");
}
@Override
public User updateUser(Long id, UserRequest request) {
log.warn("Fallback: updateUser({})", id, cause);
return null;
}
@Override
public void deleteUser(Long id) {
log.warn("Fallback: deleteUser({})", id, cause);
}
@Override
public Page<User> search(String keyword, int page, int size) {
log.warn("Fallback: search({})", keyword, cause);
return new PageImpl<>(Collections.emptyList());
}
};
}
}3.3 Resilience4j 配置
yaml
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: 10
slidingWindowType: COUNT_BASED
failureRateThreshold: 50
slowCallDurationThreshold: 2s
slowCallRateThreshold: 50
waitDurationInOpenState: 10s
permittedNumberOfCallsInHalfOpenState: 5
minimumNumberOfCalls: 5
instances:
user-service:
baseConfig: default
timelimiter:
configs:
default:
timeoutDuration: 3s
cancelRunningFuture: true
instances:
user-service:
timeoutDuration: 5s
retry:
configs:
default:
maxAttempts: 3
waitDuration: 500ms
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
retryExceptions:
- java.io.IOException
- java.net.ConnectException
instances:
user-service:
baseConfig: default3.4 使用熔断注解
java
@Service
public class OrderService {
private final UserClient userClient;
@CircuitBreaker(name = "user-service", fallbackMethod = "getUserFallback")
@Retry(name = "user-service")
@RateLimiter(name = "user-service")
@TimeLimiter(name = "user-service")
public CompletableFuture<User> getUserAsync(Long userId) {
return CompletableFuture.supplyAsync(() -> userClient.getUser(userId));
}
public CompletableFuture<User> getUserFallback(Long userId, Throwable t) {
return CompletableFuture.completedFuture(
new User(userId, "默认用户", "default@example.com")
);
}
}4. 请求拦截
4.1 认证拦截器
java
@Component
public class AuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
}
}
}4.2 日志拦截器
java
@Component
public class LoggingRequestInterceptor implements RequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
@Override
public void apply(RequestTemplate template) {
log.info("Feign Request: {} {} - Headers: {}",
template.method(),
template.url(),
template.headers());
}
}5. 响应式客户端
5.1 WebClient
java
@Configuration
public class WebClientConfig {
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(logRequest())
.filter(logResponse());
}
private ExchangeFilterFunction logRequest() {
return (request, next) -> {
log.info("Request: {} {}", request.method(), request.url());
return next.exchange(request);
};
}
private ExchangeFilterFunction logResponse() {
return (request, next) -> next.exchange(request)
.doOnNext(response ->
log.info("Response: {}", response.statusCode()));
}
}
@Service
public class ReactiveUserClient {
private final WebClient webClient;
public ReactiveUserClient(@LoadBalanced WebClient.Builder builder) {
this.webClient = builder
.baseUrl("http://user-service/api/users")
.build();
}
public Mono<User> getUser(Long id) {
return webClient.get()
.uri("/{id}", id)
.retrieve()
.bodyToMono(User.class)
.onErrorResume(e -> Mono.just(new User(id, "默认用户", "default@example.com")));
}
public Flux<User> getUsers(List<Long> ids) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("ids", String.join(",",
ids.stream().map(String::valueOf).toList()))
.build())
.retrieve()
.bodyToFlux(User.class);
}
public Mono<User> createUser(UserRequest request) {
return webClient.post()
.bodyValue(request)
.retrieve()
.bodyToMono(User.class);
}
}5.2 响应式 Feign
java
@FeignClient(name = "user-service", path = "/api/users")
public interface ReactiveUserClient {
@GetMapping("/{id}")
Mono<User> getUser(@PathVariable Long id);
@GetMapping
Flux<User> getUsers(@RequestParam List<Long> ids);
@PostMapping
Mono<User> createUser(@RequestBody UserRequest request);
}6. 负载均衡
6.1 自定义负载均衡
java
@Configuration
public class LoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(
Environment environment,
LoadBalancerClientFactory factory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new WeightedLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
private final String serviceId;
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
List<ServiceInstance> weighted = new ArrayList<>();
for (ServiceInstance instance : instances) {
int weight = Integer.parseInt(
instance.getMetadata().getOrDefault("weight", "1")
);
for (int i = 0; i < weight; i++) {
weighted.add(instance);
}
}
int index = ThreadLocalRandom.current().nextInt(weighted.size());
return new DefaultResponse(weighted.get(index));
});
}
}7. HTTP Service Clients (Spring 7)
7.1 声明式 HTTP 服务
java
@HttpServiceClient
public interface UserHttpClient {
@GetExchange("/api/users/{id}")
User getUser(@PathVariable Long id);
@GetExchange("/api/users")
List<User> getUsers(@RequestParam List<Long> ids);
@PostExchange("/api/users")
User createUser(@RequestBody UserRequest request);
@PutExchange("/api/users/{id}")
User updateUser(@PathVariable Long id, @RequestBody UserRequest request);
@DeleteExchange("/api/users/{id}")
void deleteUser(@PathVariable Long id);
}
@Configuration
public class HttpClientConfig {
@Bean
public UserHttpClient userHttpClient() {
return HttpServiceClientProxyFactory.builder()
.baseUrl("http://user-service")
.build(UserHttpClient.class);
}
}8. 小结
本章学习了 Spring Cloud 服务调用的核心内容:
| 内容 | 要点 |
|---|---|
| OpenFeign | 声明式客户端、配置 |
| 熔断降级 | Resilience4j、Fallback |
| 请求拦截 | 认证、日志 |
| 响应式客户端 | WebClient、响应式 Feign |
| 负载均衡 | 自定义策略、权重 |
| HTTP Service Clients | Spring 7 新特性 |
下一章将学习 Spring Cloud 配置中心。