Spring Cloud(三) Spring Cloud OpenFeign (服务调用组件,熔断降级,负载均衡)

 上一章学习了Eureka client 是如何注册了 Eureka Server 中去的,在本文中将讲解 服务间如何进行调用,

一、OpenFeign声明式服务调用组件,服务消费者

作为Spring Cloud的子项目之一,Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式,为微服务架构下服务之间的调用提供了解决方案

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果

总结:

  1. Feign 采用的是基于接口的注解
  2. Feign 整合了Hystrix,具有熔断降级的能力
  3. 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)新建一个接口,用于调用服务:

注意 :该接口可以理解为是 被调用服务类(服务提供者) 方法的一个映射

  1. 需要在 @FeignClient 中指定要调用的服务信息(如:服务名、URL等等)
  2. 该接口的方法必须要和 被调用的服务(服务提供者) 调用方式保持一致 (也就是说服务提供者的方法是什么样的,这里就得是什么样,包括方法上的注解。需要保证接口请求方式是一致的;需要保证接口传参是一致的)
  3. 需要使用@RequestMapping这种方式(避免使用GetMapping/PostMapping)
  4. 额外提醒,只要是传参,请都加上@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的负载均衡机制。

发布了69 篇原创文章 · 获赞 43 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/fox_bert/article/details/98987776