我正在参加「启航计划」
Spring Cloud OpenFeign 它是 Spring 官方推出的一种声明式服务调用与负载均衡组件。它底层根据 Netflix Feign,Netflix Feign 是 Netflix 规划的开源的声明式 WebService 客户端,用于简化服务间通讯。
Spring Cloud openfeign 对 Feign 进行了增强,使其支撑 Spring MVC 注解,另外还整合了 Ribbon 和 Nacos,从而使得 Feign 的运用愈加方便。
docs.spring.io/spring-clou…
原理
用到了动态代理
对比 Dubbo
OpenFeign 如同不能直接调用 service 层,一般的写法是服务供给方有必要写 controller 接口,而 dubbo 是不用写 controller 接口的。假如不能直接调用 service 层,有必要写 controller 层,那么这应该是一个缺点,因为每次都额定要写一个 controller 办法。
而且在服务顾客方,还要额定写个有@FeignClient
注解的远程调用 service 接口。看上去 Spring Cloud 的OpenFeign
比dubbo
麻烦不少。
引进依靠
运用 OpenFeign 组件需求引进客户端依靠
<!--OpenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
运用案例
在发动类上加上@EnableFeignClients
注解
/**
* 被调用方
*/
@RestController
public class WarehouseController {
/**
* 查询对应 skuId 的库存状况
* @param skuId skuId
* @return Stock 库存目标
*/
@GetMapping("/stock")
public Stock getStock(Long skuId){
// ...省略
}
}
/**
* 调用方
*/
@FeignClient("warehouse-service")
public interface WarehouseServiceFeignClient {
@GetMapping("/stock")
public Stock getStock(@RequestParam("skuId") Long skuId);
}
@FeignClient 注解说明当前接口为 OpenFeign 通讯客户端,参数值 warehouse-service 为服务供给者 ID(留意,OpenFeign服务名称不支撑下划线_,这是一个坑),这一项有必要与 Nacos 注册 ID 保持一致。
在 OpenFeign 发送恳求前会主动在 Nacos 查询 warehouse-service 一切可用实例信息,再经过内置的 Ribbon 负载均衡挑选一个实例建议 RESTful 恳求,进而保证通讯高可用。
tips: 这里的返回值虽然写的都是同一个类型,但他们可不一定是同一个类,本质上是以 Json 交互的,只需特点对的上即可。
@FeignClient 注解特点详解
contextId: 假如装备了contextId,该值将会作为beanName。
fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类有必要完结@FeignClient符号的接口
fallbackFactory: 工厂类,用于生成fallback类示例,经过这个特点咱们能够完结每个接口通用的容错逻辑,减少重复的代码
url: url一般用于调试,能够手动指定@FeignClient调用的地址
Spring Cloud CircuitBreaker Fallbacks
当断路器打开或呈现过错时履行的默许逻辑,能够经过fallback
来装备
留意:您还需求将其声明为Spring bean
。
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
假如需求访问导致回退触发的原因,能够运用fallbackFactory
特点
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
@Override
public FallbackWithFactory create(Throwable cause) {
return new FallbackWithFactory();
}
}
static class FallbackWithFactory implements TestClientWithFactory {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
敞开日志
工作中咱们有些出产问题呈现在微服务 OpenFeign 的互相调用中,然而 OpenFeign 的调用默许是不打日志的。
咱们能够经过代码装备或许装备文件装备
//全局装备
@Configuration
public class FeignLoggerConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
//部分装备
//不需求加Configuration注解
public class FeignConfig {
@Bean
public Logger.Level feignLogLevel(){
//设置feign客户端的日志打印级别为FULL
return Logger.Level.FULL;
}
}
//configuration特点,值为Class,装备信息对名称为userprivilege的服务收效
@Resource
@FeignClient(name="userprivilege",fallback = FallBack_PrivilegeService.class ,configuration = FeignConfig.class)
public interface PrivilegeService {
}
feign:
client:
config:
default: # 项目全局
loggerLevel: HEADERS
order-service: #@FeignClient注解中装备的服务名
loggerLevel: FULL
Level 有四个等级
- NONE 不打日志,默许值
- BASIC 只记载 method、url、呼应码,履行时间
- HEADERS 只记载恳求和呼应的 header
- FULL 全部都记载
上面修改了 openfeign 的日志级别是 debug,但是 springboot 默许日志级别是 info,因为 debug<info,所以需求也改为debug,openfeign 的日志才会收效
logging:
level:
com.base.service: debug # 这里是openfeign client 地点的包途径
上传文件
www.baeldung.com/java-feign-…
@PostMapping(value = "/upload-file")
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
// File upload logic
}
public class FeignSupportConfig {
@Bean
public Encoder multipartFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<HttpMessageConverters>() {
@Override
public HttpMessageConverters getObject() throws BeansException {
return new HttpMessageConverters(new RestTemplate().getMessageConverters());
}
}));
}
}
@FeignClient(name = "file", url = "http://localhost:8081", configuration = FeignSupportConfig.class)
public interface UploadClient {
@PostMapping(value = "/upload-file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String fileUpload(@RequestPart(value = "file") MultipartFile file);
}
功能优化
替换默许通讯组件
OpenFeign 默许运用 Java 自带的 URLConnection 目标创立 HTTP 恳求,但接入出产时,假如能将底层通讯组件更换为 Apache HttpClient、OKHttp 这样的专用通讯组件,根据这些组件自带的衔接池,能够更好地对 HTTP 衔接目标进行重用与管理。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!-- 或许增加 httpclient 框架依靠 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
然后在装备文件中加入如下:
feign:
okhttp:
enabled: true
# 或许
feign:
httpclient:
enabled: true
经过上面设置现已能够运用okhttp了,因为在FeignAutoConfiguration
中已完结主动装配
数据紧缩
在 OpenFeign 中,默许并没有敞开数据紧缩功用。但假如你在服务间单次传递数据超越 1K 字节,强烈推荐敞开数据紧缩功用。默许 OpenFeign 运用 Gzip 方法紧缩数据,关于大文本一般紧缩后尺度只相当于原始数据的 10%~30%,这会极大提高带宽利用率。,在项目装备文件 application.yml 中增加以下装备:
feign:
compression:
request:
enabled: true # 敞开恳求数据的紧缩功用
mime-types: text/xml,application/xml, application/json # 紧缩类型
min-request-size: 1024 # 最小紧缩值标准,当数据大于 1024 才会进行紧缩
response:
enabled: true # 敞开呼应数据紧缩功用
Tip提示: 假如应用归于计算密集型,CPU 负载长期超越 70%,因数据紧缩、解紧缩都需求 CPU 运算,敞开数据紧缩功用反而会给 CPU 增加额定负担,导致系统功能降低,这是不可取的。这种状况 建议不要敞开数据的紧缩功用
负载均衡
OpenFeign 运用时默许引证 Ribbon 完结客户端负载均衡,它默许的负载均衡战略是轮询战略。那怎么设置 Ribbon 默许的负载均衡战略呢?
只需在 application.yml 中调整微服务通讯时运用的负载均衡类即可。
warehouse-service: #服务供给者的微服务ID
ribbon:
#设置对应的负载均衡类
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Tip提示: 出于功能方面的考虑,咱们能够挑选用权重战略或区域灵敏战略来替代轮询战略,因为这样的履行效率最高。
一种封装OpenFegin的实践
在了解这种实践前,咱们需求先知道Feign对接口承继的写法支撑
Feign 承继支撑
参阅:docs.spring.io/spring-clou…
Feign经过单承继接口支撑模板api。这答应将常见操作分组到方便的基本接口中。
Feign 支撑承继和多重承继,这样咱们能够将API露出到接口当中,服务供给者完结该接口来供给服务,服务顾客只需求引进该接口地点的API模块即可。
但新近时分官方并不倡议这么做,相关考虑可见:
- why-is-it-such-a-bad-idea-to-share-an-interface-between-server-and-client
- should-i-use-a-single-interface-for-feign-and-spring-mvc
public interface UserService {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}
@RestController
public class UserResource implements UserService {
@Override
public User getUsers() {
...
}
}
@FeignClient("users")
public interface UserClient extends UserService {
}
tips: @FeignClient接口不该该在服务器和客户端之间共享,而且不支撑在类级别上用@RequestMapping注释@FeignClient接口。
实践
将 Feign 的 Client 抽取为独立模块,而且把接口有关的 POJO、默许的 Feign 装备都放到这个模块中,供给给一切顾客运用。
例如:将 UserClients、User、Feign 的默许装备都抽取到一个 feign-api 包中,一切微服务引证该依靠包,即可直接运用。
服务供给方开发实践
RPC 接口和完结各自放在独立的模块中,方便服务调用方重用服务接口,服务接口模块只能包括最基本的模块依靠(过多会导致依靠传递)。
服务的接口及相关模型有必要放在同一模块中,该模块仅包括这些内容,服务供给方的接口中不能指定 FeignClient 注解(在消费方指定),在办法中能够运用 @RequestMapping 等注解。
例如:
account
- account-api
- account-service
account-api 模块中放消费方需求用到的东西,api接口,vo,入参等…
public interface AccountApi {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}
account-service 完结 account-api 供给的接口
@RestController
@Api(tags = "用户接口")
public class AccountController implements AccountApi {
...
}
服务消费方开发实践
引证 RPC 接口模块,在消费方编写 feign 客户端类承继相关接口并处理熔断,在客户端接口上增加 FeignClient 注解即可。
@Component
@FeignClient(name = "account-service",fallbackFactory = AccountClientFallbackFactory.class)
public interface AccountClient extends AccountApi {
...
}
@Component
public class AccountClientFallbackFactory implements FallbackFactory<AccountClient> {
@Override
public AccountClient create(Throwable throwable) {
AccountClientFallback accountClientFallback = new AccountClientFallback();
accountClientFallback.setCause(throwable);
return accountClientFallback;
}
}
@Slf4j
public class AccountClientFallback implements AccountClient {
@Setter
private Throwable cause;
@Override
public ResultData<AccountDTO> getByCode(String accountCode) {
...
}
}
扩展
Spring Cloud OpenFeign 特性完结公告
因为 Spring 现在供给了自己的 HTTP 接口客户端解决方案,比如在最新的 Spring Boot 3.0 中完结接口调用能够有以下两种解决方案:
- RestTemplate
- WebClient
所以,从 Spring Cloud 2022.0.0 版本开端,Spring Cloud OpenFeign 模块现已视为功用完结状况了,这意味着 Spring Cloud 团队将不再向该模块增加新功用。
虽然 OpenFeign 不会再增加新功用,但仍是会继续修复过错和安全问题,而且也还会考虑和检查来自社区的小规模的 pull requests 恳求。
HTTP Interface
Spring 6 中带来了一个新的特性——HTTP Interface。这个新特性,能够让开发者将 HTTP 服务,定义成一个包括特定注解符号的办法的 Java 接口,然后经过对接口办法的调用,完结 HTTP 恳求。看起来很像运用Feign
来完结远程服务调用。
docs.spring.io/spring-fram…