RPC远程调用组件-OpenFeign基础知识

RPC远程调用组件-OpenFeign基础知识

概述

OpenFeign介绍

​ OpenFeign 是⼀个声明式远程调⽤客户端,通过注解的方式可以更方便的发起远程调用,相较于编程式的RestTemplate,OpenFeign能够通过使用SpringMvc注解来声明远程调用的请求,使用了动态代理技术来封装远程服务调用的过程,并且能够与SpringCloud其他组件完美配合。

基础用法

pom依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableFeignClients//开启OpenFeign客户端
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

声明Feign客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* OpenFeign会生成StoreClient的实现,并由Spring容器进行管理
* 使用服务名,会从注册中心获取服务实例,负载均衡调用
*/
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();

@GetMapping("/stores")
Page<Store> getStores(Pageable pageable);

@PostMapping(value = "/stores/{storeId}", consumes = "application/json",params = "mode=upsert")
Store update(@PathVariable("storeId") Long storeId, Store store);

@DeleteMapping("/stores/{storeId:\\d+}")
void delete(@PathVariable Long storeId);
}

三方客户端声明

1
2
3
4
5
6
7
8
9
10
11
/**
* 不在相同注册中心的接口,使用url精确指明地址(域名和直接写ip都可以)
*/
@FeignClient(value = "weather-client", url = "http://aliv18.data.moji.com")
public interface WeatherFeignClient {

@PostMapping("/whapi/json/alicityweather/condition")
String getWeather(@RequestHeader("Authorization") String auth,
@RequestParam("token") String token,
@RequestParam("cityId") String cityId);
}

超时配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
openfeign:
client:
config:
# 客户端名称(其中default默认配置)
default:
logger-level: full
connect-timeout: 1000
read-timeout: 2000
stores:
logger-level: full
connect-timeout: 3000
read-timeout: 5000

重试配置

​ 当发生超时后,OpenFeign默认是不进行重试的。可以在yaml中进行配置,或在Spring容器中放入Retryer的实现。OpenFeign提供的重试机制可配置三个参数:

  • period:初始超时时间,控制第一次超时的时间(ms)
  • maxPeriod:最大超时时间,除第一次请求外,每次重试的超时时间为上一次请求超时时间的1.5倍,如果超过maxPeriod,则取maxPeriod
  • maxAttempts:最大重试次数

拦截器

​ OpenFeign提供了请求拦截器和响应拦截器,请求拦截器可以在发生请求前对请求进行处理。如下代码声明了一个请求拦截器,放入Spring容器的拦截器会自动生效,该拦截器在发生调用前,在请求头里添加了X-Token。

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class XTokenRequestInterceptor implements RequestInterceptor {

/**
* 请求拦截器
* @param requestTemplate 请求模版
**/
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("X-Token", UUID.randomUUID().toString());
}
}

兜底返回Fallback

​ 此功能需要搭配SpringCloud组件Sentinel。当请求远程服务失败,未成功获取到响应时,可通过该功能配置兜底返回逻辑。

1
2
3
feign:
sentinel:
enabled: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}

@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long id) {
System.out.println("兜底回调....");
Product product = new Product();
product.setId(id);
product.setPrice(new BigDecimal("0"));
product.setProductName("未知商品");
product.setNum(0);
return product;
}
}

扩展内容

请求客户端配置

​ OpenFeign中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。

配置Apache HttpClient

​ 引入依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>

<!-- Feign 集成 Apache HttpClient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.1.0</version>
</dependency>

​ 修改yml配置,将 Feign 的 Apache HttpClient启用。

1
2
3
4
feign:
# feign 使用 Apache HttpClient 可以忽略,默认开启
httpclient:
enabled: true

​ 关于配置可参考源码: org.springframework.cloud.openfeign.FeignAutoConfiguration,内部有很多@ConditionalOnProperty注解。

配置 OkHttp

​ 引入依赖。

1
2
3
4
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>

​ 修改yml配置,将 Feign 的 HttpClient 禁用,启用 OkHttp,配置如下:

1
2
3
4
5
6
7
feign:
# 禁用默认 HttpClient
httpclient:
enabled: false
# 启用 OkHttp 作为底层客户端
okhttp:
enabled: true

手动创建FeignClient

​ 在某些情况下,OpenFeign提供的各类配置可能无法满足你的需求,在这种情况下,你可以使用 Feign Builder API 来创建客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//由Spring Cloud OpenFeign提供的默认配置
@Import(FeignClientsConfiguration.class)
class FooController {

private FooClient fooClient;

private FooClient adminClient;

@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");

this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}

SpringCache

​ 如果使用了 @EnableCaching 注解,就会创建并注册一个 CachingCapability Bean,这样你的 Feign 客户端就能识别其接口上的 @Cache* 注解。

1
2
3
4
5
6
public interface DemoClient {

@GetMapping("/demo/{filterParam}")
@Cacheable(cacheNames = "demo-cache", key = "#keyParam")
String demoEndpoint(String keyParam, @PathVariable String filterParam);
}

RefreshScope

@RefreshScope 是 Spring Cloud 中用于动态刷新 Bean 配置的重要注解之一。它主要用来解决在 Spring Cloud 项目中通过 Spring Cloud Config 管理配置时,当外部配置发生变化时,如何实时刷新 Bean 中的配置问题。

​ 默认情况下,Feign 客户端的刷新行为处于禁用状态,需要配置开启。

1
2
3
4
5
spring:
cloud:
openfeign:
client:
refresh-enabled: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
public class FeignConfiguration {

@Value("${feign.client.config.default.connectTimeout:5000}")
private int connectTimeout;

@Value("${feign.client.config.default.readTimeout:10000}")
private int readTimeout;

@Value("${feign.retry.maxAttempts:3}")
private int maxAttempts;

@Value("${feign.retry.period:1000}")
private long period;

@Bean
@RefreshScope
public Request.Options options() {
return new Request.Options(connectTimeout, readTimeout);
}

@Bean
@RefreshScope
public Retryer retryer() {
return new Retryer.Default(period, period * 2, maxAttempts);
}
}

结合Apollo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class FeignConfigChangeListener {

@Autowired
private ApplicationContext applicationContext;

@ApolloConfigChangeListener("feign-config")
private void onChange(ConfigChangeEvent changeEvent) {
refreshFeignConfiguration();
}

private void refreshFeignConfiguration() {
// 触发配置刷新
RefreshScope refreshScope = applicationContext.getBean(RefreshScope.class);
refreshScope.refreshAll();
}
}