014 Spring cloud Feign 代理

        Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。我们只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了RibbonHystrix来提供均衡负载的HTTP客户端实现。

        对于Feign来讲,其实就是一个WEB接口而已,它内部自集成了Spring Ribbon Spring Hystrix 断路器功能,也就是说可以支持自动降级和负载均衡功能。可以说,在内部服务与服务之间的相互数据通信桥梁就是通过Feign来实现的。也就是说,我们可以像使用web service OR dubbo 一样对其进行声明式的配置;(这非常棒~)

        在我之前的工作中,就大量的使用了Feign代理,可以说使用spring cloud 就必须要学会如何深入使用Feign代理,当然它也非常的简单。

【Feign代理在实际工作中的使用,推荐采用类似Dubbo进行封装jar包的方式,而非直接嵌套的方式】

1. 首先需要引入相关依赖,然后再主入口启用注解:

另外在application.yml中也可以对feign代理做一些高性能输入输出配置:

注意:feign在第四个版本后需要手工的开启断路器功能才可以生效。

了解完Feign的基础配置之后,要开始代码实现了。首先需要编写一个interface,并且这个interface一定是已知的服务(也就是注册到了Eureka上的接口服务,我们在这里需要使用interface的方式进行声明)

@FeignClient注解就是Feign的注解声明了,里面name属性表示了当前代理的服务APP NAME; fallback属性当然就是调用服务失败的降级策略了,也就是当网络闪段、异常等调用代理服务失败时,会根据断路器的超时时间降级到指定的fallback所赋予的HelloServiceHystrix类中,可以进行降级处理。 

        Feign底层默认提供了重试机制,也就是底层使用Retryer类对调用服务进行重试调用操作,通过底层代码我们知道默认是每100ms去进行调用,调用次数是5次。既然Feign集成了Ribbon Hystrix ,那么必然会使用两个超时机制,一个是Ribbon的超时时间,一个是Hystrix的超时时间.这两个超时时间的含义截然不同,千万要注意配置。

 

注意:代理方式说明

代理实现有两种方式:

        一种是按照Controller层次,一个Controller对应一个Feign代理,这种方式适用于对外接口中方法多的情况(推荐细粒度);

        另一种是按照服务层次,可以针对provider-service提供一个整体的Feign代理(ProviderServiceFeignClient),这种方式适用于对外接口中方法少的情况;

代码实现:

eureka-server:略;

provider:

pom:

<dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- 标示这个工程是一个服务的提供者,需要引入此jar -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <!-- 动态刷新的一个模块jar -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      
  </dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Edgware.SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<build>
    <finalName>spring-cloud-04-eureka-provider</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.cc.springcloud.Application</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

application.properties:

spring.application.name=provider-service
##微服务方式尽量不要加server.context-path,只用名字加方法实现
#server.context-path=/provider
server.port=7001

##需要引入eureka注册中心的地址
##下面两条配置代表注册到注册中心后显示自己的IP地址,实际工作中尽量加上
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${server.port}

##租期到期时间间隔
#eureka.instance.lease-expiration-duration-in-seconds=30
##租期更新时间间隔
#eureka.instance.lease-renewal-interval-in-seconds=10

##开启健康检查,必须要引入spring-boot-starter-actuator动态刷新jar
eureka.client.healthcheck.enabled=true

eureka.client.service-url.defaultZone=http://eureka1:8001/eureka

IndexController:

package com.cc.springcloud.api;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
    
    @RequestMapping(value="/index",method= {RequestMethod.GET})
    /**
     * 使用异常模拟熔断,测试断路器
     * @return
     */
    public String hello() throws Exception{
        int a = 1/0;  //抛出异常
        return "Hello World!";
    }
    
    @RequestMapping(value="/hi",method= {RequestMethod.GET})
    public String hi() throws InterruptedException {
        /**
         * 模拟超时,测试断路器
         * 设置程序用时5秒钟
         * 熔断设置为ReadTimeout: 3000  3秒
         */
        Thread.sleep(5000);
        return "Hi Feign!";
    }
}
 

consumer:

pom:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 注册到eureka -->
    <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      
      <!-- 需要引入hystrix依赖,否则异常:java.lang.NoClassDefFoundError: com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect -->
    <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
      </dependency>
      
    <!-- feign内部集成了hystrix和ribbon,直接引入feign依赖就可以使用,但引入不全需要引入上面包 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Edgware.SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<build>
    <finalName>spring-cloud-04-feign-consumer</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.cc.springcloud.Application</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

application.yml:

spring:
  application:
    name: feign-consumer
##类似@EnableRetry注释,此处代表启动springcloud ribbon retry
  cloud:
    loadbalancer:
      retry:
        enabled: true

server:
  context-path: /
  port: 7002
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:8001/eureka
      
##feign配置,在springcloud第四个版本后要手动开启断路器功能,断路器才生效,失败才调用降级。   
feign:
  hystrix:
    enabled: true
  compression:       ##请求压缩配置    
    request:
      min-request-size: 2048
      mime-types:
      - text/html,application/xml,application.json
##    response:
##      enabled:true

##设置全局断路器的超时时间为10秒(yml可以使用此形式进行配置,注意最后的为:号而非=号)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 10000

##以下为非基本配置

##针对于某一个微服务进行负载均衡和重试策略配置
provider-service:  ##表示针对provider-service的微服务进行重试策略,如果不加微服务名称,只设置以下内容表示全局
  ConnectTimeout: 10000             ##连接超时时间10秒
  ReadTimeout: 3000                 ##读取超时时间3秒          
  ribbon: 
    OkToRetryOnAllOperations: true ##对所有的请求都进行重试
    MaxAutoRetriesNextServer: 1    ##切换实例的次数,如果请求异常,在两个provider间切换
    MaxAutoRetries: 2              ##对当前实例重试的次数
  

Application:

package com.cc.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableFeignClients      //启用代理服务(必须)
@EnableCircuitBreaker    //启用断路器

@EnableDiscoveryClient   //标示是一个具体的服务,需要向注册中心注册
@SpringBootApplication   //springboot 核心配置
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
 

IndexFeignClient:

package com.cc.springcloud.feign;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.cc.springcloud.feign.hystrix.IndexFeignFailback;

/**
 * 见名知意,此为代理,代理的是provider中的IndexController中的方法;
 * 如果成功调用provider-service远程节点的IndexController中的方法,
 * 如果失败会调用本地的IndexFeignFailback降级方法
 * FeignClient相当于把代理注入spring容器中,所有可以在aqi中直接@Autowired使用
 */

@FeignClient(name="provider-service",fallback=IndexFeignFailback.class)
public interface IndexFeignClient {
    @RequestMapping(value="/index",method= {RequestMethod.GET})
    public String hello() throws Exception;
    
    @RequestMapping(value="/hi",method= {RequestMethod.GET})
    public String hi() throws InterruptedException;
}
 

IndexFeignFailback:

package com.cc.springcloud.feign.hystrix;

import org.springframework.stereotype.Component;


import com.cc.springcloud.feign.IndexFeignClient;
/**
 * 需要作为实例(组件)注入到spring容器中;
 * 一般来讲Dao层的注入注释使用@Repository
 * 对于sevice层的注入注释使用@Service
 * 其他的都使用@Component,这是一些约定;
 */

@Component   
public class IndexFeignFailback implements IndexFeignClient{

    @Override
    public String hello() throws Exception{
        return "-----hello接口的降级方法!-----";
    }

    @Override
    public String hi() throws InterruptedException{
        return "-----hi接口的降级方法!-----";
    }

}
 

api/ConsumerController:

package com.cc.springcloud.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cc.springcloud.feign.IndexFeignClient;
/**
 * 类似于dubbo的调用形式;
 */
@RestController
public class ConsumerController {

    @Autowired
    private IndexFeignClient indexFeignClient;
    
    @RequestMapping(value="/feign-hello")
    public String hello() throws Exception {
        return indexFeignClient.hello();
    }
    
    @RequestMapping(value="/feign-hi")
    public String hi() throws InterruptedException {
        return indexFeignClient.hi();
    }
}
 

启动集群测试:

测试负载均衡:创建provider-2,复制provider内容,修改端口为7002(把consumer的端口给我7003)

package com.cc.springcloud.api;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
    
    @RequestMapping(value="/index",method= {RequestMethod.GET})
    /**
     * 使用异常模拟熔断,测试断路器
     * @return
     */
    public String hello() throws Exception{
        //int a = 1/0;  //抛出异常
        return "Hello World!";
    }
    
    @RequestMapping(value="/hi",method= {RequestMethod.GET})
    public String hi() throws InterruptedException {
        /**
         * 模拟超时,测试断路器
         * 设置程序用时5秒钟
         * 熔断设置为ReadTimeout: 3000  3秒
         */
        //Thread.sleep(5000);
        return "Hi Feign!";
    }
}
【provider-2的方法保持正常访问,来测试负载】

启动集群测试:

调用hello方法会出现一次成功,一次失败;

调用hi方法永远返回成功,因为配置的ribbon重试,只要 ConnectTimeout: 10000,ReadTimeout: 3000 不超过断路器时间就切换:

##针对于某一个微服务进行负载均衡和重试策略配置
provider-service:  ##表示针对provider-service的微服务进行重试策略,如果不加微服务名称,只设置以下内容表示全局
  ConnectTimeout: 10000             ##连接超时时间10秒
  ReadTimeout: 3000                 ##读取超时时间3秒          
  ribbon: 
    OkToRetryOnAllOperations: true ##对所有的请求都进行重试
    MaxAutoRetriesNextServer: 1    ##切换实例的次数,如果请求异常,在两个provider间切换
    MaxAutoRetries: 2              ##对当前实例重试的次数

 

经验小结:

        可以配置Hystrix的超时时间大于Ribbon的超时时间。并且如果想进行重试最好是Hystrix的超时时间设置为Ribbon的超时时间的倍数。这样可以进行重试策略,如果Hystrix的超时时间小于Ribbon的超时时间,则不会重试,直接被断路器组件对调用请求执行请求段熔机制,服务降级。

Feign配合RibbonHystrix的超时策略配置如下: 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值