• 微服务之路(九)spring cloud feign


    前言

    Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。

    在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。

    Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。

    Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。

    Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。

    Spring Cloud Feign具备可插拔的注解支持,支持Feign注解、JAX-RS注解和Spring MVC的注解。

    主要议题

    • Feign基本使用
    • Ribbon整合
    • Hystrix整合
    • 问题总结

    主体内容

    一、Feign基本使用、

    声明式Web服务客户的-Feign

    声明式:接口声明、Annonation驱动

    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接口来实现接口的。

    以前我们的简单架构如下:

    那么下面我们将写一个简单的示例,服务配置如下。

    需要的服务组件(SOA)

    • 注册中心(Eureka Server):服务发现和注册。
      • a.应用名称:spring-cloud-eureka-server
      • b.服务端口:12345
    • Feign客户端(服务消费)端:调用Feign声明接口。
      • a.应用名称:person-client
    • Feign服务(服务提供)端:不一定强制实现Feign声明接口。
      • a.应用名称:person-service
    • Feign声明接口(契约):定义一种Java强类型接口。
      • a.person-api

    我们一步一步来,摒弃之前的eureka server项目,全部重新创建一下。server单独一个工程,Feign客户端+Feign服务+Feign声明接口放另一个工程。

    (1)首先,还是去start.spring.io构建Eureka server项目,如下:

    (2)然后创建feign工程。

    (3)分别导入两个项目,其中先配置eureka server的application.properties.

    spring.application.name=spring-cloud-eureka-server
    server.port=12345
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    

    增加springboot安全依赖

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    关闭安全。创建config包,包下类SecurityConfig.java。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/**");
            }
    }
    

    启动类加上@EnableEurekaServer注解

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    public class SpringcloudEurekaServerApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(SpringcloudEurekaServerApplication.class, args);
       }
    }
    

    启动好eureka server,先丢到一旁。

    (4)下面导入feign项目,更改jar为pom,删掉原本的src。创建子模块person-api,创建包com.gupao.feign.api.domain,下面创建Peson类,这里采用lombok的@Data注解简化代码。

    import lombok.Data;
    
    /**
     * @ClassName
     * @Describe 人的模型
     * @Author 66477
     * @Date 2020/6/822:14
     * @Version 1.0
     */
    @Data
    public class Person {
        private Long id;
        private String name;
    
    }
    

    (5)创建service包,包下创建interface接口PersonService。

    import com.gupao.feign.api.domain.Person;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import java.util.Collection;
    
    /**
     * @ClassName
     * @Describe {@link Person}服务
     * @Author 66477
     * @Date 2020/6/822:25
     * @Version 1.0
     */
    @FeignClient(value="person-service")//服务提供方应用的名称
    public interface PersonService {
        /**
         * 保存
         * @param person {@link Person}
         * @return 如果成功,<code>true</code>
         */
        @PostMapping(value = "/person/save")
        boolean save(@RequestBody Person person);
    
        /**
         * 查找所有的服务
         * @return
         */
        @GetMapping(value="/person/findAll")
        Collection<Person> findAll();
    }
    

    (6)然后创建feign客户端,模块名称:person-client。随后在该模块中创建com.gupao.feign.client.controller包,与包同级创建启动类PersonClientApplication.java。内容如下:

    import com.gupao.feign.api.service.PersonService;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * @ClassName
     * @Describe TODO
     * @Author 66477
     * @Date 2020/6/822:40
     * @Version 1.0
     */
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients(clients= PersonService.class)
    public class PersonClientApplication {
        public static void main(String[] args) {
            SpringApplication.run(PersonClientApplication.class,args);
        }
    }
    

    然后在controller下创建PersonClientController类,内容如下:

    import com.gupao.feign.api.domain.Person;
    import com.gupao.feign.api.service.PersonService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.util.Collection;
    
    /**
     * @ClassName
     * @Describe {@link PersonClientController}实现{@link PersonService}
     * @Author 66477
     * @Date 2020/6/822:42
     * @Version 1.0
     */
    @RestController
    public class PersonClientController implements PersonService {
    
        private final PersonService personService;
        @Autowired
        public PersonClientController(PersonService personService) {
            this.personService = personService;
        }
    
        /**
         * 保存
         * @param person {@link Person}
         * @return 如果成功,<code>true</code>
         */
        public boolean save(@RequestBody Person person){
            return personService.save(person);
        }
    
        @Override
        public Collection<Person> findAll() {
            return personService.findAll();
        }
    }
    

    再定义application.properties文件。

    spring.application.name=person-client
    server.port=8080
    eureka.client.service-url.defaultZone=http://localhost:12345/eureka
    

    pom文件记得依赖person-api

    <?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>springcloud-feign</artifactId>
            <groupId>com.example</groupId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>person-client</artifactId>
        <dependencies>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>person-api</artifactId>
                <version>${project.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    

    打开spring boot security。同样的,创建config包,然后创建SecurityConfig类。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/**");
            }
    }
    

    (7)接下来,feign服务端,创建包com.gupao.feign.person.service.provider。包下创建PersonServiceProviderAppplication启动类。

    import com.gupao.feign.api.service.PersonService;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    /**
     * @ClassName
     * @Describe {@link PersonService}提供者应用
     * @Author 66477
     * @Date 2020/6/823:00
     * @Version 1.0
     */
    @SpringBootApplication
    @EnableEurekaClient
    public class PersonServiceProviderAppplication {
        public static void main(String[] args) {
            SpringApplication.run(PersonServiceProviderAppplication.class,args);
        }
    }
    

    创建包controller,创建服务端控制器PersonServiceProviderController类。

    import com.gupao.feign.api.domain.Person;
    import com.gupao.feign.api.service.PersonService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.Collection;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @ClassName
     * @Describe {@link PersonService} 提供者控制器(可以实现{@link PersonService}接口 )
     * @Author 66477
     * @Date 2020/6/823:01
     * @Version 1.0
     */
    @RestController
    public class PersonServiceProviderController {
    
        private Map<Long,Person> persons = new ConcurrentHashMap<>();
    
        /**
         * 保存
         * @param person {@link Person}
         * @return 如果成功,<code>true</code>
         */
        @PostMapping(value = "/person/save")
       public boolean save(@RequestBody Person person){
            return persons.put(person.getId(),person) == null;
        }
    
        /**
         * 查找所有的服务
         * @return
         */
        @GetMapping(value="/person/findAll")
        public  Collection<Person> findAll(){
            return persons.values();
        }
    }
    

    引入security依赖

      <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
    

    关闭springboot security。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/**");
            }
    }
    

    配置application.properties.

    #这个名称需要和@FeignClient(value="person-service")对应
    spring.application.name=person-service
    server.port=9090
    eureka.client.service-url.defaultZone=http://localhost:12345/eureka
    

    (8)最终启动feign服务端,feign客户端。结果:

    (9)我们开始用Postman测试peson-client->person-service。

    person-api定义了@FeignClients(value="person-service"),person-service实际上是一个服务器提供方的应用名称

    person-client和person-service两个应用注册到了Eureka Server。

    person-client可以感知person-serivce应用存在的,并且Spring Cloud帮助解析PersonService中声明的应用名称:"person-service",因此person-client在调用person-service服务时,实际就路由到person-service的URL。

    浏览器访问http://localhost:8080/person/findAll

    二、Ribbon整合

    官方文档https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.0.RELEASE/reference/html/#spring-cloud-ribbon

    1.关闭Eureka注册

    (1)调整person-client,关闭Eureka(这个貌似没用)

    #整合ribbon-关闭Eureka
    ribbon.eureka.enabled=false
    

    我直接采用的下面这个方式直接关闭eureka注册

    #整合ribbon-去除eureka注册
    eureka.client.register-with-eureka=false
    # 不获取注册列表信息, 是否从eureka服务器获取注册信息 , false = 不获取,true = 获取
    eureka.client.fetch-registry: false
    

    (2)定义person-client服务ribbon的服务列表(服务名称:person-service)

    #整合ribbon-配置“person-service"的负载均衡的服务器列表(写两遍是为了证明它是可以多配置的)
    person-service.ribbon.listOfServers:http://localhost:9090《http://localhost:9090
    

    注意:要想完全取消Eureka注册,只需要将person-clien启动类上的@EnableEurekaClient注释掉。(这种视频中讲到,但是我本地没有实现,不是道是不是这个版本是否有升级变动)

    2.实现ribbon规则

    • 核心规则接口

      • IRule
        • 随机规则:RandomRule
        • 最可用规则:BestAvailableRule
        • 轮询规则:RoundRobinRule
        • 重试实现:RetryRule
        • 客户端配置:ClientConfigEnabledRoundRobinRule
        • 可用性过滤规则:AvailabilityFilterRule
        • RT权重规则:WeightedResponseTimeRule
        • 规避区域规则:ZoneAvoidanceRule
        • ...

    (1)比如,我们举例第一个自定义策略。首先,我们进入RandomRule源码,发现它继承的是 AbstractLoadBalancerRule。那我们也在person-client下创建一个ribbon包,包里创建一个类FirstServerForverRule(意思为永远获取第一台服务器)继承 AbstractLoadBalancerRule抽象类。重写它的方法。

    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    import java.util.List;
    
    /**
     * @ClassName
     * @Describe 自定义实现 {@link com.netflix.loadbalancer.IRule}
     * @Author 66477
     * @Date 2020/6/922:39
     * @Version 1.0
     */
    public class FirstServerForverRule extends AbstractLoadBalancerRule {
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    
        @Override
        public Server choose(Object o) {
            Server server = null;
            ILoadBalancer loadBalancer = getLoadBalancer();
            List<Server> allServers = loadBalancer.getAllServers();
            return allServers.get(0);
        }
    }
    

    (2)person-client启动类中暴露自定义实现为Spring Bean

    @Bean
    public FirstServerForverRule firstServerForverRule(){
        return new FirstServerForverRule();
    }
    

    (3)激活这个配置,启动类上加上@RibbonClient(value = "person-service",configuration = FirstServerForverRule.class)

    (4)在下面位置打上断点,debug启动client。

    当我postman随便访问两个接口之一,发现确实走到里面的,allServers变量值为

    并且两个都是application.properties文件里配置的服务端server。那么显而易见,下面一句代码就是永远获取第一个服务。这就是这个自定义策略的实现。

    三、Hystrix整合

    1.新增依赖至父项目pom

    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    

    2.调整feign-api接口PersonService.增加fallback

    import com.gupao.feign.api.domain.Person;
    import com.gupao.feign.api.hystrix.PersonServiceFallback;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import java.util.Collection;
    
    /**
     * @ClassName
     * @Describe {@link Person}服务
     * @Author 66477
     * @Date 2020/6/822:25
     * @Version 1.0
     */
    @FeignClient(value="person-service",fallback = PersonServiceFallback.class)//服务提供方应用的名称
    public interface PersonService {
        /**
         * 保存
         * @param person {@link Person}
         * @return 如果成功,<code>true</code>
         */
        @PostMapping(value = "/person/save")
        boolean save(@RequestBody  Person person);
    
        /**
         * 查找所有的服务
         * @return
         */
        @GetMapping(value="/person/findAll")
        Collection<Person> findAll();
    }
    

    3.创建一个包hystrix,创建一个类实现PersonService.

    import com.gupao.feign.api.service.PersonService;
    import org.springframework.cloud.openfeign.FeignClient;
    import java.util.Collection;
    import java.util.Collections;
    
    /**
     * @ClassName
     * @Describe TODO
     * @Author 66477
     * @Date 2020/6/1020:42
     * @Version 1.0
     */
    
    public class PersonServiceFallback implements PersonService {
    
        @Override
        public boolean save(Person person) {
            return false;
        }
    
        @Override
        public Collection<Person> findAll() {
            return Collections.emptyList();
        }
    }
    

    4.调整客户端person-client,激活Hystrix:启动类增加@EnableHystrix

    四、问题总结

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

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

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

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

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

    解答:Rbbbon对于Eureka是不强依赖,不给过也不排除。

    4.生成环境上也都是feign?

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

    Feign作为客户端

    Ribbon作为负载均衡

    Eureka作为注册中心

    Zuul作为网关

    Security作为安全OAuth2认证

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

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

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

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

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

    解答:当然可以,Feign的接口定义要求强制实现。

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

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

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

    解答:需要其他设备来监控,PNIG eureka服务器是否活着,活着采用高可用方式管理Eureka。这可以调整心跳检测的频率。

  • 相关阅读:
    我不为人人,人人不为我
    sed 小结
    linux 之 压缩 / 解压
    java arraylist的问题
    flex swf和movieclip之前的微妙关系
    Flex contextMenu
    。。
    数据库
    flex Vector
    浮动ip
  • 原文地址:https://www.cnblogs.com/xusp/p/13090011.html
Copyright © 2020-2023  润新知