ribbon 订阅
Ribbon是一种以面板及标签页为架构的用户界面(User Interface),原先出现在Microsoft Office 2007后续版本的Word、Excel和PowerPoint等组件中,后来也被运用到Windows 7的一些附加组件等其它软件中,如画图和写字板,以及Windows 8中的资源管理器。ESRI推出的ArcGIS Explorer 9.4也采用这种界面。它是一个收藏了命令按钮和图标的面板。它把命令组织成一组“标签”,每一组包含了相关的命令。每一个应用程序都有一个不同的标签组,展示了程序所提供的功能。在每个标签里,各种的相关的选项被组在一起。设计Ribbon的目的是为了使应用程序的功能更加易于发现和使用,减少了点击鼠标的次数[1]。有些标签,被称为“上下文相关标签”,只当特定的对象被选择时才显示。上下文相关标签只展示那些获得焦点的对象的特定功能,在对象没有被选定的时候是隐藏的。 展开全文
Ribbon是一种以面板及标签页为架构的用户界面(User Interface),原先出现在Microsoft Office 2007后续版本的Word、Excel和PowerPoint等组件中,后来也被运用到Windows 7的一些附加组件等其它软件中,如画图和写字板,以及Windows 8中的资源管理器。ESRI推出的ArcGIS Explorer 9.4也采用这种界面。它是一个收藏了命令按钮和图标的面板。它把命令组织成一组“标签”,每一组包含了相关的命令。每一个应用程序都有一个不同的标签组,展示了程序所提供的功能。在每个标签里,各种的相关的选项被组在一起。设计Ribbon的目的是为了使应用程序的功能更加易于发现和使用,减少了点击鼠标的次数[1]。有些标签,被称为“上下文相关标签”,只当特定的对象被选择时才显示。上下文相关标签只展示那些获得焦点的对象的特定功能,在对象没有被选定的时候是隐藏的。
信息
出    处
微软
内    容
收藏命令按钮和图示
中文名
功能区
外文名
Ribbon
Ribbon特点
1、将工具栏的命令分为一个个选项卡。2、与窗口标题栏融合在一起。
收起全文
精华内容
下载资源
问答
  • Ribbon

    千次阅读 2020-04-07 07:46:03
    Ribbon

    1 负载均衡的两种方式

     

    @Autowired
    private DiscoveryClient discoveryClient;
    //-------------------------------------------手写负载均衡----------------------------//
    @GetMapping("/hello")
    public String hello() {
        // 1.拿到用户中心所有的实例信息
        List<ServiceInstance> userInstances = discoveryClient.getInstances("user-center");
        // 2.所有用户中心实例的请求地址
        List<String> targetUrls = userInstances.stream()
                .map((instance) -> instance.getUri().toString() + "/user/hello")
                .collect(Collectors.toList());
        //随机请求
        int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
        log.info("请求的目标地址:{}", targetUrls.get(i));
        return new RestTemplate().getForObject(targetUrls.get(i), String.class);
    }

    2 整合Ribbon

    2.1 加依赖

    spring-cloud-starter-alibaba-nacos-discovery 已经依赖ribbon了所以无需单独引入

    使用ribbon无需写注解,无需写配置(需要修改另说)

    2.2 hello world

    https://cloud.spring.io/spring-cloud-static/Hoxton.SR3/reference/htmlsingle/#rest-template-loadbalancer-client

    @Configuration
    public class RestTemplageConfig {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/hello")
    public String hello() {
        return restTemplate.getForObject("http://user-center/user/hello", String.class);
    }

    3 Ribbon组成


    4 Ribbon内置的负载均衡规则


    5 Ribbon细粒度配置

    5.1 Java代码配置

    Spring中的父子容器:https://yuanyu.blog.csdn.net/article/details/105294435

    https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#_customizing_the_ribbon_client

    16.2 Customizing the Ribbon Client

    The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

    注意: UserCenterRibbonConfiguration需要被扫描到,RibbonConfiguration不能被扫描到,否则会成为全局配置。@SpringBootApplication会扫描当前包和子包

    @Configuration
    @RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
    public class UserCenterRibbonConfiguration {}
    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule ribbonRule() {
            return new RandomRule();
        }
    }
    

    5.2 用配置属性配置

    <clientName>.ribbon.NFLoadBalancerRuleClassName
    • com.netflix.loadbalancer.RandomRule
    • com.nobug.client.config.NacosSameClusterWeightedRule
    user-center:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

    5.3 最佳实践总结

    代码配置方式 vs 属性配置方式
    配置方式优点缺点
    代码配置基于代码,更加灵活有小坑(父子上下文) 
    线上修改得重新打包、发布
    属性配置【优先级更高】易上手
    配置更加直观
    线上修改无需重新打包、发布
    优先级更高
    极端场景下没有代码配置方式灵活
    • 尽量使用属性配置,属性方式实现不了的情况下再考虑用代码配置
    • 在同一个微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性

    6 Ribbon全局配置 

    • 方式一 : 让ComponentScan上下文重叠(强烈不建议使用)
    • 方式二【唯一正确的途径】 : @RibbonClients(defaultConfiguration=xxx.class)

     RibbonConfiguration需要被@SpringBootApplication扫描

    @Configuration
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class) 
    public class RibbonConfiguration {
    }

    7 Ribbon支持的配置项

    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule ribbonRule() {
            return new RandomRule();
        }
        @Bean
        public IPing ping(){
            return new PingUrl();
        }
        //...
    }
    

    https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#_customizing_the_ribbon_client_by_setting_properties

    16.4 Customizing the Ribbon Client by Setting Properties

    • <clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer
    • <clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule
    • <clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing
    • <clientName>.ribbon.NIWSServerListClassName: Should implement ServerList
    • <clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter
    xxx-center:
      ribbon:
        NFLoadBalancerClassName: ILoadBalancer实现类
        NFLoadBalancerRuleClassName: IRule实现类
        NFLoadBalancerPingClassName: IPing实现类
        NIWSServerListClassName: ServerList实现类
        NIWSServerListFilterClassName: ServerListFilter实现类

    8  Ribbon的饥饿加载 

    开启饥饿加载

    ribbon:
      eager-load:
        enabled: true
        clients: user-service ,xxx # 细粒度指定哪些具体的服务【多个使用逗号分隔,不在这个名单上的依然使用懒加载】

    9 nacos && Ribbon

    9.1 扩展Ribbon支持Nacos权重

    http://www.imooc.com/article/288660

    /**
     * 扩展Ribbon支持Nacos权重
     */
    //IRule
    @Slf4j
    public class NacosWeightedRule extends AbstractLoadBalancerRule {
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
            //读取配置文件,并初始化NacosWeightedRule
        }
        @Override
        public Server choose(Object o) {
            try {
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
                log.info("lb={}",loadBalancer);
                //想要请求的微服务的名称
                String name = loadBalancer.getName();
                //拿到服务发现的相关API
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                //nacos client自动通过基于权重的负载均衡算法,给我们选择一个实例
                Instance instance = namingService.selectOneHealthyInstance(name);
                log.info("选择的实例是: port={},instance={}", instance.getPort(), instance);
                return new NacosServer(instance);
            } catch (NacosException e) {
                //e.printStackTrace();
                log.error(e.getErrMsg());
            }
            return null;
        }
    }

    9.2 扩展Ribbon同一集群优先调用

    /**
     * 扩展Ribbon-同一集群优先调用
     */
    @Slf4j
    public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {}
        @Override
        public Server choose(Object o) {
            //1.找到指定服务的所有实例 A
            //2.过滤出相同集群下的所有实例 B
            //3.如果B是空,就用A
            //4.基于权重的负载均衡算法,返回1个实例
            try {
                //获取到配置中的集群名称 SH
                //spring.cloud.nacos.discovery.cluster-name=SH
                String clusterName = nacosDiscoveryProperties.getClusterName();
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
                //想要请求的微服务的名称
                String name = loadBalancer.getName();
                //拿到服务发现的相关API
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                //1.找到指定服务的所有实例
                List<Instance> instances = namingService.selectInstances(name, true);
    
                //2.过滤出相同集群下的所有实例
                List<Instance> sameClusterInstances = instances.stream()
                        .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                        .collect(Collectors.toList());
                // 3.如果上述2为空,就用1
                List<Instance> instancesToBeChosen;
                if (CollectionUtils.isEmpty(sameClusterInstances)) {
                    instancesToBeChosen = instances;
                    log.warn("发生了跨集群的调用,name = {}, clusterName = {}, instances = {}", name, clusterName, instances);
                } else {
                    instancesToBeChosen = sameClusterInstances;
                    log.info("同一集群调用,name = {}, clusterName = {}, instances = {}", name, clusterName, instances);
                }
                //4.基于权重的负载均衡算法,返回一个实例
                Instance instance = ExtendsBalancer.getHostByRandomWeightExtends(instancesToBeChosen);
                log.info("选择的实例是  instance = {}", instance);
                return new NacosServer(instance);
            } catch (Exception e) {
                //e.printStackTrace();
                log.error(e.getMessage());
                return null;
            }
        }
    }
    /**
     *
     */
    class ExtendsBalancer extends Balancer {
        public static Instance getHostByRandomWeightExtends(List<Instance> hosts) {
            return getHostByRandomWeight(hosts);
        }
    }

     

    扩展Ribbon支持基于元数据的版本管理:http://www.imooc.com/article/288674

    展开全文
  • ribbon

    2019-10-24 03:50:54
    ribbon字体是一款用于个性海报文化设计字体
  • 转载请标明出处: http://blog.csdn.net/forezp/article/details/69788938 本文出自方志朋的博客 在上一篇文章,讲了服务的注册和...Spring cloud有两种调用方式,一种是ribbon+restTemplate,另一种是feign。在这

    转载请标明出处:
    https://www.fangzhipeng.com/springcloud/2017/06/02/sc02-rest-ribbon.html
    本文出自方志朋的博客

    个人博客纯净版:https://www.fangzhipeng.com/springcloud/2017/06/02/sc02-rest-ribbon.html

    最新Finchley版本:
    https://www.fangzhipeng.com/springcloud/2018/08/02/sc-f2-ribbon.html
    或者
    http://blog.csdn.net/forezp/article/details/81040946

    在上一篇文章,讲了服务的注册和发现。在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。在这一篇文章首先讲解下基于ribbon+rest。

    一、ribbon简介

    Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using @FeignClient then this section also applies.

    -----摘自官网

    ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。Feign默认集成了ribbon。

    ribbon 已经默认实现了这些配置bean:

    • IClientConfig ribbonClientConfig: DefaultClientConfigImpl

    • IRule ribbonRule: ZoneAvoidanceRule

    • IPing ribbonPing: NoOpPing

    • ServerList ribbonServerList: ConfigurationBasedServerList

    • ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter

    • ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

    二、准备工作

    这一篇文章基于上一篇文章的工程,启动eureka-server 工程;启动service-hi工程,它的端口为8762;将service-hi的配置文件的端口改为8763,并启动,这时你会发现:service-hi在eureka-server注册了2个实例,这就相当于一个小的集群。访问localhost:8761如图所示:

    如何一个工程启动多个实例,请看这篇文章:https://blog.csdn.net/forezp/article/details/76408139

    在这里插入图片描述

    三、建一个服务消费者

    重新新建一个spring-boot工程,取名为:service-ribbon;
    在它的pom.xml文件分别引入起步依赖spring-cloud-starter-eureka、spring-cloud-starter-ribbon、spring-boot-starter-web,代码如下:

    <?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">
    	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>com.forezp</groupId>
    	<artifactId>service-ribbon</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<packaging>jar</packaging>
    
    	<name>service-ribbon</name>
    	<description>Demo project for Spring Boot</description>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.2.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-eureka</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-ribbon</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    
    	<dependencyManagement>
    		<dependencies>
    			<dependency>
    				<groupId>org.springframework.cloud</groupId>
    				<artifactId>spring-cloud-dependencies</artifactId>
    				<version>Dalston.RC1</version>
    				<type>pom</type>
    				<scope>import</scope>
    			</dependency>
    		</dependencies>
    	</dependencyManagement>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    	<repositories>
    		<repository>
    			<id>spring-milestones</id>
    			<name>Spring Milestones</name>
    			<url>https://repo.spring.io/milestone</url>
    			<snapshots>
    				<enabled>false</enabled>
    			</snapshots>
    		</repository>
    	</repositories>
    
    
    </project>
    
    
    

    在工程的配置文件指定服务的注册中心地址为http://localhost:8761/eureka/,程序名称为 service-ribbon,程序端口为8764。配置文件application.yml如下:

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    server:
      port: 8764
    spring:
      application:
        name: service-ribbon
    

    在工程的启动类中,通过@EnableDiscoveryClient向服务中心注册;并且向程序的ioc注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。

    @SpringBootApplication
    @EnableDiscoveryClient
    public class ServiceRibbonApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(ServiceRibbonApplication.class, args);
    	}
    
    	@Bean
    	@LoadBalanced
    	RestTemplate restTemplate() {
    		return new RestTemplate();
    	}
    
    }
    
    

    写一个测试类HelloService,通过之前注入ioc容器的restTemplate来消费service-hi服务的“/hi”接口,在这里我们直接用的程序名替代了具体的url地址,在ribbon中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的url替换掉服务名,代码如下:

    @Service
    public class HelloService {
    
        @Autowired
        RestTemplate restTemplate;
    
        public String hiService(String name) {
            return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
        }
    
    }
    
    

    写一个controller,在controller中用调用HelloService 的方法,代码如下:

    
    /**
     * Created by fangzhipeng on 2017/4/6.
     */
    @RestController
    public class HelloControler {
    
        @Autowired
        HelloService helloService;
        @RequestMapping(value = "/hi")
        public String hi(@RequestParam String name){
            return helloService.hiService(name);
        }
    
    
    }
    
    

    在浏览器上多次访问http://localhost:8764/hi?name=forezp,浏览器交替显示:

    hi forezp,i am from port:8762

    hi forezp,i am from port:8763

    这说明当我们通过调用restTemplate.getForObject(“http://SERVICE-HI/hi?name=”+name,String.class)方法时,已经做了负载均衡,访问了不同的端口的服务实例。

    四、此时的架构

    在这里插入图片描述

    • 一个服务注册中心,eureka server,端口为8761
    • service-hi工程跑了两个实例,端口分别为8762,8763,分别向服务注册中心注册
    • sercvice-ribbon端口为8764,向服务注册中心注册
    • 当sercvice-ribbon通过restTemplate调用service-hi的hi接口时,因为用ribbon进行了负载均衡,会轮流的调用service-hi:8762和8763 两个端口的hi接口;

    源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter2

    更多阅读

    史上最简单的 SpringCloud 教程汇总

    SpringBoot教程汇总

    Java面试题系列汇总

    五、参考资料

    本文参考了以下:

    spring-cloud-ribbon

    springcloud ribbon with eureka

    服务消费者


    扫码关注公众号有惊喜

    (转载本站文章请注明作者和出处 方志朋的博客

    展开全文
  • (三)Feign请求超时问题 方法一 方法二 方法三 三、Spring Cloud Ribbon概述与核心工作原理 (一)Ribbon与负载均衡 (二)Ribbon核心工作原理 1.Ribbon 服务配置方式 2.和Feign的集成模式 (三)LoadBalancer–...

    目录

    一、Spring Cloud Feign概述与工作原理解读

    (一)服务间调用的几种方式

    (二)Feign 概述

    二、FeignClent注解剖析+Spring Cloud Feign基本功能配置解读

    (一)@FeignClient 注解剖析

    (二)Spring Cloud Feign基本功能配置

    (三)Feign请求超时问题

    方法一

    方法二

    方法三

    三、Spring Cloud Ribbon概述与核心工作原理

    (一)Ribbon与负载均衡

    (二)Ribbon核心工作原理

    1.Ribbon 服务配置方式

    2.和Feign的集成模式

    (三)LoadBalancer–负载均衡器的核心

    1.负载均衡器的内部基本实现原理

    2.如何维护Server列表?(新增、更新、删除)

    3.负载均衡器如何维护服务实例的状态?

    4.如何从服务列表中挑选一个合适的服务实例?

    (1)服务实例容器:ServerList的维护

    (2) 服务实例列表过滤器ServerListFilter

    (3)LoadBalancer选择服务实例 的流程

    四、Spring Cloud Ribbon源码解读

    注:以上所有只做理论性的总结与分析,相关实战代码会在后面的博客中和github中逐步增加。

    参考书籍、文献和资料:


    一、Spring Cloud Feign概述与工作原理解读

    (一)服务间调用的几种方式

    使用Spring Cloud开发微服务时,在服务消费者调用服务提供者时,底层通过HTTP Client 的方式访问。但实际上在服务调用时,有主要以下来实现:

    使用JDK原生的URLConnection;

    Apache提供的HTTP Client;

    Netty提供的异步HTTP Client;

    Spring提供的RestTemplate。

    Spring Cloud的Spring Cloud Open Feign相对是最方便与最优雅的,使Feign支持Spring MVC注解的同时并整合了Ribbon。

    (二)Feign 概述

    Feign 是一个声明式的 Web Service 客户端。它的出现使开发 Web Service 客户端变得很简单。使用 Feign 只需要创建一个接口加上对应的注解,比如:@FeignClient 注解。 Feign 有可插拔的注解,包括 Feign 注解和 AX-RS 注解。Feign 也支持编码器和解码器,Spring Cloud Open Feign 对 Feign 进行增强支持 Spring Mvc 注解,可以像 Spring Web 一样使用 HttpMessageConverters 等。

    Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。接下来介绍一下 Feign 的特性,具体如下:

    • 可插拔的注解支持,包括 Feign 注解和AX-RS注解。
    • 支持可插拔的 HTTP 编码器和解码器。
    • 支持 Hystrix 和它的 Fallback。
    • 支持 Ribbon 的负载均衡。
    • 支持 HTTP 请求和响应的压缩。Feign 是一个声明式的 WebService 客户端,它的目的就是让 Web Service 调用更加简单。它整合了 Ribbon 和 Hystrix,从而不需要开发者针对 Feign 对其进行整合。Feign 还提供了 HTTP 请求的模板,通过编写简单的接口和注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。Feign 会完全代理 HTTP 的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。

    (三)Feign 工作原理

    1. 在开发微服务应用时,我们会在主程序入口添加 @EnableFeignClients 注解开启对 Feign Client 扫描加载处理。根据 Feign Client 的开发规范,定义接口并加 @FeignClients 注解。
    2. 当程序启动时,会进行包扫描,扫描所有 @FeignClients 的注解的类,并将这些信息注入 Spring IOC 容器中。当定义的 Feign 接口中的方法被调用时,通过JDK的代理的方式,来生成具体的 RequestTemplate。当生成代理时,Feign 会为每个接口方法创建一个 RequetTemplate 对象,该对象封装了 HTTP 请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的。
    3. 然后由 RequestTemplate 生成 Request,然后把 Request 交给 Client 去处理,这里指的 Client 可以是 JDK 原生的 URLConnection、Apache 的 Http Client 也可以是 Okhttp。最后 Client 被封装到 LoadBalanceclient 类,这个类结合 Ribbon 负载均衡发起服务之间的调用。

    二、FeignClent注解剖析+Spring Cloud Feign基本功能配置解读

    (一)@FeignClient 注解剖析

    FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上。

    声明接口之后,在代码中通过@Resource注入之后即可使用。@FeignClient标签的常用属性如下:

    • name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
    • url: url一般用于调试,可以手动指定@FeignClient调用的地址
    • decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
    • configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
    • fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
    • fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
    • path: 定义当前FeignClient的统一前缀

    (二)Spring Cloud Feign基本功能配置

    所谓的基本功能配置主要是指可以自定义Feign的配置,相关代码会在后续博客和github中更新。

    1. 日志配置(后续博客和github中更新)
    2. 契约配置(后续博客和github中更新)
    3. Basic认证配置(后续博客和github中更新)
    4. 超时时间配置(后续博客和github中更新)
    5. 客户端组件配置(后续博客和github中更新)
    6. GZIP压缩配置(后续博客和github中更新)
    7. 编码器解码器配置(后续博客和github中更新)
    8. Feign默认Client的替换配置(后续博客和github中更新)
    9. Feign返回图片流处理方式(后续博客和github中更新)
    10. Feign调用传递Token(后续博客和github中更新)
    11. venus-cloud-feign的设计和使用(后续博客和github中更新)

    (三)Feign请求超时问题

    Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了
    解决方案有三种,以feign为例。

    方法一

    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
    该配置是让Hystrix的超时时间改为5秒

    方法二

    hystrix.command.default.execution.timeout.enabled: false
    该配置,用于禁用Hystrix的超时时间

    方法三

    feign.hystrix.enabled: false
    该配置,用于索性禁用feign的hystrix。该做法除非一些特殊场景,不推荐使用。

    三、Spring Cloud Ribbon概述与核心工作原理

    Ribbon是2013年1月Netflix公司开发的一个组件,它在云服务体系中起着至关重要的作用,一直是Netflix最活跃的项目。

    Pivotal公司将其整入了Spring Cloud生态,正式命名为Spring Cloud Ribbon作为微服务弹性扩展的组件,与其他组件结合发挥强大作用。(Pivotal中国研发中心于2013.04成立)

    (一)Ribbon与负载均衡

    负载均衡在业界有不少分类:(基本可见https://blog.csdn.net/xiaofeng10330111/article/details/85682513

    最常见的有软负载和硬负载,代表产品为nginx和F5.

    另外一组分类为集中式负载和进程内负载,即服务端负载均衡和客户端负载均衡。这种分类下,nginx和F5都为集中式负载,Ribbon为进程内负载。

    Ribbon是Spring Cloud框架中相当核心的模块,负责着服务负载调用,Ribbon也可以脱离SpringCloud单独使用。
    另外Ribbon是客户端的负载均衡框架,即每个客户端上,独立维护着自身的调用信息统计,相互隔离;也就是说:Ribbon的负载均衡表现在各个机器上变现并不完全一致
    Ribbon 也是整个组件框架中最复杂的一环,控制流程上为了保证服务的高可用性,有很多比较细节的参数控制,在使用的过程中,需要深入理清每个环节的处理机制,这样在问题定位上会高效很多。

    (二)Ribbon核心工作原理                      

    Spring Cloud集成模式下的Ribbon有以下几个特征:

    1.Ribbon 服务配置方式

    每一个服务配置都有一个Spring ApplicationContext上下文,用于加载各自服务的实例。
    比如,当前Spring Cloud 系统内,有如下几个服务:

    服务名称角色依赖服务
    order 订单模块user
    user用户模块
    mobile-bff移动端BFForder,user

    mobile-bff服务在实际使用中,会用到order和user模块,那么在mobile-bff服务的Spring上下文中,会为order 和user 分别创建一个子ApplicationContext,用于加载各自服务模块的配置。也就是说,各个客户端的配置相互独立,彼此不收影响

    2.和Feign的集成模式

    在使用Feign作为客户端时,最终请求会转发成 http://<服务名称>/<relative-path-to-service>的格式,通过LoadBalancerFeignClient, 提取出服务标识<服务名称>,然后根据服务名称在上下文中查找对应服务的负载均衡器FeignLoadBalancer,负载均衡器负责根据既有的服务实例的统计信息,挑选出最合适的服务实例。

    (三)LoadBalancer–负载均衡器的核心

    LoadBalancer 的职能主要有三个:

    • 维护Sever列表的数量(新增、更新、删除等)
    • 维护Server列表的状态(状态更新)
    • 当请求Server实例时,能否返回最合适的Server实例

    1.负载均衡器的内部基本实现原理    

     

     

    • Server  

    Server 作为服务实例的表示,会记录服务实例的相关信息,如:服务地址,所属zone,服务名称,实例ID等    

    • ServerList    

    维护着一组Server实例列表,在应用运行的过程中,Ribbon通过ServerList中的服务实例供负载均衡器选择。ServerList维护列表可能在运行的过程中动态改变   

    • ServerStats    

    作为对应Server 的运行情况统计,一般是服务调用过程中的Server平均响应时间,累计请求失败计数,熔断时间控制等。一个ServerStats实例唯一对应一个Server实例    

    • LoadBalancerStats    

    作为 ServerStats实例列表的容器,统一维护    

    • ServerListUpdater    

    负载均衡器通过ServerListUpdater来更新ServerList,比如实现一个定时任务,每隔一段时间获取最新的Server实例列表 

    • Pinger    

    服务状态检验器,负责维护ServerList列表中的服务状态注意:Pinger仅仅负责Server的状态,没有能力决定是否删除    

    • PingerStrategy    

    定义以何种方式还检验服务是否有效,比如是按照顺序的方式还是并行的方式    

    • IPing    

    Ping,检验服务是否可用的方法,常见的是通过HTTP,或者TCP/IP的方式看服务有无认为正常的请求    

    2.如何维护Server列表?(新增、更新、删除)             

    Server列表的维护从实现方法上分为两类:

    • 基于配置的服务列表

    这种方式一般是通过配置文件,静态地配置服务器列表,这种方式相对而言比较简单,但并不是意味着在机器运行的时候就一直不变。netflix 在做Spring cloud 套件时,使用了分布式配置框架netflix archaius ,archaius 框架有一个特点是会动态的监控配置文件的变化,将变化刷新到各个应用上。也就是说,当我们在不关闭服务的情况下,如果修改了基于配置的服务列表时, 服务列表可以直接刷新

    • 结合服务发现组件(如Eureka)的服务注册信息动态维护服务列表

    基于Spring Cloud框架下,服务注册和发现是一个分布式服务集群必不可少的一个组件,它负责维护不同的服务实例(注册、续约、取消注册)。

    3.负载均衡器如何维护服务实例的状态?

    Ribbon负载均衡器将服务实例的状态维护托交给Pinger、 PingerStrategyIPing 来维护,具体交互模式如下所示:              

    4.如何从服务列表中挑选一个合适的服务实例?

    (1)服务实例容器:ServerList的维护

    负载均衡器通过 ServerList来统一维护服务实例,具体模式如上图,基础的接口定义如下:

    /**
     * Interface that defines the methods sed to obtain the List of Servers
     * @author stonse
     *
     * @param <T>
     */
    public interface ServerList<T extends Server> {
        //获取初始化的服务列表
        public List<T> getInitialListOfServers();
        
        /**
         * Return updated list of servers. This is called say every 30 secs
         * (configurable) by the Loadbalancer's Ping cycle
         * 获取更新后的的服务列表
         */
        public List<T> getUpdatedListOfServers();   
    
    }

       在Ribbon的实现中,在ServerList(负责维护服务实例,并使用ServerListFilter过滤器过滤出符合要求的服务实例列表List<Server>)中,维护着Server的实例,并返回最新的List<Server>集合,供LoadBalancer使用   。               

    (2) 服务实例列表过滤器ServerListFilter

    传入一个服务实例列表,过滤出满足过滤条件的服务列表

    public interface ServerListFilter<T extends Server> {
        public List<T> getFilteredListOfServers(List<T> servers);
    }
    • Ribbon 的默认ServerListFilter实现1:ZoneAffinityServerListFilter

    Ribbon默认采取了区域优先的过滤策略,即当Server列表中,过滤出和当前实例所在的区域(zone)一致的server列表

    • Ribbon 的ServerListFilter实现2:ZonePreferenceServerListFilter

    ZonePreferenceServerListFilter 集成自 ZoneAffinityServerListFilter,在此基础上做了拓展,在 返回结果的基础上,再过滤出和本地服务相同区域(zone)的服务列表。

    当指定了当前服务的所在Zone,并且 ZoneAffinityServerListFilter 没有起到过滤效果时,ZonePreferenceServerListFilter会返回当前Zone的Server列表。

    • Ribbon 的ServerListFilter实现3:ServerListSubsetFilter

    这个过滤器作用于当Server数量列表特别庞大时(比如有上百个Server实例),这时,长时间保持Http链接也不太合适,可以适当地保留部分服务,舍弃其中一些服务,这样可使释放没必要的链接。
    此过滤器也是继承自 ZoneAffinityServerListFilter,在此基础上做了拓展,在实际使用中不太常见。

    (3)LoadBalancer选择服务实例 的流程

    LoadBalancer的核心功能是根据负载情况,从服务列表中挑选最合适的服务实例。LoadBalancer内部采用了如下图所示的组件完成:

    LoadBalancer 选择服务实例的流程

    • 通过ServerList获取当前可用的服务实例列表;
    • 通过ServerListFilter将步骤1 得到的服务列表进行一次过滤,返回满足过滤器条件的服务实例列表;
    • 应用Rule规则,结合服务实例的统计信息,返回满足规则的某一个服务实例;

    通过上述的流程可以看到,实际上,在服务实例列表选择的过程中,有两次过滤的机会:第一次是首先通过ServerListFilter过滤器,另外一次是用过IRule 的选择规则进行过滤。

    四、Spring Cloud Ribbon源码解读

    既然是在restTemplate加了@LoadBalanced注解,那就进去这个注解里面看下吧。

    /**
     * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
     * @author Spencer Gibb
     */
    @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Qualifier
    public @interface LoadBalanced {
    }

    从注释中可以知道,这个注解是用来给RestTemplate做标记,以使用负载均衡客户端(LoadBalancerClient)来配置它。所以,我们在生成的RestTemplate的bean上添加这么一个注解,这个bean就会配置LoadBalancerClient。LoadBalancerClient的代码如下:

    /**
     * Represents a client side load balancer
     * @author Spencer Gibb
     */
    public interface LoadBalancerClient {
        /**
         * Choose a ServiceInstance from the LoadBalancer for the specified service
         * @param serviceId the service id to look up the LoadBalancer
         * @return a ServiceInstance that matches the serviceId
         */
        ServiceInstance choose(String serviceId);
    
        /**
         * execute request using a ServiceInstance from the LoadBalancer for the specified
         * service
         * @param serviceId the service id to look up the LoadBalancer
         * @param request allows implementations to execute pre and post actions such as
         * incrementing metrics
         * @return the result of the LoadBalancerRequest callback on the selected
         * ServiceInstance
         */
        <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    
        /**
         * Create a proper URI with a real host and port for systems to utilize.
         * Some systems use a URI with the logical serivce name as the host,
         * such as http://myservice/path/to/service.  This will replace the
         * service name with the host:port from the ServiceInstance.
         * @param instance
         * @param original a URI with the host as a logical service name
         * @return a reconstructed URI
         */
        URI reconstructURI(ServiceInstance instance, URI original);
    
    }

    LoadBalancerClient是一个接口,里面有三个方法。 
    第一个,ServiceInstance choose(String serviceId);从方法名上就可以看出,是根据传入的serviceId(服务名),从负载均衡器中选择一个服务实例,服务实例通过ServiceInstance类来表示。 
    第二个,execute方法,使用从负载均衡器中选择的服务实例来执行请求内容。 
    第三个,URI reconstructURI(ServiceInstance instance, URI original);方法,是重新构建一个URI的,还记得我们在代码中,通过RestTemplate请求服务时,写的是服务名吧,这个方法就会把这个请求的URI进行转换,返回host+port,通过host+port的形式去请求服务。 
    从工程中搜索LoadBalancerClient接口的实现类,可以找到RibbonLoadBalancerClient类实现了这个接口,并且实现了接口中定义的方法。

    再梳理一下逻辑,我们在RestTemplate上添加了@LoadBalanced注解,RibbonLoadBalancerClient就会配置到这个RestTemplate实例上。

    在LoadBalancerClient接口的同一个包路径下,还会看到另一个LoadBalancerAutoConfiguration类,看名字就感觉这是一个自动配置LoadBalancer的,进去这个类看一下。

    /**
     * Auto configuration for Ribbon (client side load balancing).
     *
     * @author Spencer Gibb
     * @author Dave Syer
     */
    @Configuration
    @ConditionalOnClass(RestTemplate.class)
    @ConditionalOnBean(LoadBalancerClient.class)
    public class LoadBalancerAutoConfiguration {
    
        @LoadBalanced
        @Autowired(required = false)
        private List<RestTemplate> restTemplates = Collections.emptyList();
    
        @Bean
        public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
                final List<RestTemplateCustomizer> customizers) {
            return new SmartInitializingSingleton() {
                @Override
                public void afterSingletonsInstantiated() {
                    for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                        for (RestTemplateCustomizer customizer : customizers) {
                            customizer.customize(restTemplate);
                        }
                    }
                }
            };
        }
    @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient) {
            return new LoadBalancerInterceptor(loadBalancerClient);
        }
    
    }

    注释中说明这个类是为Ribbon做自动配置的,类上的@Configuration说明这是一个配置类,在当前项目中存在RestTemplate类、并且存在LoadBalancerClient接口的实现类时,就满足了自动化配置的条件。 
    在LoadBalancerAutoConfiguration类中,创建了一个LoadBalancerInterceptor拦截器,还维护了一个被@LoadBalanced修饰的RestTemplate列表,在初始化的时候,会为每个restTemplate实例添加LoadBalancerInterceptor拦截器。 
    我们自己实现的项目,就定义了RestTemplate的一个对象,并且引入了spring-cloud相关的包,存在RibbonLoadBalancerClient作为LoadBalancerClient的实现类,所以,满足自动化配置的条件。接下来就看下,在restTemplate实例添加的LoadBalancerInterceptor拦截器的逻辑。

    public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    
        private LoadBalancerClient loadBalancer;
    
        public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
            this.loadBalancer = loadBalancer;
        }
    
        @Override
        public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                final ClientHttpRequestExecution execution) throws IOException {
            final URI originalUri = request.getURI();
            String serviceName = originalUri.getHost();
            return this.loadBalancer.execute(serviceName,
                    new LoadBalancerRequest<ClientHttpResponse>() {
    
                        @Override
                        public ClientHttpResponse apply(final ServiceInstance instance)
                                throws Exception {
                            HttpRequest serviceRequest = new ServiceRequestWrapper(request,
                                    instance);
                            return execution.execute(serviceRequest, body);
                        }
    
                    });
        }
        private class ServiceRequestWrapper extends HttpRequestWrapper {
    
            private final ServiceInstance instance;
    
            public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance) {
                super(request);
                this.instance = instance;
            }
    
            @Override
            public URI getURI() {
                URI uri = LoadBalancerInterceptor.this.loadBalancer.reconstructURI(
                        this.instance, getRequest().getURI());
                return uri;
            }
    
        }
    
    }

    由于在自动配置类中,对restTemplate实例添加了LoadBalancerInterceptor拦截器,所以,当用restTemplate发送http请求时,就会执行这个拦截器的intercept方法。 
    intercept方法中,会根据request.getURI(),获取请求的uri,再获取host,我们在发送http请求的时候,是用的服务名作为host,所以,这里就会拿到服务名,再调用具体LoadBalancerClient实例的execute方法,发送请求。 
    LoadBalancerClient的实现类为RibbonLoadBalancerClient,最终的负载均衡请求由它来执行,所以,还需要再梳理下RibbonLoadBalancerClient的逻辑。

    先看下RibbonLoadBalancerClient中的execute方法:

    @Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
                serviceId), serverIntrospector(serviceId).getMetadata(server));
    
        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
    
        try {
            T returnVal = request.apply(ribbonServer);
            statsRecorder.recordStats(returnVal);
            return returnVal;
        }
        // catch IOException and rethrow so RestTemplate behaves correctly
        catch (IOException ex) {
            statsRecorder.recordStats(ex);
            throw ex;
        }
        catch (Exception ex) {
            statsRecorder.recordStats(ex);
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }

    服务名作为serviceId字段传进来,先通过getLoadBalancer获取loadBalancer,再根据loadBalancer获取server,下面是getServer的代码:

    protected Server getServer(ILoadBalancer loadBalancer) {
        if (loadBalancer == null) {
            return null;
        }
        return loadBalancer.chooseServer("default"); // TODO: better handling of key
    }

    如果loadBalancer为空,就直接返回空,否则就调用loadBalancer的chooseServer方法,获取相应的server。 
    看一下ILoadBalancer是一个接口,里面声明了一系列负载均衡实现的方法:

    public interface ILoadBalancer {
        public void addServers(List<Server> newServers);
        public Server chooseServer(Object key);
        public void markServerDown(Server server);
        public List<Server> getReachableServers();
        public List<Server> getAllServers();
    }

    这里面还有一个getServerList方法,不过已经标记为Deprecated,所以就没有列出。 
    这些方法名比较直观,很容易就能猜出是干啥的,addServers是用来添加一个server集合,chooseServer是选择一个server,markServerDown用来标记某个服务下线,getReachableServers获取可用的Server集合,getAllServers是获取所有的server集合。 
    ILoadBalancer有很多实现,那具体是用的哪个类呢,可以通过RibbonClientConfiguration类看到,这个配置类在初始化的时候,返回了ZoneAwareLoadBalancer作为负载均衡器。

    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
            ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
            IRule rule, IPing ping) {
        ZoneAwareLoadBalancer<Server> balancer = LoadBalancerBuilder.newBuilder()
                .withClientConfig(config).withRule(rule).withPing(ping)
                .withServerListFilter(serverListFilter).withDynamicServerList(serverList)
                .buildDynamicServerListLoadBalancer();
        return balancer;
    }

    ZoneAwareLoadBalancer从名字中可以看出来,这个负载均衡器和zone是有关系的。下面看下ZoneAwareLoadBalancer中的chooseServer方法:

    @Override
    public Server chooseServer(Object key) {
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }
    
            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Throwable e) {
            logger.error("Unexpected exception when choosing server using zone aware logic", e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }
    

    这个方法会根据server的zone和可用性来选择具体的实例,返回一个Server对象。

    通过ZoneAwareLoadBalancer选择具体的Server之后,再包装成RibbonServer对象,之前返回的server是该对象中的一个字段,除此之外,还有服务名serviceId,是否需要使用https等信息。最后,通过LoadBalancerRequest的apply方法,向具体的server发请求,从而实现了负载均衡。 
    下面是apply方法的定义:

    public interface LoadBalancerRequest<T> {
        public T apply(ServiceInstance instance) throws Exception;
    }

    在请求时,传入的ribbonServer对象,被当成ServiceInstance类型的对象进行接收。ServiceInstance是一个接口,定义了服务治理系统中,每个实例需要提供的信息,比如serviceId,host,port等。 
    LoadBalancerRequest是一个接口,最终会通过实现类的apply方法去执行,实现类是在LoadBalancerInterceptor中调用RibbonLoadBalancerClient的execute方法时,传进来的一个匿名类,可以通过查看LoadBalancerInterceptor的代码看到。 
    创建LoadBalancerRequest匿名类的时候,就重写了apply方法,apply方法中,还新建了一个ServiceRequestWrapper的内部类,这个类中,就重写了getURI方法,getURI方法会调用loadBalancer的reconstructURI方法来构建uri。

    看到这里,已经可以大体知道Ribbon实现负载均衡的流程了,我们在RestTemplate上添加注解,就会有LoadBalancerClient的对象来配置它,也就是RibbonLoadBalancerClient。同时,LoadBalancerAutoConfiguration会进行配置,创建一个LoadBalancerInterceptor,并且拿到我们声明的所有restTemplate,在这些restTemplate中添加LoadBalancerInterceptor拦截器。 
    当通过restTemplate发送请求时,就会经过这个拦截器,在拦截器中,就会调用RibbonLoadBalancerClient中的方法,获取到根据服务名,通过负载均衡方法获取到服务实例,然后去请求这个实例。 
    上面说的这些,是如何对请求进行负载均衡的,但是还有个问题,我们请求的实例,是从Eureka Server上获取到的,那这个实例列表是如何获取的呢?怎么保证这个实例列表中的实例是可用的呢?

    在RibbonLoadBalancerClient选择实例的时候,是通过ILoadBalancer的实现类根据负载均衡算法选择服务实例的,也就是ZoneAwareLoadBalancer的chooseServer中的逻辑,那就在这里找线索。查看ZoneAwareLoadBalancer的继承关系,可以看到如下图所示。 

    可以看到,最上面是ILoadBalancer接口,AbstractLoadBalancer类继承了这个接口,BaseLoadBalancer继承了AbstractLoadBalancer类,DynamicServerListLoadBalancer继承了BaseLoadBalancer,ZoneAwareLoadBalancer继承了DynamicServerListLoadBalancer。

    ILoadBalancer接口的代码已经看过了,现在看下AbstractLoadBalancer的代码:

    public abstract class AbstractLoadBalancer implements ILoadBalancer {
    
        public enum ServerGroup{
            ALL,
            STATUS_UP,
            STATUS_NOT_UP        
        }
    
        /**
         * delegate to {@link #chooseServer(Object)} with parameter null.
         */
        public Server chooseServer() {
            return chooseServer(null);
        }
    
        /**
         * List of servers that this Loadbalancer knows about
         * 
         * @param serverGroup Servers grouped by status, e.g., {@link ServerGroup#STATUS_UP}
         */
        public abstract List<Server> getServerList(ServerGroup serverGroup);
    
        /**
         * Obtain LoadBalancer related Statistics
         */
        public abstract LoadBalancerStats getLoadBalancerStats();    
    }

    这是一个抽象类,里面加了一个枚举,增加了两个抽象方法。定义的chooseServer方法。

    下面再看BaseLoadBalancer类,BaseLoadBalancer类就算是负载均衡器的一个基础实现类,在里面可以看到定义了两个list:

    @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List<Server> allServerList = Collections
            .synchronizedList(new ArrayList<Server>());
    @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List<Server> upServerList = Collections
            .synchronizedList(new ArrayList<Server>());

    从名字上看,这就是维护所有服务的实例列表,和维护状态为up的实例列表。 
    而且还可以看到BaseLoadBalancer中实现的ILoadBalancer接口中的方法,比如下面这两个,获取可用的服务列表,就会把upServerList返回,获取所有的服务列表,就会把allServerList返回。

    @Override
    public List<Server> getReachableServers() {
        return Collections.unmodifiableList(upServerList);
    }
    
    @Override
    public List<Server> getAllServers() {
        return Collections.unmodifiableList(allServerList);
    }

    接下来,再看DynamicServerListLoadBalancer类。从类头上的注释可以知道,这个类可以动态的获取服务列表,并且利用filter对服务列表进行过滤。

    在DynamicServerListLoadBalancer类中,能看到定义了一个ServerList类型的serverListImpl字段,ServerList是一个接口,里面有两个方法:

    public interface ServerList<T extends Server> {
    
        public List<T> getInitialListOfServers();
    
        /**
         * Return updated list of servers. This is called say every 30 secs
         * (configurable) by the Loadbalancer's Ping cycle
         * 
         */
        public List<T> getUpdatedListOfServers();   
    
    }

    getInitialListOfServers是获取初始化的服务列表。 
    getUpdatedListOfServers是获取更新的服务列表。 
    ServerList有多个实现类,具体用的哪个呢,可以在EurekaRibbonClientConfiguration类中找到,这是Ribbon和Eureka结合的自动配置类,这里面有个方法:

    @Bean
    @ConditionalOnMissingBean
    public ServerList<?> ribbonServerList(IClientConfig config) {
        DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
                config);
        DomainExtractingServerList serverList = new DomainExtractingServerList(
                discoveryServerList, config, this.approximateZoneFromHostname);
        return serverList;
    }

    方法中先新建了一个DiscoveryEnabledNIWSServerList类型的对象,又把这个对象作为一个参数,创建了DomainExtractingServerList类型的对象,最终返回的是DomainExtractingServerList的实例对象。 
    查看DomainExtractingServerList中重写的这两个方法,发现还是调用的DiscoveryEnabledNIWSServerList中的方法。然后,进到DiscoveryEnabledNIWSServerList类中,看这两个方法的定义:

    @Override
    public List<DiscoveryEnabledServer> getInitialListOfServers(){
        return obtainServersViaDiscovery();
    }
    
    @Override
    public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
        return obtainServersViaDiscovery();
    }

    这两个方法,都是调用了obtainServersViaDiscovery这个方法:

    private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
        List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
    
        if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
            logger.warn("EurekaClient has not been initialized yet, returning an empty list");
            return new ArrayList<DiscoveryEnabledServer>();
        }
    
        EurekaClient eurekaClient = eurekaClientProvider.get();
        if (vipAddresses!=null){
            for (String vipAddress : vipAddresses.split(",")) {
                // if targetRegion is null, it will be interpreted as the same region of client
                List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
                for (InstanceInfo ii : listOfInstanceInfo) {
                    if (ii.getStatus().equals(InstanceStatus.UP)) {
    
                        if(shouldUseOverridePort){
                            if(logger.isDebugEnabled()){
                                logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
                            }
    
                            // copy is necessary since the InstanceInfo builder just uses the original reference,
                            // and we don't want to corrupt the global eureka copy of the object which may be
                            // used by other clients in our system
                            InstanceInfo copy = new InstanceInfo(ii);
    
                            if(isSecure){
                                ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
                            }else{
                                ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
                            }
                        }
                        DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr);
                        des.setZone(DiscoveryClient.getZone(ii));
                        serverList.add(des);
                    }
                }
                if (serverList.size()>0 && prioritizeVipAddressBasedServers){
                    break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
                }
            }
        }
        return serverList;
    }

    在这个方法中,就是通过eurekaClient去注册中心获取服务,将状态为up的服务实例封装成DiscoveryEnabledServer对象,然后放入列表返回,这就是获取服务列表的流程。 
    获取服务列表的流程知道了,那是如何触发去获取,如何更新服务列表的呢?再看DynamicServerListLoadBalancer类中的代码,有一段:

    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };

    ServerListUpdater是一个接口,在DynamicServerListLoadBalancer的构造函数中,创建了一个PollingServerListUpdater类的对象,为ServerListUpdater字段赋值。进入PollingServerListUpdater类看一下:

    @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
    
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }

    里面有个start方法,实现了Runnable接口,run方法里调用UpdateAction的doUpdate,之后再启动一个定时任务,执行这个方法。定时任务传入的两个时间参数:initialDelayMs和refreshIntervalMs,任务启动后一秒开始执行,并且每隔三十秒执行一次,用于刷新列表。

    看到这里,就可以大体了解了,构造DynamicServerListLoadBalancer实例的时候,就会启动一个定时任务了,一开始先获取服务列表,之后每隔三十秒获取一次。负载均衡时,就是通过负载均衡算法在实例列表中选择一个,发送请求。

    以上这就是Ribbon源码大体的流程。
     

    注:以上所有只做理论性的总结与分析,相关实战代码会在后面的博客中和github中逐步增加。

    参考书籍、文献和资料:

    【1】郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.

    【2】徐进,叶志远,钟尊发,蔡波斯等. 重新定义Spring Cloud. 北京:机械工业出版社. 2018.

    【3】https://blog.csdn.net/wo18237095579/article/details/83343915.

    【4】https://blog.csdn.net/a15514920226/article/details/78924483.

    【5】https://blog.csdn.net/xiaofeng10330111/article/details/85682513.

    【6】https://blog.csdn.net/luanlouis/article/details/83060310.

    【7】https://blog.csdn.net/chayangdz/article/details/82177917.

    展开全文
  • Ribbon.xmind

    2021-09-12 15:50:51
    Ribbon
  • 负载均衡Ribbon和Feign---SpringCloud

    万次阅读 2021-01-15 15:44:20
    负载均衡Ribbon和Feign Ribbon负载均衡(基于客户端) 6.1 负载均衡以及Ribbon Ribbon是什么? Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。 简单的说,Ribbon 是 Netflix 发布的...

    负载均衡Ribbon和Feign

    Ribbon负载均衡(基于客户端)

    6.1 负载均衡以及Ribbon

    Ribbon是什么?

    • Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具
    • 简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 的客户端组件提供一系列完整的配置项,如:连接超时、重试等。简单的说,就是在配置文件中列出 LoadBalancer (简称LB:负载均衡) 后面所有的及其,Ribbon 会自动的帮助你基于某种规则 (如简单轮询,随机连接等等) 去连接这些机器。我们也容易使用 Ribbon 实现自定义的负载均衡算法!

    Ribbon能干嘛?

    在这里插入图片描述

    • LB,即负载均衡 (LoadBalancer) ,在微服务或分布式集群中经常用的一种应用。
    • 负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高用)。
    • 常见的负载均衡软件有 Nginx、Lvs 等等。
    • Dubbo、SpringCloud 中均给我们提供了负载均衡,SpringCloud 的负载均衡算法可以自定义

    负载均衡简单分类

    • 集中式LB
      • 即在服务的提供方和消费方之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求通过某种策略转发至服务的提供方!
    • 进程式 LB
      • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
      • Ribbon 就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址!

    集成Ribbon

    springcloud-consumer-dept-80向pom.xml中添加Ribbon和Eureka依赖

    <!--Ribbon-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--Eureka: Ribbon需要从Eureka服务中心获取要拿什么-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    

    在application.yml文件中配置Eureka

    # Eureka配置
    eureka:
      client:
        register-with-eureka: false # 不向 Eureka注册自己
        service-url: # 从三个注册中心中随机取一个去访问
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    

    主启动类加上@EnableEurekaClient注解,开启Eureka

    //Ribbon 和 Eureka 整合以后,客户端可以直接调用,不用关心IP地址和端口号
    @SpringBootApplication
    @EnableEurekaClient //开启Eureka 客户端
    public class DeptConsumer_80 {
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumer_80.class, args);
        }
    }
    

    自定义Spring配置类:ConfigBean.java 配置负载均衡实现RestTemplate

    @Configuration
    public class ConfigBean {//@Configuration -- spring  applicationContext.xml
    
        @LoadBalanced //配置负载均衡实现RestTemplate
        @Bean
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    }
    

    修改conroller:DeptConsumerController.java

    //Ribbon:我们这里的地址,应该是一个变量,通过服务名来访问
    //private static final String REST_URL_PREFIX = "http://localhost:8001";
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
    

    数据库导出

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9eSa5VMs-1610696634599)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210115135531598.png)]

    使用Ribbon实现负载均衡

    流程图:

    在这里插入图片描述

    1.新建两个服务提供者Moudle:springcloud-provider-dept-8003、springcloud-provider-dept-8002

    2.参照springcloud-provider-dept-8001 依次为另外两个Moudle添加pom.xml依赖 、resourece下的mybatis和application.yml配置,Java代码

    3.启动所有服务测试(根据自身电脑配置决定启动服务的个数),访问http://eureka7001.com:7002/查看结果

    在这里插入图片描述

    测试访问http://localhost/consumer/dept/list 这时候随机访问的是服务提供者8003

    在这里插入图片描述

    再次访问http://localhost/consumer/dept/list这时候随机的是服务提供者8001

    在这里插入图片描述

    以上这种每次访问http://localhost/consumer/dept/list随机访问集群中某个服务提供者,这种情况叫做轮询,轮询算法在SpringCloud中可以自定义。

    自定义负载均衡

    在springcloud-provider-dept-80模块下的ConfigBean中进行配置,切换使用不同的规则

    @Configuration
    public class ConfigBean {//@Configuration -- spring  applicationContext.xml
    
        /**
         * IRule:
         * RoundRobinRule 轮询策略
         * RandomRule 随机策略
         * AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
         * RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
         */
        @Bean
        public IRule myRule() {
            return new RandomRule();//使用随机策略
            //return new RoundRobinRule();//使用轮询策略
            //return new AvailabilityFilteringRule();//使用轮询策略
            //return new RetryRule();//使用轮询策略
        }
    }
    

    也可以自定义规则,在myRule包下自定义一个配置类MyRule.java,注意:该包不要和主启动类所在的包同级,要跟启动类所在包同级

    在这里插入图片描述

    MyRule.java

    /**
     * @Auther: csp1999
     * @Date: 2020/05/19/11:58
     * @Description: 自定义规则
     */
    @Configuration
    public class MyRule {
    
        @Bean
        public IRule myRule(){
            return new MyRandomRule();//默认是轮询RandomRule,现在自定义为自己的
        }
    }
    

    主启动类开启负载均衡并指定自定义的MyRule配置类

    //Ribbon 和 Eureka 整合以后,客户端可以直接调用,不用关心IP地址和端口号
    @SpringBootApplication
    @EnableEurekaClient
    //在微服务启动的时候就能加载自定义的Ribbon类(自定义的规则会覆盖原有默认的规则)
    @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)//开启负载均衡,并指定自定义的规则
    public class DeptConsumer_80 {
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumer_80.class, args);
        }
    }
    

    自定义的规则(这里我们参考Ribbon中默认的规则代码自己稍微改动):MyRandomRule.java

    public class MyRandomRule extends AbstractLoadBalancerRule {
    
        /**
         * 每个服务访问5次则换下一个服务(总共3个服务)
         * <p>
         * total=0,默认=0,如果=5,指向下一个服务节点
         * index=0,默认=0,如果total=5,index+1
         */
        private int total = 0;//被调用的次数
        private int currentIndex = 0;//当前是谁在提供服务
    
        //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                return null;
            }
            Server server = null;
    
            while (server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                List<Server> upList = lb.getReachableServers();//获得当前活着的服务
                List<Server> allList = lb.getAllServers();//获取所有的服务
    
                int serverCount = allList.size();
                if (serverCount == 0) {
                    /*
                     * No servers. End regardless of pass, because subsequent passes
                     * only get more restrictive.
                     */
                    return null;
                }
    
                //int index = chooseRandomInt(serverCount);//生成区间随机数
                //server = upList.get(index);//从或活着的服务中,随机获取一个
    
                //=====================自定义代码=========================
    
                if (total < 5) {
                    server = upList.get(currentIndex);
                    total++;
                } else {
                    total = 0;
                    currentIndex++;
                    if (currentIndex > =upList.size()) {
                        currentIndex = 0;
                    }
                    server = upList.get(currentIndex);//从活着的服务中,获取指定的服务来进行操作
                }
                
                //======================================================
                
                if (server == null) {
                    /*
                     * The only time this should happen is if the server list were
                     * somehow trimmed. This is a transient condition. Retry after
                     * yielding.
                     */
                    Thread.yield();
                    continue;
                }
                if (server.isAlive()) {
                    return (server);
                }
                // Shouldn't actually happen.. but must be transient or a bug.
                server = null;
                Thread.yield();
            }
            return server;
        }
    
        protected int chooseRandomInt(int serverCount) {
            return ThreadLocalRandom.current().nextInt(serverCount);
        }
    
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
            // TODO Auto-generated method stub
        }
    }
    
    

    Feign负载均衡(基于服务端)

    7.1 Feign简介

    Feign是声明式Web Service客户端,它让微服务之间的调用变得更简单,类似controller调用service。SpringCloud集成了Ribbon和Eureka,可以使用Feigin提供负载均衡的http客户端

    只需要创建一个接口,然后添加注解即可~

    Feign,主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法

    1. 微服务名字 【ribbon】
    2. 接口和注解 【feign】

    Feign能干什么?

    • Feign旨在使编写Java Http客户端变得更容易
    • 前面在使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一个客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它 (类似以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。

    Feign默认集成了Ribbon

    • 利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

    Feign的使用

    1. 创建springcloud-consumer-fdept-feign模块

      在这里插入图片描述

      拷贝springcloud-consumer-dept-80模块下的pom.xml,resource,以及java代码到springcloud-consumer-feign模块,并添加feign依赖。

      <!--Feign的依赖-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-feign</artifactId>
          <version>1.4.6.RELEASE</version>
      </dependency>
      

      通过Ribbon实现:—原来的controller:DeptConsumerController.java

      /**
       * @Auther: csp1999
       * @Date: 2020/05/17/22:44
       * @Description:
       */
      @RestController
      public class DeptConsumerController {
      
          /**
           * 理解:消费者,不应该有service层~
           * RestTemplate .... 供我们直接调用就可以了! 注册到Spring中
           * (地址:url, 实体:Map ,Class<T> responseType)
           * <p>
           * 提供多种便捷访问远程http服务的方法,简单的Restful服务模板~
           */
          @Autowired
          private RestTemplate restTemplate;
      
          /**
           * 服务提供方地址前缀
           * <p>
           * Ribbon:我们这里的地址,应该是一个变量,通过服务名来访问
           */
      //    private static final String REST_URL_PREFIX = "http://localhost:8001";
          private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
      
          /**
           * 消费方添加部门信息
           * @param dept
           * @return
           */
          @RequestMapping("/consumer/dept/add")
          public boolean add(Dept dept) {
              // postForObject(服务提供方地址(接口),参数实体,返回类型.class)
              return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
          }
      
          /**
           * 消费方根据id查询部门信息
           * @param id
           * @return
           */
          @RequestMapping("/consumer/dept/get/{id}")
          public Dept get(@PathVariable("id") Long id) {
              // getForObject(服务提供方地址(接口),返回类型.class)
              return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
          }
      
          /**
           * 消费方查询部门信息列表
           * @return
           */
          @RequestMapping("/consumer/dept/list")
          public List<Dept> list() {
              return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
          }
      }
      

      通过Feign实现:—改造后controller:DeptConsumerController.java

      /**
       * @Auther: csp1999
       * @Date: 2020/05/17/22:44
       * @Description:
       */
      @RestController
      public class DeptConsumerController {
      
          @Autowired
          private DeptClientService deptClientService;
      
          /**
           * 消费方添加部门信息
           * @param dept
           * @return
           */
          @RequestMapping("/consumer/dept/add")
          public boolean add(Dept dept) {
              return deptClientService.addDept(dept);
          }
      
          /**
           * 消费方根据id查询部门信息
           * @param id
           * @return
           */
          @RequestMapping("/consumer/dept/get/{id}")
          public Dept get(@PathVariable("id") Long id) {
             return deptClientService.queryById(id);
          }
      
          /**
           * 消费方查询部门信息列表
           * @return
           */
          @RequestMapping("/consumer/dept/list")
          public List<Dept> list() {
              return deptClientService.queryAll();
          }
      }
      

      Feign和Ribbon二者对比,前者显现出面向接口编程特点,代码看起来更清爽,而且Feign调用方式更符合我们之前在做SSM或者SprngBoot项目时,Controller层调用Service层的编程习惯!

      主配置类

      /**
       * @Auther: csp1999
       * @Date: 2020/05/17/22:47
       * @Description:
       */
      @SpringBootApplication
      @EnableEurekaClient
      // feign客户端注解,并指定要扫描的包以及配置接口DeptClientService
      @EnableFeignClients(basePackages = {"com.haust.springcloud"})
      // 切记不要加这个注解,不然会出现404访问不到
      //@ComponentScan("com.haust.springcloud")
      public class FeignDeptConsumer_80 {
          public static void main(String[] args) {
              SpringApplication.run(FeignDeptConsumer_80.class, args);
          }
      }
      
    2. 改造springcloud-api模块

      pom.xml添加feign依赖

      <!--Feign的依赖-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-feign</artifactId>
          <version>1.4.6.RELEASE</version>
      </dependency>
      

      新建service包,并新建DeptClientService.java接口,

      package com.kuang.springcloud.controller;
      
      import com.kuang.springcloud.pojo.Dept;
      import com.kuang.springcloud.service.DeptClientService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.List;
      
      /**
       * @Auther: csp1999
       * @Date: 2020/05/17/22:44
       * @Description:
       */
      @RestController
      public class DeptConsumerController {
      
          @Autowired
          private DeptClientService deptClientService=null;
      
          /**
           * 消费方添加部门信息
           * @param dept
           * @return
           */
          @RequestMapping("/consumer/dept/add")
          public boolean add(Dept dept) {
              return deptClientService.addDept(dept);
          }
      
          /**
           * 消费方根据id查询部门信息
           * @param id
           * @return
           */
          @RequestMapping("/consumer/dept/get/{id}")
          public Dept get(@PathVariable("id") Long id) {
              return deptClientService.queryById(id);
          }
      
          /**
           * 消费方查询部门信息列表
           * @return
           */
          @RequestMapping("/consumer/dept/list")
          public List<Dept> list() {
              return deptClientService.queryAll();
          }
      }
      

    Feign VS Ribbon

    根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.

    Feign 本质上也是实现了 Ribbon,只不过后者是在调用方式上,为了满足一些开发者习惯的接口调用习惯!

    下面我们关闭springcloud-consumer-dept-80 这个服务消费方,换用springcloud-consumer-dept-feign(端口还是80) 来代替:(依然可以正常访问,就是调用方式相比于Ribbon变化了)

    在这里插入图片描述

    展开全文
  • 使用Feign调用接口分两层,ribbon的调用和hystrix的调用,所以ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间 #hystrix的超时时间 hystrix: command: default: execution: timeout: enabled: ...
  • 转载请标明出处: http://blog.csdn.net/forezp/article/details/69788938 本文出自方志朋的博客 在上一篇文章,讲了服务的注册和发现...Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种...
  • RibbonMenu

    2021-03-17 22:41:39
    RibbonMenu介绍: 实现弹出式侧边菜单。点击菜单上某个item,菜单自动缩回隐藏。
  • ribbon负载均衡

    万次阅读 2020-02-05 18:40:54
    简介 Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于...Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个S...
  • ribbon.zip

    2020-05-27 16:41:10
    因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来...
  • ribbon-subgraph:Ribbon Finance的子图
  • Ribbon使用

    2021-07-23 19:39:13
    1. 什么是Ribbon 是 Netflixfa 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中,Eureka一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从Eureka中读取到的服务...
  • SpringCloud-Ribbon负载均衡服务调用及手写Ribbon算法
  • RibbonControl

    2018-09-27 11:14:31
    DeveExpress中的RibbonControl的控件的简单应用,帮助快速掌握使用
  • Ribbon Workbench

    2015-05-12 11:13:20
    Ribbon Workbench for CRM2013/CRM2015 操作文档参照下面: http://ribbonworkbench.uservoice.com/knowledgebase/articles/71374-1-getting-started-with-the-ribbon-workbench
  • 初识Ribbon

    2020-07-27 15:03:06
    Ribbon 简介: Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端 负载均衡的工具。 Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon 客户端组件提供一系列...
  • Office Ribbon

    2016-08-03 09:17:50
    ribbon的雏形,通过配置文件的导入初始化ribbon菜单,同时可以定制相关控件
  • ribbon sample

    2015-02-28 13:38:40
    ribbon sample示例代码,microsoft word,outlook的风格
  • Ribbon简介

    2021-05-22 20:56:26
    文章目录Ribbon简介Ribbon的职能Ribbon负载均衡演示Ribbon核心组件IRule Ribbon简介 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。 简单地说,Ribbon是Netflix发布的开源项目,主要功能...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 74,543
精华内容 29,817
关键字:

ribbon