上一章学习了Eureka client 是如何注册了 Eureka Server 中去的,在本文中将讲解 服务间如何进行调用,
一、OpenFeign(声明式服务调用组件,服务消费者)
作为Spring Cloud的子项目之一,Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式,为微服务架构下服务之间的调用提供了解决方案
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果
总结:
- Feign 采用的是基于接口的注解
- Feign 整合了Hystrix,具有熔断降级的能力
- Feign 整合了Ribbon,具有负载均衡的能力
二、OpenFeign注解介绍
OpenFeign提供了两个重要标注@FeignClient和@EnableFeignClients
- @EnableFeignClients:标注用于修饰Spring Boot应用的入口类,以通知Spring Boot启动应用时,扫描应用中声明的Feign客户端可访问的Web服务。
- @FeignClient:标注用于声明Feign客户端可访问的Web服务。
1、@EnableFeignClients标注的参数
- value, basePackages (默认{})
- basePackageClasses (默认{})
- defaultConfiguration (默认{})
- clients (默认{})
2、@FeignClient标注的参数
- name, value (默认""),两者等价
- qualifier (默认"")
- url (默认"")
- decode404 (默认false)
- configuration (默认FeignClientsConfiguration.class)
- fallback (默认void.class)
- fallbackFactory (默认void.class)
- path (默认"")
- primary (默认true)
3、 @FeignClient标注的configuration参数
@FeignClient标注的configuration参数,默认是通过FeignClientsConfiguration类定义的,可以配置Client,Contract,Encoder/Decoder等。
FeignClientsConfiguration类中的配置方法及默认值如下:
- feignContract: SpringMvcContract
- feignDecoder: ResponseEntityDecoder
- feignEncoder: SpringEncoder
- feignLogger: Slf4jLogger
- feignBuilder: Feign.Builder
- feignClient: LoadBalancerFeignClient(开启Ribbon时)或默认的HttpURLConnection
4、定制@FeignClient标注的configuration类
@FeignClient标注的默认配置类为FeignClientsConfiguration,我们可以定义自己的配置类如下:
@Configuration
public class MyConfiguration {
@Bean
public Contract feignContract(...) {...}
@Bean
public Encoder feignEncoder() {...}
@Bean
public Decoder feignDecoder() {...}
...
}
然后在使用@FeignClient标注时,给出参数如下:
@FeignClient(name = "myServiceName", configuration = MyConfiguration.class, ...)
public interface MyService {
@RequestMapping("/")
public String getName();
...
}
当然,定制@FeignClient标注的configuration类还可以有另一个方法,直接配置application.yaml文件即可,示例如下:
feign:
client:
config:
feignName: myServiceName
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
encoder: com.example.MyEncoder
decoder: com.example.MyDecoder
contract: com.example.MyContract
三、OpenFeign 调用其他服务介绍
想要使用Feign也比较简单,定义一个通过注解@FeignClient()指定需要调用的服务的接口,启动类加上@EnableFeignClients开启Feign功能即可
我们先看一下官方的文档,了解一下具体操作
1、添加依赖和声明启动类注解
- 我们需要添加spring-cloud-starter-openfeign依赖
- 我们需要为启动类添加@EnableFeignClients注解,让他变为一个Feign客户端
2、创建@FeignClient接口
@FeignClient 的value值指的是任意的客户端的名称,还有url 用来指定一个主机地址
value和name属性是等价的
官网给的这段介绍中有段非常重要的描述在下文红色区域进行描述,在这段描述中我们看到这样的一段描述,如果你使用的是 Eureka Client 呢么将自动从 Eureka Server 注册表中获取服务,呢么这段描述我们可以理解为 Feign 默认是集成了 Eureka
四、OpenFeign 调用服务案例
1、建立服务注册中心Eureka Server
服务注册中心简略记录,详细的可以看之前的博文
(1)新建一个Eureka Server工程,导入eureka-server依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
(2)新建启动类:
package com.imooc.homepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 1、pom文件中对应到spring-cloud-starter-netflix-eureka-server
* 2、只需要使用@EnableEurekaServer 注解就可以让应用变为Eureka服务端
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
(3)配置文件:
spring:
application:
#这个spring应用的名字(之后调用会用到)
name: homepage-eureka
server:
#服务注册中心端口号
port: 8000
eureka:
instance:
#服务注册中心实例的主机名
hostname: localhost
client:
# 表示是否从 eureka server 中获取注册信息(检索服务),默认是true
fetch-registry: false
# 表示是否将自己注册到 eureka server(向服务注册中心注册自己),默认是true
register-with-eureka: false
service-url:
#服务注册中心的配置内容,指定服务注册中心的位置,eureka 服务器的地址(注意:地址最后面的 /eureka/ 这个是固定值)
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
2、创建服务提供者客户端Eureka Client
(1)导入依赖:
<!--spring cloud的eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--
添加web是为了解决client启动后自动关闭的问题
由于microserver-user 服务是是web项目 所以还需要添加对应的web包
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
同时要导入Spring Boot插件,使jar打包能正常运行(避免执行时报错XXX.jar中没有主清单属性):
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(2)新建启动类:
package com.imooc.homepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 1、pom文件中对应到spring-cloud-starter-netflix-eureka-client
* 2、使用@EnableEurekaClient 注解就可以让应用变为Eureka客户端端
* 3、@SpringBootApplication是启动器
*/
@EnableEurekaClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class,args);
}
}
(3)创建真实的执行类,自己执行 或 为外部提供服务(消费者调用)
package com.imooc.homepage.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ServiceController {
// 这里是为了拿到配置文件中的port
@Value("${server.port}")
private String port;
// @GetMapping("/hello/{name}")
@RequestMapping(value = "/hello/{name}",method = RequestMethod.GET)
public String getHello(@PathVariable("name") String name){
return "hello "+name+",被调用的服务提供者(客户端):homepage-eurekaClient,被调用的服务端口 port:"+port;
}
public void setPort(String port) {
this.port = port;
}
}
(4)配置文件:
这里是将自己注册进服务注册中心
spring:
application:
name: homepage-eureka-client
server:
port: 8100
eureka:
client:
service-url:
#将自己注册进下面这个地址的服务注册中心
defaultZone: http://localhost:8000/eureka/
3、创建消费者客户端
(1)添加pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>imooc-homepage</artifactId>
<groupId>com.imooc.homepage</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>homepage-feign</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>homepage-feign</name>
<dependencies>
<!--spring cloud的eureka客户端依赖-->
<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>
<!--
添加web是为了解决client启动后自动关闭的问题
由于microserver-user 服务是是web项目 所以还需要添加对应的web包
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!--配置boot运行插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)创建启动类
package com.imooc.homepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 1、pom文件中对应到spring-cloud-starter-netflix-eureka-client
* 2、@SpringBootApplication:是启动器
* 3、@EnableEurekaClient:使用该注解就可以让应用变为Eureka客户端端
* 4、@EnableFeignClients:开启OpenFeign支持
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients//(basePackages = {"com.imooc.homepage.controllerFeign"})
public class OpenFeignApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignApplication.class,args);
}
}
(3)新建一个接口,用于调用服务:
注意 :该接口可以理解为是 被调用服务类(服务提供者) 方法的一个映射
- 需要在 @FeignClient 中指定要调用的服务信息(如:服务名、URL等等)
- 该接口的方法必须要和 被调用的服务(服务提供者) 调用方式保持一致 (也就是说服务提供者的方法是什么样的,这里就得是什么样,包括方法上的注解。需要保证接口请求方式是一致的;需要保证接口传参是一致的)
- 需要使用@RequestMapping这种方式(避免使用GetMapping/PostMapping)
- 额外提醒,只要是传参,请都加上@RequestParam("XXXX");如果传对象就加上@RequestBody; 这样你就不用踩Feign的传参的坑了,不管是使用Feign的服务还是提供接口的client服务,请都加上这些传参注解保持一致;
package com.imooc.homepage.controllerFeign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @FeignClient(name = "")指定这是feign客户端; name属性指明要调用的服务名
* 注意:这里一定要加 @Component 注解,因为 @FeignClient 并没有将类托管给spring的功能
* 如果不加 @Component 在使用 @Autowired 注入的时候会出现问题,找不到Bean对象
*/
@FeignClient(name = "homepage-eureka-client")
@Component
public interface IServiceFeign {
// 指定要调用的服务,要和调用的服务调用方式保持一致
// @GetMapping("/hello/{name}")
@RequestMapping(value = "/hello/{name}",method = RequestMethod.GET)
public String getHello(@PathVariable("name") String name);
}
(4)调用服务:
package com.imooc.homepage.controller;
import com.imooc.homepage.controllerFeign.IServiceFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
public class ServiceController {
/**
* 1、注入IServiceFeign(feign的客户端,来调用服务)
* 注意:这里我使用 @Autowired 时编译器会报红 Could not autowire. No beans of 'IServiceFeign' type found
* 原因是如果没有在 IServiceFeign 上声明 @Component 就不会将它托管给spring容器
*/
@Autowired
// @Resource
private IServiceFeign iServiceFeign;
// @GetMapping("/consumer/{name}")
@RequestMapping(value = "/consumer/{name}",method = RequestMethod.GET)
public String consumer(@PathVariable("name") String name) {
// 调用具体的服务
return iServiceFeign.getHello(name);
}
public void setiServiceFeign(IServiceFeign iServiceFeign) {
this.iServiceFeign = iServiceFeign;
}
}
遇到的问题:
使用@Autowired时会说找不到,但改为@Resource时就可以了
解决方案:
原因是我们没有在 IServiceFeign 接口上定义 @Component
因为 @FeignClient 并没有将类托管给 spring 的功能
如果不加 @Component 在使用 @Autowired 注入的时候会出现问题,找不到Bean对象
(5) 配置文件:
这里只是将自己注册进服务注册中心,与之前一样
spring:
application:
name: homepage-feign
server:
port: 8200
eureka:
client:
service-url:
#将自己注册进下面这个地址的服务注册中心
defaultZone: http://localhost:8000/eureka/
4、启动服务进行验证(启动服务注册中心、服务提供客户端、消费端)
下图可以看出,已经将 服务提供客户端、消费端 注册进了服务注册中心
(1)服务注册中心界面
(2)服务提供客户端:自己调用自己的执行类方法
(3)消费者客户端:调用服务提供者客户端的执行类方法
五、Feign 整合 Hystrix(熔断降级的能力)
我们之前说过 openFeign 整合了 Hystrix 熔断降级的能力
我们都知道微服务的目的是功能模块的解耦,将每一个模块划分为一个单独的服务,达到其中任何一个服务出了问题而不会影响其他服务的使用。
问:什么是熔断降级?
答:
微服务间调用的前提是必须保证被调用的服务是正常启动的状态,但是我们开发过程中一定会出现以下场景:
我们的某一个微服务因为出现了问题或者需要更新的情况下,我们会暂时停止这个服务。
那么问题来了,被调用的微服务停止了,我们的消费端访问就会报错了,我们该怎么办呢?
Hystrix 就是为我们解决这一问题的,在微服务中一旦被调用的微服务停止后,Hystrix 就会熔断这个访问请求,降级去自己里面调用预制好的方法。
官方文档
如果Hystrix在类路径上,默认情况下,Feign会用一个断路器来包装所有的方法。返回一个com.hystrix.hystrixcommand也可用。这让您可以使用响应式模式(对.to可视()或。观察()或异步使用(对.queue()的调用)。
下面我们在 homepage-feign 的基础上来做一些改动
1、添加 Hystrix 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、在 yml 配置文件中添加 Hystrix 配置开启
fegin: hystrix : enabled : true 这个配置项是指,开启熔断机制,也许在yml里显示没有相关依赖,但是不用慌,也许是因为版本问题,这个设置项加上就行,是起作用的)
默认是禁用Hystrix支持Feign,需在 yml 中加入以下配置启用Hystrix对Feign的支持
feign:
#默认是不支持的,所以这里要开启,设置为true
hystrix:
enabled: true
3、在启动类上添加 @EnableHystrix 注解,开启熔断支持
4、创建一个 Hystrix 熔断降级的类
这个类实现 @FeignClient 接口的方法,用作降级后的调用类
方法返回: return "sorry! 网络异常,服务暂时无法访问。 请求的name为:"+name;
这里可以根据使用场景,扩展做数据存储、降级调用等等。
5、 在 @FeingClient 接口注解中设定降级的类
通过注解中的 fallback 属性设定降级类
6、关闭被调用的服务进行验证
六、Feign 整合 Ribbon(负载均衡的能力)
openFeign默认支持了负载均衡的能力
问:什么是负载均衡?
答:在服务调用中,如果只有一个服务提供者,那么所有的消费者都会请求这一个服务。消费者少了还好说,如果消费者很多那么会加大服务提供者的压力,使效率低下或者服务崩溃。
我们现在建立多个相同服务提供者为之提供服务,中间由负载均衡器来根据情况为消费者提供最合适的服务对象,这样不但减小了单个服务器的压力,同时也加大了调用效率。
官方文档
根据官网的描述Feign 默认是使用 Ribbon的(不引入新的jar包也可以使用,feign默认的负载均衡策略是交替调用的),如果我们要对负载均衡的策略进行改变,可以直接将对应的jar包引入来实现负载均衡,并进行配置。
既然是负载均衡,那么也先得有东西均衡,所以我们需要再创建一个微服务,这个微服务与之前的服务提供者 homepage-eurekaClient 微服务一模一样,依赖、配置全都保持一样, 唯一的改变是 端口!
1、我是直接拷贝了 homepage-eurekaClient 然后只修改了配置文件里面的端口号
注意:服务名保持一样,因为负载均衡是通过获取注册中心的服务注册信息,根据服务名去匹配的
2、我们启动两个服务提供者和feign消费者,注册进服务注册中心
下图我们可以看到负载均衡的准备已经完成
3、 调用测试
我们重复刷新这个请求,可以看到是端口8100、8101交替调用的,这就是openFeign的负载均衡机制。