SpringCloudFeign
技术回顾
[Netflix Eureka]
[RestTemplate]
[Netflix Ribbon]
[Netflix Hystrix]
注意:Hystrix 可以是服务端实现,也可以是客户端实现,类似于 AOP 封装:正常逻辑、容错处理。
Feign 基本使用
申明式 Web 服务客户端
申明式:接口声明、Annotation 驱动
Web 服务:HTTP 的方式作为通讯协议
客户端:用于服务调用的存根
Feign:原生并不是 Spring Web MVC的实现,基于JAX-RS(Java REST 规范)实现。Spring Cloud 封装了Feign ,使其支持 Spring Web MVC。RestTemplate、HttpMessageConverter
RestTemplate以及 Spring Web MVC 可以显示地自定义HttpMessageConverter实现。
假设,有一个Java 接口 PersonService,Feign 可以将其声明它是以 HTTP 方式调用的。
Feign 使用
创建步骤
项目结构

各个子 module 的作用
- spring-cloud-feign-eureka: 注册中心,服务发现和注册
- spring-cloud-feign-demo:Feign 的基本实现,提供父控
- person-api:Feign 声明接口(契约):定义一种 Java 强类型接口
- person-client: Feign 客户端:调用Feign 申明接口
- person-service: Feign 服务端:不一定强制实现 Feign 申明接口
各个子 module 实现及配置
spring-cloud-feign-eureka
maven 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>FeignEureka服务启动类:启用 Eureka 服务
@SpringBootApplication @EnableEurekaServer public class SpringCloudFeignEurekaApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudFeignEurekaApplication.class, args); } }application 配置: Eureka 地址
# 服务名 spring.application.name=spring-cloud-eureka-server # Eureka 服务器端口 server.port=12345 # 取消服务器自我注册 eureka.client.register-with-eureka=false # 注册中心的服务器,没有必要再去检索服务 eureka.client.fetch-registry=false # actuator 配置 management.endpoint.health.show-details=always management.endpoints.web.exposure.include=*
spring-cloud-feign-demo
maven 依赖配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>person-api:
用户实体类
public class Person { private Long id; private String name; // get&set }Feign 声明式接口
// 服务提供方的应用名称 @FeignClient(value = "person-service") public interface PersonService { @PostMapping(value = "/person/save") boolean save(@RequestBody Person person); @GetMapping(value = "/person/find/all") Collection<Person> findAll(); }
person-client:
maven 依赖
<dependency> <groupId>com.gupao</groupId> <artifactId>person-api</artifactId> <version>${project.version}</version> </dependency>PersonClient 服务启动类
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(clients = PersonService.class) public class PersonClientApplication { public static void main(String[] args) { SpringApplication.run(PersonClientApplication.class, args); } }
PersonClient 服务消费: 对外提供服务
@RestController public class PersonClientController implements PersonService { private final PersonService personService; @Autowired public PersonClientController(PersonService personService) { this.personService = personService; } @Override public boolean save(@RequestBody Person person) { return personService.save(person); } @Override public Collection<Person> findAll() { return personService.findAll(); } }application 配置
spring.application.name=person-client server.port=8080 # Eureka Server 服务 URL,用于客户端注册 eureka.client.service-url.defaultZone=\ http://localhost:12345/eureka management.endpoint.health.show-details=always management.endpoints.web.exposure.include=*
person-service:
maven 依赖配置
<dependency> <groupId>com.gupao</groupId> <artifactId>person-api</artifactId> <version>${project.version}</version> </dependency>PerconService 服务启动类
@SpringBootApplication @EnableEurekaClient public class PersonServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(PersonServiceProviderApplication.class, args); } }PersonService 服务提供:提供 person-api 的实现
@RestController public class PersonServiceController { private final ConcurrentMap<Long, Person> persons = new ConcurrentHashMap<>(); @PostMapping(value = "/person/save") public boolean save(@RequestBody Person person){ return persons.putIfAbsent(person.getId(), person) == null; } @GetMapping(value = "/person/find/all") public Collection<Person> findAll(){ return persons.values(); } }application 配置
# 服务提供方的应用名称需要和 @FeignClient 声明对应 spring.application.name=person-service server.port=9090 # Eureka Server 服务 URL,用于客户端注册 eureka.client.service-url.defaultZone=\ http://localhost:12345/eureka management.endpoint.health.show-details=always management.endpoints.web.exposure.include=*
服务启动
依次启动 spring-cloud-feign-eureka、person-service、person-client
结果显示
可以在 eureka 上看到,person-service 和 person-client 注册成功

使用 postman 调用服务
这里只调用客户端,如果需调用服务端,把端口从 8080 改成 9090 即可
/person/save

/person/find/all

服务调用顺序
PostMan -> person-client -> person-service
person-api 定义了 @FeignClients(value="person-service") , person-service 实际是一个服务器提供方的应用名称。
person-client 和 person-service 两个应用注册到了Eureka Server
person-client 可以感知 person-service 应用存在的,并且 Spring Cloud 帮助解析 PersonService 中声明的应用名称:“person-service”,因此 person-client 在调用 ``PersonService` 服务时,实际就路由到 person-service 的 URL
整合 Netflix Ribbon
官方参考文档:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR3/multi/multi_spring-cloud-ribbon.html
使用ribbon 不使用 eureka
关闭 Eureka 注册
调整 person-client 关闭 Eureka
ribbon.eureka.enabled = false
定义服务 ribbon 的服务列表(服务名称:person-service)
person-service.ribbon.listOfServers = http://localhost:9090,http://localhost:9090,http://localhost:9090
完全取消 Eureka 注册
//@EnableEurekaClient //注释 @EnableEurekaClient
自定义 Ribbon 的规则
接口和 Netflix 内部实现
- IRule
- 随机规则:RandomRule
- 最可用规则:BestAvailableRule
- 轮训规则:RoundRobinRule
- 重试实现:RetryRule
- 客户端配置:ClientConfigEnabledRoundRobinRule
- 可用性过滤规则:AvailabilityFilteringRule
- RT权重规则:WeightedResponseTimeRule
- 规避区域规则:ZoneAvoidanceRule
实现步骤
项目结构变动

实现 Rule
public class FirstServerForeverRule extends AbstractLoadBalancerRule { @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object key) { ILoadBalancer loadBalancer = getLoadBalancer(); List<Server> allServers = loadBalancer.getAllServers(); return allServers.get(0); } }暴露自定义实现为 Spring Bean
@Configuration public class RuleConfiguration { @Bean public FirstServerForeverRule firstServerForeverRule() { return new FirstServerForeverRule(); } }激活这个配置
@SpringBootApplication //@EnableEurekaClient @EnableFeignClients(clients = PersonService.class) @RibbonClient(value = "person-service", configuration = RuleConfiguration.class) public class PersonClientApplication { public static void main(String[] args) { SpringApplication.run(PersonClientApplication.class, args); } }文档参考地址
https://www.springcloud.cc/spring-cloud-dalston.html#_customizing_the_ribbon_client

检验结果
通过调试可知:
ILoadBalancer loadBalancer = getLoadBalancer();
// 返回三个配置 Server,即:
// person-service.ribbon.listOfServers = \
// http://localhost:9090,http://localhost:9090,http://localhost:9090
List<Server> allServers = loadBalancer.getAllServers();
return allServers.get(0);
还原 Eureka注册的结果
注册三台服务提供方服务器:


整合 Netflix Hystrix
实现步骤
调整 Feign 接口
person-api
// 服务提供方的应用名称 @FeignClient(value = "person-service", fallback = PersonServiceFallback.class) public interface PersonService { @PostMapping(value = "/person/save") boolean save(@RequestBody Person person); @GetMapping(value = "/person/find/all") Collection<Person> findAll(); }添加 Fallback 实现
在 person-api 下添加 PersonServiceFallback 实现
public class PersonServiceFallback implements PersonService { @Override public boolean save(Person person) { return false; } @Override public Collection<Person> findAll() { return Collections.emptyList(); } }调整客户端(激活Hystrix)
person-client 配置
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(clients = PersonService.class) @EnableHystrix //@RibbonClient(value = "person-service", configuration = RuleConfiguration.class) public class PersonClientApplication { public static void main(String[] args) { SpringApplication.run(PersonClientApplication.class, args); } }
问答部分
能跟dubbo一样,消费端像调用本地接口方法一样调用服务端提供的服务么?还有就是远程调用方法参数对象不用实现序列化接口么?
答: FeignClient 类似 Dubbo,不过需要增加以下 @Annotation,和调用本地接口类似
Feign通过注释驱动弱化了调用Service细节,但是Feign的Api设定会暴露service地址,那还有实际使用价值么?
答:实际价值是存在的,Feign API 暴露 URI,比如:"/person/save"
整合ribbon不是一定要关闭注册中心吧?
答: Ribbon 对于 Eureka 是不强依赖,不过也不排除
生产环境上也都是feign的?
答:据我所知,不少的公司在用,需要 Spring Cloud 更多整合:
Feign 作为客户端
Ribbon 作为负载均衡
Eureka 作为注册中心
Zuul 作为网管
Security 作为安全 OAuth 2 认证
Ribbon直接配置在启动类上是作用所有的controller,那如果想作用在某个呢?
答:Ribbon 是控制全局的负载均衡,主要作用于客户端 Feign,Controller是调用 Feign 接口,可能让人感觉直接作用了 Controller。
其实eureka也有ribbon中简单的负载均衡吧
答:Eureka 也要 Ribbon 的实现,可以参考
com.netflix.ribbon:ribbon-eureka如果服务提供方,没有接口,我客户端一般咋处理?要根据服务信息,自建feign接口?
无法连接注册中心的老服务,如何调用cloud服务
答:可以通过域名的配置 Ribbon 服务白名单
eureka 有时监控不到宕机的服务 正确的启动方式是什么
答:这可以调整的心跳检测的频率