Skip to content

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: true

3. 熔断降级

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: default

3.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 ClientsSpring 7 新特性

下一章将学习 Spring Cloud 配置中心。