前言

本文是对上一篇博文的扩充,很多平时用不到的特性就开始简略一写,Spring Cloud各版本之间的差距很大的,用不到的可能下一个版本就被kill掉了。由于笔者写本文开始的时候误解了Feign的继承特性,导致实验没有成功,今天是周六加班过程中画了个图,参考了一些资料才得出正确的结果,本人是一边学习一边写的博客做验证,更新估计是快不了了……不多说了,继续Feign的学习。

五、 继承特性

我们发现每次定义Feign调用服务提供者的接口时都要再写一份和所调用的接口方法名参数列表均相同的接口,那么有没有一种可以不用写这些接口就能直接使用Feign去调用服务提供者的接口的方法呢?答案就是本节我给大家带来的Feign的继承特性,结构基本如下:

贴一下服务提供者项目的结构图

首先为了能方便区分之前的接口,我们这次在服务提供者项目中创建一个interfaces的包,在包中创建我们要用的接口,上代码

package com.cnblogs.hellxz.interfaces;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; /**
* <b>类名</b>: HelloService
* <p><b>描 述</b>: 用于定义Controller的接口,实现放在Controller中
* 这个类的主要作用是方便Feign中继承特性的测试
* </p>
*
* <p><b>创建日期</b> 2018/6/23 11:43 </p>
* @author HELLXZ 张
* @version 1.0
* @since jdk 1.8
*/
public interface HelloService { /**
* 声明一个接口,没有实现
*/
@GetMapping(value = "/refactor-service/{name}")
String hello(@PathVariable("name") String name);
}

上述代码需要注意的点:

  1. 我们会想到Feign的客户端会继承这个接口,Feign中是不能有@GetMapping等这样的组合注解的,那么在这个接口中我们是否也要写成@RequestMapping并指定请求方法呢? 经过本人实测,是不用的。这里完全按照SpringMVC这一套来就可以
  2. 这是个接口类,没有实现

上面的接口是没有实现的,所以我在controller包中为它提供一个实现类

package com.cnblogs.hellxz.controller;

import com.cnblogs.hellxz.interfaces.HelloService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; /**
* 实现了HelloService接口的Controller
*/
@RestController //必须添加这个注解,告诉SpringMVC这是一个Controller
public class HelloServiceImplController implements HelloService { private final Logger logger = LoggerFactory.getLogger(HelloServiceImplController.class);
/**
* 实现了HelloService中的hello方法,通过这个方法来返回结果
*/
@Override
public String hello(@PathVariable String name) {
logger.info("refactorHelloService的hello方法执行了,入参:name:{}", name);
return "hello,"+name;
}
}

上述代码中需要注意的是:

  1. @RestController注解必须加上
  2. 重写接口的方法的时候,@GetMapping等url之类的不用写
  3. 在方法的参数注解依旧要写,不然会接收不到参数,我为了排查这个问题打印了日志,如上个代码块中的@PathVariable就是获取参数的

现在我们将服务提供者mvn install安装到本地仓库中,这样我们就可以在其它项目中依赖到服务提供者

接下来,我们在FeignCustomer这个项目的pom的dependencies节点中添加服务提供者的依赖

        <!--添加服务提供者的依赖-->
<dependency>
<groupId>com.cnblogs.hellxz</groupId>
<artifactId>EurekaServiceProvider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

完成到这一步,我们还需要一个Feign客户端来调用服务提供者的服务,这里和我们之前用的方法不是很相同,代码如下:

package com.cnblogs.hellxz.client;

import com.cnblogs.hellxz.interfaces.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient; /**
* 继承服务提供者的HelloService的接口,从而拥有这个接口的所有方法
* 那么在这个Feign中就只需要使用HelloService定义的接口方法
*/
@FeignClient("eureka-service")
public interface RefactorHelloServiceFeign extends HelloService {
}

注意:

  1. 这里我们无需重写与实现HelloService的接口
  2. 用extends关键字,而不是implements,用implements会要求必须实现

接下来我们在HelloController中注入上边的Feign,并加入调用Controller,省略之前的代码

	//省略类头
@Autowired
private RefactorHelloServiceFeign refactorHelloServiceFeign; //省略其它Controller方法 /**
* 测试Feign的继承特性
*/
@GetMapping("/refactor/{name}")
@ResponseBody
public String refactorHelloService(@PathVariable String name){
return refactorHelloServiceFeign.hello(name);
}

使用Postman测试一下这个Controller

2. 优缺点

优点:在定义Feign中无需重写要调用的方法,减少工作量,代码也更灵活

缺点:代码耦合性更高,需要更严格的规范,不然接口变动易发生牵一发而动全身,从而增加维护难度

六、Ribbon配置

Spring Cloud Feign的负载均衡是通过Spring Cloud Ribbon实现的,当我们为Feign接口使用@FeignClient(value="eureka-service")的时候,不仅为我们创建了Feign客户端,还为我们创建了一个名为“eureka-service”的Ribbon,客户端。

了解了以上这点,我们可以通过在配置文件中加入指定参数从而实现自定义Feign中的Ribbon的配置

以下Ribbon的配置就不在代码中体现了

1.全局配置

通过ribbon.<key>=<value>的方式来修改全局默认配置

举例(yml):

ribbon:
# 同一实例最大重试次数,不包括首次调用
MaxAutoRetries: 1
# 重试其他实例的最大重试次数,不包括首次所选的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都进行重试
OkToRetryOnAllOperations: false

2. 指定服务配置

通过<client>.ribbon.<key>=<value>的方式,修改指定服务的配置

举例(properties)

eureka-service.ribbon.ConnectTimeout=500
eureka-service.ribbon.ReadTimeout=2000
eureka-service.ribbon.OkToRetryOnAllOperations=true
eureka-service.ribbon.MaxAutoRetriesNextServer=2
eureka-service.ribbon.MaxAutoRetries=1

其中使用的eureka-service是要被配置的服务名

七、重试机制

Spring Cloud Feign默认实现了重试机制(内部同样是Ribbon进行的重试),当一次请求服务提供者,而服务提供者在Feign的延迟时间内没有返回结果,那么Feign会默认发起第二次请求,如果是因为网络问题,使第一次请求失败,第二次请求可能就会成功,通过这一点来实现服务的高可用。如果服务提供者有多个实例,还可以将第二次请求的机会交给其他实例。

我们可以通过上一部分的配置Ribbon,来改变重试的不同操作,比如eureka-service.ribbon.MaxAutoRetriesNextServer=2这个配置说明最多只能在两个实例间重试,

eureka-service.ribbon.MaxAutoRetries=1这个配置说明最多自动重试一次

我们可以设计实验:

  1. 将服务提供者开放一个提供线程sleep的延迟方法,让它的响应时间远超过ReadTimout的值,在这个方法中写日志,看看会有几次请求进入
  2. 第二个实验是将上述实验的服务提供者启动两个实例,查看输出

实验1:

服务提供者的GetRequestController中添加方法:

    /**
* 测试Feign延迟重试的代码
* 这里我们为这个方法加上超过Feign默认2000ms以上的延迟,我们只需要通过查看日志输出即可
*/
@GetMapping("/retry")
public String feignRetry(){
logger.info("feignRetry方法调用成功");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
}

在FeignCustomer的EurekaServiceFeign中加入调用上边服务提供者的方法

    /**
* 测试重连机制
*/
@RequestMapping(value = "/retry", method = RequestMethod.GET)
String feignRetry();

HelloController中添加调用Feign上边方法的Controller

    /**
* 测试重连机制
*/
@GetMapping("/retry")
@ResponseBody
public String retry(){
return eurekaServiceFeign.feignRetry();
}

application.yml中添加

ribbon:
# 同一实例最大重试次数,不包括首次调用
MaxAutoRetries: 1
# 重试其他实例的最大重试次数,不包括首次所选的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都进行重试
OkToRetryOnAllOperations: false

测试1:

可见重试的确是有的,但是本实验中明明设置了最多只重试一次,也就是说应该打印两次,在idea中提示无法解析这些配置,可能是我在FeignCustomer项目中用的Spring Cloud的依赖太新了,这个问题先放在这里,因为这个配置没有生效,所以第二个实验也就做不下去了……有知道的同学麻烦告知一二 [双手合十]

八、Hystrix配置

Spring Cloud Feign自然也是Hystrix的实现,也就拥有了断路的功能,这里我们看下如何设置其中的Hystrix的属性

1. 全局配置

通过前缀hystrix.command.default加上要配置的属性,比如

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

在配置Hystrix属性之前,我们要确保feign.hystrix.enabled的参数值不为false,如果为false,则关闭断路器(Hystrix)的支持

关闭熔断器:hystrix.command.default.execution.timeout.enabled=false

关闭Hystrix功能:feign.hystrix.enabled=false

2. 禁用Hystrix

除了全局配置中的关闭方法,如果我们不想全局关闭Hystrix支持,只是针对某个客户端关闭Hystrix功能 ,需要为Feign提供一个配置类,如下

package com.cnblogs.hellxz.config;

import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope; /**
* 用于禁用Feign中Hystrix的配置
*/
public class DisableHystrixConfiguration { @Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}

在Feign类的@FeignClient注解中,使用configuration参数指向刚刚创建的配置类

@FeignClient(value = "eureka-service",configuration = DisableHystrixConfiguration.class) //其中的value的值为要调用服务的名称, configuration中配置的是禁用Feign中的Hystrix功能的类
public interface EurekaServiceFeign {

3. 指定命令配置

Hystrix命令的配置在实际应用时也会根据实际业务情况制定出不同的配置,配置方法也跟传统的Hystrix命令的参数配置相似,采用hystrix.command.<commandKey>作为前缀,默认情况下<commandKey>会使用Feign客户端的方法名作为标识,所以,我们针对EurekaServiceFeign中的helloFeign方法的配置为:

hystrix.command.helloFeign.execution.isolation.thread.timeoutInMilliseconds=5000

在使用指定命令配置的时候,需要注意,方法名很有可能重复,这时候相同的方法名的Hystrix配置会共用,当然也可以使用重写Feign.Builder的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFeign.Builder实现。

4. 服务降级配置

在学习Feign中服务降级的配置之前,我们回顾一下Hystrix中服务降级是使用的@HystrixCommand注解加上fallback参数指向降级方法。其实Feign也是这样的,实现也比较简单。

只需要写一个实现了这个Feign的类,打上@Component注解,然后在每个方法中加入自己想让它失败后的处理逻辑。

注意:要注掉刚才配置的关闭Hystrix的方法,在github的代码中我已经注释掉了

用代码看起来更明显

新建EurekaServiceFeignFallback实现EurekaServiceFeign

package com.cnblogs.hellxz.client;

import com.cnblogs.hellxz.entity.User;
import org.springframework.stereotype.Component; import java.util.List; /**
* 服务降级回调类
*/
@Component //必须加这个注解,让这个类做为Bean交给Spring管理,否则会报找不到降级类
public class EurekaServiceFeignFallback implements EurekaServiceFeign{ /**
* 这里只用这个方法举例了,返回一个“失败"
*/
@Override
public String helloFeign() {
return "失败";
} @Override
public String greetFeign(String dd) {
return null;
} @Override
public List<User> getUsersByIds(List<Long> ids) {
return null;
} @Override
public String getParamByHeaders(String name) {
return null;
} @Override
public User getUserByRequestBody(User user) {
return null;
} @Override
public String feignRetry() {
return null;
}
}

EurekaServiceFeign@FeignClient注解后边加入fallback参数指向刚刚创建的降级类

@FeignClient(value = "eureka-service",configuration = DisableHystrixConfiguration.class, fallback = EurekaServiceFeignFallback.class)
public interface EurekaServiceFeign {

注意:configuration这个参数用不到的话可以不加,这里是之前用来配置禁用Hystrix功能的

现在这就已经配置好了,如果在网络环境不好的情况就会完成服务的降级,因为Feign会自动重试,我的配置并没有生效,所以无从验证。但是我在工作中经常使用它,这样配置是可以实现服务的降级的。

九、其它配置

1.请求压缩(Feign request/response compression)

Spring Cloud Feign支持对请求与响应进行GZIP压缩来减少通信过程中带宽的消耗

只需加入两行配置即可开启请求压缩

feign.compression.request.enabled=true
feign.compression.response.enabled=true

还有一些更细致的配置

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

2. Feign日志(Feign logging)

开启Feign的日志功能后,每创建一个Feign客户端同时也会创建一个日志记录器(logger),它的名字默认就是Feign接口的全量命名,并且这个日志logger只能响应DEBUG级的日志

开启记录

application.yml中填加以logging.level为前缀,后边加上Feign接口的路径全量命名

logging.level.com.cnblogs.hellxz.client.EurekaServiceFeign: DEBUG

添加上述配置还是算完,官网文档中说日志输出级别默认是NONE的,我们还需配置下

The Logger.Level object that you may configure per client, tells Feign how much to log. Choices are:

  • NONE, No logging (DEFAULT).
  • BASIC, Log only the request method and URL and the response status code and execution time.
  • HEADERS, Log the basic information along with request and response headers.
  • FULL, Log the headers, body, and metadata for both requests and responses.

简单翻译下:

  • NONE:不记录任何信息
  • BASIC:仅记录请求方法和URL,以及状态码和执行时间
  • HEADERS:在BASIC基础上同时记录请求和响应的头信息
  • FULL:记录所有请求与响应的明细

我们可以改变日志输出等级来完成日志输出,官网给出的例子是FULL

@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

除了使用配置类,还可以在主类中加入

@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}

结束

Spring Cloud Feign这一部分就写到这里了,Feign便于使用,功能强大,相比Hystrix实现更灵巧,但是这里多说一点,就是如果想对某个接口更细致并且性能要求高的情况下,用Hystrix更适用一些。

本来以为Feign这部分没有这么多东西,一边看书,一边看官方文档,这一下子写了这么久……

下一部分我们来看看API网关服务:Spring Cloud Zuul

本文引用:

  1. 《Spring Cloud 微服务实战》翟永超
  2. 官方文档

本文为学习记录文章,如需转载请注明出处

Spring Cloud(十二)声名式服务调用:Feign 的使用(下)的更多相关文章

  1. Spring Cloud(二):服务注册与发现 Eureka【Finchley 版】

    Spring Cloud(二):服务注册与发现 Eureka[Finchley 版]  发表于 2018-04-15 |  更新于 2018-05-07 |  上一篇主要介绍了相关理论,这一篇开始我们 ...

  2. Spring Cloud(十一)声名式服务调用:Feign的使用 (上)

    一.写在前边 最近开发任务比较忙,下班也开始锻炼了,这个系列的文章就放了很久,看github我提交的Feign的入门程序已经好久了,今天正好得空,这就更上一贴,准备分几部分写 注意:之前几个项目中,笔 ...

  3. Spring Cloud(十二):Spring Cloud Zuul 限流详解(附源码)(转)

    前面已经介绍了很多zuul的功能,本篇继续介绍它的另一大功能.在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选 ...

  4. 一起来学Spring Cloud | 第四章:服务消费者 ( Feign )

    上一章节,讲解了SpringCloud如何通过RestTemplate+Ribbon去负载均衡消费服务,本章主要讲述如何通过Feign去消费服务. 一.Feign 简介: Feign是一个便利的res ...

  5. Spring Cloud(三):服务提供与调用 Eureka【Finchley 版】

    Spring Cloud(三):服务提供与调用 Eureka[Finchley 版]  发表于 2018-04-15 |  更新于 2018-05-07 |  上一篇文章我们介绍了 Eureka 服务 ...

  6. Spring Cloud(一):服务治理技术概览【Finchley 版】

    Spring Cloud(一):服务治理技术概览[Finchley 版]  发表于 2018-04-14 |  更新于 2018-05-07 |  Spring Cloud Netflix 是 Spr ...

  7. Spring Cloud(四):服务容错保护 Hystrix【Finchley 版】

    Spring Cloud(四):服务容错保护 Hystrix[Finchley 版]  发表于 2018-04-15 |  更新于 2018-05-07 |  分布式系统中经常会出现某个基础服务不可用 ...

  8. Spring Cloud(十一):服务网关 Zuul(过滤器)【Finchley 版】

    Spring Cloud(十一):服务网关 Zuul(过滤器)[Finchley 版]  发表于 2018-04-23 |  更新于 2018-05-07 |  在上篇文章中我们了解了 Spring ...

  9. 一起来学Spring Cloud | 第六章:服务网关 ( Zuul)

    本章节,我们讲解springcloud重要组件:微服务网关Zuul.如果有同学从第一章看到本章的,会发现我们已经讲解了大部分微服务常用的基本组件. 已经讲解过的: 一起来学Spring Cloud | ...

随机推荐

  1. leetcode刷题笔记172 阶乘后的零

    题目描述: 给定一个整数 n,返回 n! 结果尾数中零的数量. 示例1: 输入: 输出: 解释: ! = , 尾数中没有零. 示例2: 输入: 输出: 解释: ! = , 尾数中有 个零. 说明: 你 ...

  2. 分布式理论:深入浅出Paxos算法

    前言 Paxos算法是用来解决分布式系统中,如何就某个值达成一致的算法.它晦涩难懂的程度完全可以跟它的重要程度相匹敌.目前关于paxos算法的介绍已经非常多,但大多数是和稀泥式的人云亦云,却很少有人能 ...

  3. 机器学习初入门01-numpy的基础用法

    一.numpy基础结构 1. numpy.genformtxt('路径名', delimiter = '分割符', dytype = 读取方式如str ):读取一个文件,返回一个numpy.ndarr ...

  4. [转载]JAVA内存分析——栈、堆、方法区 程序执行变化过程

    面向对象的内存分析 参考:http://www.sxt.cn/Java_jQuery_in_action/object-oriented.html :尚学堂JAVA300集-064内存分析详解_栈_堆 ...

  5. maven util 类 添加 service

    直接关键代码: public class DictionaryUtil { // 以下的处理,是为了在工具类中自动注入service // 前提是在applicationContext.xml中,将该 ...

  6. PAT甲题题解-1004. Counting Leaves (30)-统计每层叶子节点个数+dfs

    统计每层的叶子节点个数建树,然后dfs即可 #include <iostream> #include <cstdio> #include <algorithm> # ...

  7. Sprint第二个计划

    一.现状 现在是冲刺的第二个阶段了,我们的进度还是一样,没有太大的进展.所以这一个阶段应该加紧进度,好好学习别的组,弥补我们组的不足.一开始我们是打算用原来的初级APP,然后再补充一些新的内容.可是现 ...

  8. 微信小程序input组件抖动及textarea组件光标错位解决方案

    问题一: 使用微信小程序input组件时,在移动端唤起focus或blur事件时,因光标占位导致内容出现叠影及抖动现象. 解决方案: 用<textarea>组件代替了<input/& ...

  9. win7下php7.1运行getenv('REMOTE_ADDR')fastcgi停止运行

    // 本地环境phpStudy, PHP7.1.13nts+nginx,切换Apache也不行//ps:php版本<=7.0正常echo "<pre>";// $ ...

  10. hdu6444 Neko's Loop

    要想A一道题,你需要正版ide,正版草稿纸,正版键盘,正版双手,但最重要的是正版大脑.(改编自wxf在微积分群的吐槽) 分析一下题意,选的所有数的下标模gcd(n,k)都是同余的,而且是在所有同余的下 ...