Spring Cloud教程 第二弹 客户端负载均衡Ribbon

更多Spring与微服务相关的教程请戳这里 Spring与微服务教程合集

1、浅谈负载均衡

负载均衡(Load Balance),是利用特定方式将流量分摊到多个操作单元上的一种手段。这个应该大家耳熟能详了!

负载均衡的分类:

  • 硬负载:即利用硬件进行负载均衡处理,如F5
  • 软负载:即利用软件进行负载均衡处理,如nginx

负载均衡的另一种分类:

  • 集中式负载(服务端负载):集中式负载位于因特网与服务提供者之间,如nginx、F5
  • 进程内负载(客户端负载):指从一个实例库(即服务注册中心)选取一个实例进行流量导入。这类负载的负载均衡器是类似与Ribbon的IPC(Inter-process communication,进程间通信 )组件

2、走进Ribbo世界

2.1、入门实战-搭建消费者工程

简介:

Ribbon是一个负载均衡客户端,在实际使用中,Ribbon相当于一个服务消费者的存在。因此,我下面会介绍如何搭建以Ribbon组件为基础的消费者工程,Eureka Server工程与服务提供者工程这里暂不介绍

2.1.1、pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    <relativePath/>
  </parent>

  <properties>
    <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
  </properties>

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

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

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

2.1.2、application.yml

注意:

这里只有Ribbon入门实战中暂时不涉及ribbon相关的配置,ribbon相关配置后续介绍

server:
  port: 8020
  servlet:
    context-path: /consumer-a
spring:
  application:
    name: consumer-a
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka-server/eureka/
    register-with-eureka: true
    fetch-registry: true

2.1.3、配置RestTemplate

RestTemplate是spring web模块提供的一个操作Rest接口的模板类,该类Ribbon似乎并没有任何关系

我们一直强调Ribbon是一个客户端负载均衡组件,正是@LoadBalanced注解让RestTemplate具有负载均衡能力,其背后的原理下面讲解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class ConsumerAApplication {

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

2.1.4、消费者方法

首先,服务提供者暴露一个返回自身端口号的接口,服务消费者消费该方法

关键代码:

restTemplate.getForObject("http://service-a/service-a/common/port", String.class);

本来URL应该是这样的:http://ip:port/service-a/common/port,但是由于Ribbon是从Eureka Server上拉取服务实例,因此这里可以将ip:port替换成服务名称。

而由于@LoadBalanced让RestTemplate具有负载均衡能力,所以当服务提供者有多个实例时(单机情况下,可以通过使用不同端口号来启动多个实例),每次返回的端口号也都不一样

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

@RestController
@RequestMapping("/common")
public class CommonController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/provider/port")
    public String getProviderPort(){
        //此处的第一个service-a等同与ip:port,第二个service-a是上下文
        String result = restTemplate.getForObject("http://service-a/service-a/common/port", String.class);
        return result;
    }

}

2.1.5、运行测试

首先看看Eureka Server的仪表盘

这里我服务消费者启动了一个实例,而服务提供者启动了多个实例,浏览器多次访问http://localhost:8020/consumer-a/common/provider/port  可以看到页面显示的端口号是变化的,这说明负载均衡生效

2.2、Ribbon核心接口

Ribbon核心接口共同定义了Ribbon的行为,是Ribbon的骨架。官方文档上指出了Ribbon的核心接口,下表显示了Spring Cloud Netflix默认为Ribbon提供的bean

Bean TypeBean Name Class Name 描述
IClientConfig  ribbonClientConfig  DefaultClientConfigImpl  定义配置
IRule  ribbonRule  ZoneAvoidanceRule  定义负载均衡策略
IPing  ribbonPing  DummyPing  定期Ping服务检查可用性
ServerList<Server>  ribbonServerListConfigurationBasedServerList定义服务列表
ServerListFilter<Server>   ribbonServerListFilterZonePreferenceServerListFilter  定义特定期望获取服务列表
ILoadBalancer   ribbonLoadBalancerZoneAwareLoadBalancer  定义负载均衡选择服务的核心方法
ServerListUpdater ribbonServerListUpdater PollingServerListUpdater为DynamicServerListLoadBalancer定义动态更新服务列表接口

2.3、Ribbon负载均衡策略

由于Ribbon的负载均衡策略是由IRule接口定义的,因此下面看一下IRule接口的继承关系

绿色表示接口,蓝色表示抽象类

IRule
        AbstractLoadBalancerRule (com.netflix.loadbalancer)
                ClientConfigEnabledRoundRobinRule (com.netflix.loadbalancer) 这个类看名字就知道实际上是用的RoundRobinRule
                        BestAvailableRule (com.netflix.loadbalancer) (1)最低并发策略
                        PredicateBasedRule (com.netflix.loadbalancer)
                                ZoneAvoidanceRule (com.netflix.loadbalancer) (2)区域回避策略
                                AvailabilityFilteringRule (com.netflix.loadbalancer) (3)可用过滤策略。过滤掉一直连接失败和存在高并发连接的server
                RoundRobinRule (com.netflix.loadbalancer) (4)轮询策略
                        WeightedResponseTimeRule (com.netflix.loadbalancer) (5)响应时间加权策略。根据server的响应时间分配权重,响应时间越长,权重越低。综合了网络磁盘IO各种因素,这些因素直接影响了响应时间
                        ResponseTimeWeightedRule (com.netflix.loadbalancer) 过时,不建议使用
                RandomRule (com.netflix.loadbalancer) (6)随机策略
                RetryRule (com.netflix.loadbalancer)  (7)重试策略

综上所属,Ribbon默认有7种负载均衡策略

IRule接口源码:

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}
  • choose方法主要实现负载均衡逻辑
  • 在调用过程中,Ribbon是通过ILoadBalancer来关联IRule的,ILoadBalancer的chooseServer方法会转换为调用IRule的choose方法。
  • AbstractLoadBalancerRule类将IRule接口与ILoadBalancer接口关联起来了

AbstractLoadBalancerRule类源码:

package com.netflix.loadbalancer;

import com.netflix.client.IClientConfigAware;

public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
    private ILoadBalancer lb;

    public AbstractLoadBalancerRule() {
    }

    public void setLoadBalancer(ILoadBalancer lb) {
        this.lb = lb;
    }

    public ILoadBalancer getLoadBalancer() {
        return this.lb;
    }
}

2.4、Ribbon核心工作原理源码解读

关于Ribbon核心工作原理的源码解读,请看这篇文章 Spring Cloud教程 第三弹 Ribbon工作原理

3、个性化配置

对于Ribbon的核心接口,都是按默认规则配置好的,比如负载均衡算法,默认采用的是ZoneAvoidanceRule 算法,如果我们想采用别的负载均衡算法呢,那么需要我们做一些个性化配置

3.1、全局个性化配置

3.1.1、方式一

只需要将个性化配置类打上@Configuration注解,并且让spring boot扫到它即可生效

import com.bobo.group.consumera.annotation.AVoidScan;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.NoOpPing;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class CustomRibbonConfig {
    @Bean
    public IRule ribbonRule(){
        System.out.println("初始化ribbonRule--RandomRule");
        return new RandomRule();
    }
    @Bean
    public IPing ribbonPing(){
        System.out.println("初始化ribbonPing--NoOpPing");
        return new NoOpPing();
    }
}

3.1.2、方式二

首先要做的事就是,避免让spring boot扫到CustomRibbonConfig ,通过@ComponentScan注解的excludeFilters属性实现

再通过@RibbonClients注解的defaultConfiguration属性实现全局配置

首先自定义一个注解

import java.lang.annotation.*;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AVoidScan {
}

@AVoidScan注解打在CustomRibbonConfig类上,然后再spring boot的启动类上分别打上@ComponentScan注解和@RibbonClients注解,部分代码如下所示

@AVoidScan
@Configuration
public class CustomRibbonConfig {
    //省略
}


@SpringBootApplication
@ComponentScan(excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AVoidScan.class})
})
@RibbonClients(defaultConfiguration = {CustomRibbonConfig.class})
public class ConsumerAApplication {
    //省略
}

3.2、针对单个服务源配置

3.2.1、方式一,基于注解

同样要做的事就是,避免让spring boot扫到CustomRibbonConfig,实现方式上面已经讲过了

再通过@RibbonClient注解针对单个服务源进行配置,如下所示

@RibbonClients(value = {
        @RibbonClient(name = "service-a",configuration = CustomRibbonConfig.class)
})

3.2.2、方式二,基于配置文件

格式为:<client>.ribbon.*       

*代表配置项的key,key在CommonClientConfigKey类中定义,而值在RibbonProperties中获取

比如我要配置负载均衡策略,配置细节如下:

service-a:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

当多种配置方式并存时,其优先级如下:

配置文件中定义的 > RibbonClient注解定义的 > 默认的

4、使用负载均衡API

除了通过RestTemplate进行负载均衡,还可以直接使用负载均衡客户端

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

@RestController
@RequestMapping("/common")
public class CommonController {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/provider/port")
    public String getProviderPort(){

        //直接使用LoadBalancerClient
        ServiceInstance instance = loadBalancerClient.choose("service-a");
        URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
        //省略
    }


}

5、Ribbon缓存机制

Ribbon并不是实时从eureka server上更新服务列表的,而是有一个缓冲时间,默认每30秒从eureka server上更新一次服务实例,可以通过如下配置项修改

  ribbon:
    ServerListRefreshInterval: 30000

6、不使用Eureka

ribbon默认是结合eureka使用的,ribbon会定期从eureka server上拉取实例列表

ribbon同样支持不使用eureka,如何实现呢?

  1. 首先要做的是去除eureka client的依赖
  2. 去掉@EnableEurekaClient注解
  3. 关闭eureka
  4. 在配置文件中针对单个服务源手动配置实例列表
service-a:
  ribbon:
    listOfServers: http://localhost:8010,http://localhost:8011,http://localhost:8012,http://localhost:8013

ribbon:
  eureka:
    #关闭eureka
    enabled: false

7、开启Ribbon的饥饿加载

Ribbon在进行客户端负载均衡时并不是启动时就加载上下文,而采用了懒加载,即第一次请求的时候才会去加载,这样会导致第一次请求发生超时

初始化方法是RibbonApplicationContextInitializer.initialize方法

如何开启饥饿加载?如下所示

ribbon:
  eager-load:
    clients: service-a
    enabled: true

8、Ribbon的超时与重试机制

  • 对于重试机制:如果ribbon在重试期间,时间超过了hystrix的超时时间,则会熔断。因此ribbon的重试时间 必须要小于 hystrix超时时间,否则重试没有意义
  • 对于OkToRetryOnAllOperations,false表示只会对get请求重试。而true表示所有请求都重试。true要慎用,如果接口没有做幂等性,重试可能会导致不良的后果
  • 默认情况下,GET方式请求无论是连接异常还是读取异常,都会进行重试,非GET方式请求,只有连接异常时,才会进行重试
ribbon:
  ReadTimeout: 3000 #业务逻辑超时时间
  ConnectTimeout: 3000 #连接超时时间
  MaxAutoRetries: 1 # 同一台实例最大重试次数,不包括首次调用,如果不配置ribbon的重试次数,默认会重试一次
  MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用
  OkToRetryOnAllOperations: false #是否所有请求都重试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

波波老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值