跳至主要內容

SpringCloudFeign

soulballad微服务SpringCloud NetfilxSpringCloud约 1942 字大约 6 分钟

技术回顾

[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。RestTemplateHttpMessageConverter

RestTemplate 以及 Spring Web MVC 可以显示地自定义 HttpMessageConverter 实现。

假设,有一个Java 接口 PersonService,Feign 可以将其声明它是以 HTTP 方式调用的。

Feign 使用

创建步骤

  1. 项目结构

    1570279342392

  2. 各个子 module 的作用

    • spring-cloud-feign-eureka: 注册中心,服务发现和注册
    • spring-cloud-feign-demo:Feign 的基本实现,提供父控
      • person-api:Feign 声明接口(契约):定义一种 Java 强类型接口
      • person-client: Feign 客户端:调用Feign 申明接口
      • person-service: Feign 服务端:不一定强制实现 Feign 申明接口
  3. 各个子 module 实现及配置

    1. 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=*
        
    2. 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>
      
      1. 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();
          }
          
      2. 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=*
    
  1. 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=*
      
  2. 服务启动

    依次启动 spring-cloud-feign-eureka、person-service、person-client

  3. 结果显示

    可以在 eureka 上看到,person-service 和 person-client 注册成功

    1570280274319

  4. 使用 postman 调用服务

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

    • /person/save

      1570280419059

    • /person/find/all

      1570280436096

服务调用顺序

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

实现步骤

  1. 项目结构变动

    1570336847725

  2. 实现 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);
        }
    }
    
  3. 暴露自定义实现为 Spring Bean

    @Configuration
    public class RuleConfiguration {
        @Bean
        public FirstServerForeverRule firstServerForeverRule() {
            return new FirstServerForeverRule();
        }
    }
    
  4. 激活这个配置

    @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);
        }
    }
    
  5. 文档参考地址

    https://www.springcloud.cc/spring-cloud-dalston.html#_customizing_the_ribbon_client

    1570336930394

检验结果

通过调试可知:

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注册的结果

注册三台服务提供方服务器:

1570337049801

1570337066587

整合 Netflix Hystrix

实现步骤

  1. 调整 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();
    }
    
  2. 添加 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();
        }
    }
    
  3. 调整客户端(激活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);
        }
    }
    

问答部分

  1. 能跟dubbo一样,消费端像调用本地接口方法一样调用服务端提供的服务么?还有就是远程调用方法参数对象不用实现序列化接口么?

    答: FeignClient 类似 Dubbo,不过需要增加以下 @Annotation,和调用本地接口类似

  2. Feign通过注释驱动弱化了调用Service细节,但是Feign的Api设定会暴露service地址,那还有实际使用价值么?

    答:实际价值是存在的,Feign API 暴露 URI,比如:"/person/save"

  3. 整合ribbon不是一定要关闭注册中心吧?

    答: Ribbon 对于 Eureka 是不强依赖,不过也不排除

  4. 生产环境上也都是feign的?

    答:据我所知,不少的公司在用,需要 Spring Cloud 更多整合:

    Feign 作为客户端

    Ribbon 作为负载均衡

    Eureka 作为注册中心

    Zuul 作为网管

    Security 作为安全 OAuth 2 认证

  5. Ribbon直接配置在启动类上是作用所有的controller,那如果想作用在某个呢?

    答:Ribbon 是控制全局的负载均衡,主要作用于客户端 Feign,Controller是调用 Feign 接口,可能让人感觉直接作用了 Controller。

  6. 其实eureka也有ribbon中简单的负载均衡吧

    答:Eureka 也要 Ribbon 的实现,可以参考com.netflix.ribbon:ribbon-eureka

  7. 如果服务提供方,没有接口,我客户端一般咋处理?要根据服务信息,自建feign接口?

    无法连接注册中心的老服务,如何调用cloud服务

    答:可以通过域名的配置 Ribbon 服务白名单

  8. eureka 有时监控不到宕机的服务 正确的启动方式是什么

    答:这可以调整的心跳检测的频率

上次编辑于:
贡献者: soulballad