• 【云原生&微服务九】SpringCloud之Feign实现声明式客户端负载均衡详细案例


    一、Feign概述

    Feign是一个声明式的客户端负载均衡器;采用的是基于接口的注解;整合了ribbon,具有负载均衡的能力;整合了Hystrix,具有熔断的能力;

    1、为什么会使用Feign代替Ribbon

    使用RestTemplate + ribbon的方式来进行服务间的调用,会导致我们每次去调用其他服务的一个接口,都要单独写一些代码。

    而Feign是声明式调用,可以让我们不用写代码,直接用一些接口和注解就可以完成对其他服务的调用。

    2、Feign和OpenFeign的区别?

    1. 依赖不同:一个是spring-cloud-starter-feign,一个是spring-cloud-starter-openfeign
    2. 支持的注解:OpenFeign是springcloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。
      即:OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

    二、Feign实现负载均衡

    整体项目目录包括四个Module,分别为:eureka-server、feign-server-api、feign-server、feign-consumer。
    在这里插入图片描述

    其中eureka-server作为服务注册中心、feign-server-api作为服务提供者给consumer引入、feign-server作为具体的服务提供者实现、feign-consumer作为服务消费者。

    0、最上层父项目spring-cloud-center的pom.xml文件

    
    
        
            org.springframework.boot
            spring-boot-starter-parent
            2.3.7.RELEASE
             
        
        4.0.0
        pom
    
        
        
            feign-server-api
            feign-server
            feign-consumer
            eureka-server
        
    
        spring-cloud-center
        com.saint
        0.0.1-SNAPSHOT
        spring-cloud-center
    
        
            1.8
        
    
        
            
                
                    org.springframework.boot
                    spring-boot-dependencies
                    2.3.7.RELEASE
                    pom
                    import
                
                
                
                    org.springframework.cloud
                    spring-cloud-dependencies
                    Hoxton.SR8
                    pom
                    import
                
                
                
                    com.alibaba.cloud
                    spring-cloud-alibaba-dependencies
                    2.2.5.RELEASE
                    pom
                    import
                
            
        
    
        
            
                
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    
                        1.8
                        1.8
                        UTF-8
                    
                
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    关于Spring-cloud和SpringBoot的版本对应关系,参考博文:SpringBoot、SpringCloud、SpringCloudAlibaba的版本对应关系

    1、搭建服务注册中心eureka-server

    eureka-server整体代码结构目录如下:
    在这里插入图片描述
    其整体很简单、仅仅包含一个pom.xml文件、一个配置文件、一个启动类。

    1、pom.xml

    
    
        
            spring-cloud-center
            com.saint
            0.0.1-SNAPSHOT
        
        4.0.0
    
        eureka-server
        0.0.1-SNAPSHOT
        eureka-server
    
        
    
            
            
                org.springframework.cloud
                spring-cloud-starter-netflix-eureka-server
            
    
            
                org.springframework.boot
                spring-boot-starter-web
            
    
            
                org.springframework.boot
                spring-boot-starter
                true
            
            
                org.springframework.boot
                spring-boot-autoconfigure
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    2、修改配置文件application.yml

    server:
      port: 10010
    spring:
      application:
        name: eureka-server
    eureka:
      client:
        # 把自身注册到Eureka-server中
        register-with-eureka: true
        # 服务注册中心不需要去检索其他服务
        fetch-registry: false
        # 指定服务注册中心的位置
        service-url:
          defaultZone: http://localhost:10010/eureka
      instance:
        hostname: localhost
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3、修改启动类

    package com.saint;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     * @author Saint
     */
    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里和普通的启动有一个区别:需要加上 @EnableEurekaServer 注解开启Eureka-Server。

    4、启动eureka-server

    启动成功后,控制台输出如下:
    在这里插入图片描述
    进入到eureka-server 的dashboard,可以看到eureka-server已经上线:
    在这里插入图片描述

    2、搭建服务提供者API(feign-server-api)

    feign-server-api整体代码结构目录如下:
    在这里插入图片描述

    其中包含一个pom.xml文件、一个用户类User、一个标注@RequestMapping注解的接口ServiceA。

    1、pom.xml

    
    
        
            spring-cloud-center
            com.saint
            0.0.1-SNAPSHOT
        
        4.0.0
        feign-server-api
        0.0.1-SNAPSHOT
        feign test service provider api
    
        
            
                org.springframework.boot
                spring-boot-starter-web
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2、User

    package com.saint.feign.model;
    
    /**
     * @author Saint
     */
    public class User {
        private Long id;
        private String name;
        private Integer age;
    
        public User() {
        }
    
        public User(Long id, String name, Integer age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    3、ServiceA

    package com.saint.feign.service;
    
    import com.saint.feign.model.User;
    import org.springframework.web.bind.annotation.*;
    
    /**
     * @author Saint
     */
    @RequestMapping("/user")
    public interface ServiceA {
        @RequestMapping(value = "/sayHello/{id}", method = RequestMethod.GET)
        String sayHello(@PathVariable("id") Long id,
                        @RequestParam("name") String name,
                        @RequestParam("age") Integer age);
    
        @RequestMapping(value = "/", method = RequestMethod.POST)
        String createUser(@RequestBody User user);
    
        @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
        String updateUser(@PathVariable("id") Long id, @RequestBody User user);
    
        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        String deleteUser(@PathVariable("id") Long id);
    
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        User getById(@PathVariable("id") Long id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3、搭建服务提供者implement(feign-server)

    feign-server-api整体代码结构目录如下:
    在这里插入图片描述

    其中包含一个pom.xml文件、一个application配置文件、一个启动类、一个Controller。

    1、pom.xml

    
    
        
            spring-cloud-center
            com.saint
            0.0.1-SNAPSHOT
        
        4.0.0
    
        feign-server
        0.0.1-SNAPSHOT
        feign test service provider
    
        
    
            
                com.saint
                feign-server-api
                0.0.1-SNAPSHOT
            
    
            
            
                org.springframework.cloud
                spring-cloud-starter-netflix-eureka-client
            
    
            
                org.springframework.boot
                spring-boot-starter-web
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2、application.yml

    server:
      port: 8081
    spring:
      application:
        name: service-a
    eureka:
      client:
        # 将当前服务注册到服务注册中心
        service-url:
          defaultZone: http://localhost:10010/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3、启动类FeignServerApplication

    package com.saint.feign;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    /**
     * @author Saint
     */
    @EnableEurekaClient
    @SpringBootApplication
    public class FeignServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(FeignServerApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4、编写ServiceAController

    ServiceAController实现feign-server-api模块下的ServiceA,提供具体的业务实现。

    package com.saint.feign.controller;
    
    import com.saint.feign.model.User;
    import com.saint.feign.service.ServiceA;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Saint
     */
    @RestController
    public class ServiceAController implements ServiceA {
        @Override
        public String sayHello(@PathVariable("id") Long id,
                               @RequestParam("name") String name,
                               @RequestParam("age") Integer age) {
            System.out.println("打招呼,id=" + id + ", name=" + name + ", age=" + age);
            return "{'msg': 'hello, " + name + "'}";
        }
    
        @Override
        public String createUser(@RequestBody User user) {
            System.out.println("创建用户," + user);
            return "{'msg': 'success'}";
        }
    
        @Override
        public String updateUser(@PathVariable("id") Long id, @RequestBody User user) {
            System.out.println("更新用户," + user);
            return "{'msg': 'success'}";
        }
    
        @Override
        public String deleteUser(@PathVariable("id") Long id) {
            System.out.println("删除用户,id=" + id);
            return "{'msg': 'success'}";
        }
    
        @Override
        public User getById(@PathVariable("id") Long id) {
            System.out.println("查询用户,id=" + id);
            return new User(1L, "张三", 20);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    5、启动service-a服务实例1(8081端口)

    服务启动成功后,控制台输出如下:
    在这里插入图片描述

    再看eureka-server dashboard中多了一个 SERVICE-A 服务,并且其有一个实例 192.168.1.6:service-a:8081
    在这里插入图片描述

    6、启动service-a服务实例2(8082端口)

    1> 修改FeignServerApplication的配置:
    在这里插入图片描述

    2> 复制出一个FeignServerApplication配置:
    在这里插入图片描述

    3> 修改第二启动类配置名为:FeignServerApplication-8082,启动端口为8082
    在这里插入图片描述

    4> 运行FeignServerApplication-8082
    在这里插入图片描述

    5> 启动之后,看eureka-server dashboard中SERVICE-A 服务多了一个实例 192.168.1.6:service-a:8082
    在这里插入图片描述

    4、搭建服务消费者feign-consumer

    feign-consumer整体代码结构目录如下:
    在这里插入图片描述

    其包含一个pom.xml文件、一个application配置文件、一个启动类、一个FeignClient接口、一个Controller。

    1、pom.xml

    这里使用的是open-feign,想使用老版本的feign把代码中的注释放开;并把spring-cloud-starter-openfeign依赖注掉即可。

    
    
        
            spring-cloud-center
            com.saint
            0.0.1-SNAPSHOT
        
        4.0.0
    
        feign-consumer
        0.0.1-SNAPSHOT
        feign test consumer
    
        
    
            
                com.saint
                feign-server-api
                0.0.1-SNAPSHOT
            
    
            
    
    
    
    
    
    
            
            
                org.springframework.cloud
                spring-cloud-starter-openfeign
            
    
            
            
                org.springframework.cloud
                spring-cloud-starter-netflix-eureka-client
            
    
            
                org.springframework.boot
                spring-boot-starter-web
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    2、修改配置文件application.yml

    server:
      port: 9090
    spring:
      application:
        name: service-b
    eureka:
      client:
        # 将当前服务注册到服务注册中心
        service-url:
          defaultZone: http://localhost:10010/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注:服务端口为9090,后面我们进行接口调用的时候会用到。

    3、修改启动类

    package com.saint.feign;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * @author Saint
     */
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients
    public class FeignConsumerApplication {
        public static void main(String[] args) {
            SpringApplication.run(FeignConsumerApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其中的@EnableFeignClients注解负责使@FeignClient注解生效,可以被扫描到。

    4、FeignClient接口(ServiceAClient)

    package com.saint.feign.client;
    import com.saint.feign.service.ServiceA;
    import org.springframework.cloud.openfeign.FeignClient;
    
    /**
     * @author Saint
     */
    @FeignClient("SERVICE-A")
    public interface ServiceAClient extends ServiceA {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    FeignClient接口要实现feign-server-api中的ServiceA接口,以表明当前FeignClient针对的对象

    5、编写ServiceBController

    package com.saint.feign.controller;
    
    import com.saint.feign.model.User;
    import com.saint.feign.client.ServiceAClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    
    @RestController
    @RequestMapping("/ServiceB/user")  
    public class ServiceBController {
    	
    	@Autowired
    	private ServiceAClient serviceA;
    	
    	@RequestMapping(value = "/sayHello/{id}", method = RequestMethod.GET)
    	public String greeting(@PathVariable("id") Long id, 
    			@RequestParam("name") String name, 
    			@RequestParam("age") Integer age) {
    		return serviceA.sayHello(id, name, age);
    	}
    	
    	@RequestMapping(value = "/", method = RequestMethod.POST)
    	public String createUser(@RequestBody User user) {
    		return serviceA.createUser(user);
    	}
    
    	@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    	public String updateUser(@PathVariable("id") Long id, @RequestBody User user) {
    		return serviceA.updateUser(id, user); 
    	}
    
    	@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    	public String deleteUser(@PathVariable("id") Long id) {
    		return serviceA.deleteUser(id);
    	}
    
    	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
    	public User getById(@PathVariable("id") Long id) {
    		return serviceA.getById(id);
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    ServiceBController中通过FeignClient做负载均衡调用SERVICE-A服务中提供的接口。

    6、启动feign-consumer

    启动成功后,看eureka-server dashboard中多了一个 SERVICE-B 服务,并且其有一个实例 192.168.1.6:service-b:9090
    在这里插入图片描述

    4、使用浏览器进行调用服务消费者

    上述步骤中,我们已经依次启动了eureka-server、feign-server-8081、feign-server-8082、feign-consumer;三个服务、四个实例。

    此处我们针对服务消费者ribbon-feign-sample-consumer做四次接口调用,均为:http://localhost:9090/ServiceB/user/sayHello/1?name=saint&age=18

    在这里插入图片描述

    然后我们去看feign-server-8081、feign-server-8082的控制台输出:

    1> feign-server-8081控制台输出:
    在这里插入图片描述

    2> feign-server-8082控制台输出:
    在这里插入图片描述

    3> 结果说明:
    我们可以发现,四个请求,ribbon-feign-sample-8081和ribbon-feign-sample-8082各分担了两个请求。

    从现象上来看,已经Feign实现了负载均衡,并且默认是按照轮询的方式。

    下文我们接着讨论 Feign是如何实现负载均衡(源码分析)?

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    MySQL的MVCC机制
    OSEK network management
    函数习题(下)
    腾讯空降测试工程师,绩效次次拿S,真是砂纸擦屁股,给我露了一手啊
    【无标题】win10 server 服务器,安装mongodb时,遇到无法定位程序输入点BCryptHash , 降低mongodb 版本 4.2.3
    为什么TDM更适合数字传输?(模拟信号与数字信号传输比较,TDM与FDM传输方式比较)
    读书笔记-《你好哇,程序员》
    深入解读GLIDE/PITI代码
    服务器中毒怎么办?企业数据安全需重视
    LeetCode.15. 三数之和
  • 原文地址:https://blog.csdn.net/m0_67392126/article/details/126080717