接下来使用一个积分项目进行演示。这个项目分为3个微服务:商品(goods)、用户(customer)、计算(caculation)。用户通过购买商品获取积分;不同的商品可能参加了不同的活动,有的商品不赠送积分;不同的用户等级可能通过购物获取的积分不同。
要使用 openFeign 组件,首先我们需要先引入相应的依赖。注意,使用openFeign 还需要引入loadBalancer 的依赖:
org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.cloud spring-cloud-starter-openfeign
由于在父项目中已经引入了 spring-cloud-dependencies 的依赖,上述2个组件的版本号可通过版本仲裁确定,无需指定。
实现OpenFeign组件的远程调用,首先需要实现@FeignClient 注解修饰的接口,该接口会将本地调用转化成远程的接口调用。
@FeignClient(value = "coin-goods-svc", path = "/goods") public interface GoodsService { // 获取指定商品 @GetMapping("/getGoods") GoodsInfo getGoods(@RequestParam("id") Long id); // 获取整个购物车内的商品 @GetMapping("/getBatch") MapgetGoodsCart(@RequestParam("ids") Collection ids); }
原来我们是直接通过 webclient 直接发起的远程接口调用,内容如下:
webClientBuilder.build().get() .uri("http://coin-goods-svc/goods/getGoods?id=" + id) .retrieve() .bodyToMono(GoodsInfo.class) .block();
现在我们直接注入 GoodsService 并调取对应方法:
@Autowired private GoodsService goodsService; public Integer getGoodsCoin(Long id) { // 忽略无关逻辑 // 获取指定商品 GoodsInfo goods = goodsService.getGoods(id); }
现在我们不必再业务代码里面指定调用接口的URI 和 Method 了,做到了代码的职责分离。
接下来,我们需要在微服务的启动类上加上 @EnableFeignClients 注解,这样服务启动时的,才会扫描指定目录下使用了 @FeignClient 注解的接口,并为其生成对应的动态代理实例。
// 省略其它注解 @EnableFeignClients(basePackages = {"com.fyup"}) public class CustomerApplication{ // 忽略具体实现 }
我们也可以不指定具体的扫描路径,而是直接指定要进行代理的接口,不过这种方式在 OpenFeign 远程调用接口较多时会很麻烦,不具备扩展性,不符合开闭原则。
// 通过指定具体的远程调用接口 // 省略其它注解 @EnableFeignClients(clients = {GoodsService.class}) public class CustomerApplication{ // 忽略具体实现 }
OpenFeign 还提供了在日志中打印远程调用细节的功能,只需要开启相应配置,并向 Spring Bean 容器内注册对应的 Bean 即可。可打印的日志分为4个级别,这里贴一下源码的截图:
如上所示,如果我们选择 Level.FULL 级别,会打印完整的 Request 和 Response 的 Header、Body。
因为 OpenFeign 组件内的日志都是以 Debug 级别输出的,所以我们需要现将对应的远程调用接口的日志输出级别打开。
logging: level: com.fyup.coin.customer.feign.GoodsService: debug
因为要打印完整的 URL、Method、以及Request 和 Response 的 Header、Body 等信息,所以还需要在配置类中注入 Level.FULL 的 Bean。实现如下:
@Bean Logger.Level feignLogger() { return Logger.Level.FULL; }
接口超时不响应会悬挂消费者请求,大量的请求超时未响应会给系统造成很大压力,在调用链路过长的情况下可能会在系统内部产生雪崩反应。
我们可以在Feign客户端的application.yml 文件中的 feign.client.config 配置项配置Feign 客户端远程调用的超时时间。feign.client.config.default 配置项配置全局的超时时间,通过 feign.client.config.${serviceName} 配置访问某个特定微服务的超时时间。 示例配置如下:
# 其它忽略 feign: client: config: # 全局超时配置 default: connectTimeout: 1000 readTimeout: 3000 # 针对coin-goods-svc服务的超时配置 coin-goods-svc: connectTimeout: 1000 readTimeout: 2000
其中,connectTimeout 表示的是服务消费者与服务提供者建立远程连接的超时时间;
readTimeout 是指从发出请求开始,到服务端响应请求之前为客户端设置的请求超时时间。
我们也可以使用 OpenFeign 组件实现服务降级,用法就是使用 @FeignClient 注解修饰远程调用接口时,使用 @FiegnClient 注解的 fallback 属性或者 fallbackFactory 属性指定降级方法的降级类即可。
因为OpenFeign 实现服务降级依赖于 hystrix ,因此我们需要先引入hystrix 依赖如下:
org.springframework.cloud spring-cloud-starter-netflix-hystrix 2.2.9.RELEASE org.springframework.cloud spring-cloud-netflix-ribbon
下面实现了一个降级类示例,该降级类是通过 @FiegnClient 注解的 fallback 属性来指定的:
@Slf4j @Component public class GoodsServiceDowngrade implements GoodsService { // 获取指定商品 @Override public GoodsInfo getGoods(Long id) { // 对应的远程调用方法执行失败后会调用该方法,根据业务情况自行设计实现细节,此处仅打印日志 log.info("fallback method in getGoods."); return null; } // 获取整个购物车内的商品 @Override public MapgetGoodsCart(Collection ids) { // 对应的远程调用方法执行失败后会调用该方法,根据业务情况自行设计实现细节,此处仅打印日志 log.info("fallback method in getGoodsCart."); return null; } }
接下来在远程调用接口内指定降级类:
@FeignClient(value = "coin-goods-svc", path = "/goods", fallback=GoodsServiceDowngrade.class) public interface GoodsService { // 获取指定商品 @GetMapping("/getGoods") GoodsInfo getGoods(@RequestParam("id") Long id); // 获取整个购物车内的商品 @GetMapping("/getBatch") MapgetGoodsCart(@RequestParam("ids") Collection ids); }
下面实现的降级类,是通过 @FiegnClient 注解的 fallbackFactory 属性来指定的,与上面不同的是,使用降级工厂处理降级可以获取到远程调用方法失败的原因。
@Slf4j @Component public class GoodsServiceDowngradeFactory implements FallbackFactory{ @Override public GoodsService create(Throwable cause) { return new GoodsService() { @Override public GoodsInfo getGoods(Long id) { // 根据业务情况自行设计实现细节,此处仅打印日志 log.info("fallback method in getGoods.", cause); return null; } @Override public Map getGoodsCart(Collection ids) { log.info("fallback method in getGoodsCart.", cause); return null; } }; } }
下面我们在@FeignClient 注解修饰的远程调用接口内使用我们实现的降级工厂:
@FeignClient(value = "coin-goods-svc", path = "/goods", fallbackFactory=GoodsServiceDowngradeFactory.class) public interface GoodsService { // 获取指定商品 @GetMapping("/getGoods") GoodsInfo getGoods(@RequestParam("id") Long id); // 获取整个购物车内的商品 @GetMapping("/getBatch") MapgetGoodsCart(@RequestParam("ids") Collection ids); }
注意我们在使用的时候,降级类和降级工厂同时只能选择其中一种。
最后一步,开启降级的配置开关feign.circuitbreaker.enabled: true,使代码生效。即在 application.yml 文件中,原来的配置中加上:
# 其它忽略 feign: client: config: # 全局超时配置 default: connectTimeout: 1000 readTimeout: 3000 # 针对coin-goods-svc服务的超时配置 coin-goods-svc: connectTimeout: 1000 readTimeout: 2000 circuitbreaker: enabled: true
上一篇:MySQL 覆盖索引