三、SpringCloud Alibaba之Ribbon

3.1、负载均衡介绍

单台服务器不管好好坏总是会遇到性能瓶颈,而解决单台服务器瓶颈方式就是采用集群。高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的计算能力。在一个集群中,每一台服务器提供的功能是一样,但是每一台服务器的类型可能是不一样的(性能、配置、价格等等),因此需要设计合理的任务分配策略,将计算任务分配到多台服务器上执行,并且保证在每一台服务器上输入相同的参数,输出的结果一样。

高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法。对于任务分配器,现在更流行的通用叫法是“负载均衡器”

 

目前主流的负载方案分为以下两种:

服务端负载均衡:在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。

客户端负载均衡:根据自己的请求情况做负载均衡,在服务调用者这一端进行负载均衡, 服务调用者存有服务提供者的ip、端口等信息,根据一定的负载均衡策略,服务调用者自己决定调用哪个ip上的服务。

3.2、Ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

简单地说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单地说,就是在配置文件中列出Load Balancer(简称LB)后面所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是负载均衡,和硬件负载均衡F5不同,它的负载均衡是基于客户端的,Zuul网关和Feign可以通过Ribbon轻松的实现服务的负载均衡,同时避免了与业务无关的冗余代码。

3.2.1、Ribbon的使用入门

客户端负载均衡器:https://cloud.spring.io/spring-cloud-netflix/1.0.x/

Client Side Load Balancer: 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.

A central concept in Ribbon is that of the named client. Each load balancer is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer (e.g. using the @FeignClient annotation). Spring Cloud creates a new ensemble as an ApplicationContext on demand for each named client using RibbonClientConfiguration. This contains (amongst other things) an ILoadBalancer, a RestClient, and a ServerListFilter.

Ribbon 是一个客户端负载平衡器,可以让你对 HTTP 和 TCP 客户端的行为进行大量控制。Feign 已经使用了 Ribbon,因此如果你正在使用 @FeignClient,本节也同样适用。

Ribbon 的一个核心概念是命名客户端。每个负载平衡器都是一个组件集合的一部分,这些组件协同工作,按需联系远程服务器,而这个组件集合有一个应用程序开发人员赋予它的名称(例如使用 @FeignClient 注解)。Spring Cloud 会使用 RibbonClientConfiguration 为每个被命名的客户端创建一个新的应用程序上下文(ApplicationContext)。其中包括 ILoadBalancer、RestClient 和 ServerListFilter。

 

Customizing the Ribbon Client

You can configure some bits of a Ribbon client using external properties in <client>.ribbon.*, which is no different than using the Netflix APIs natively, except that you can use Spring Boot configuration files. The native options can be inspected as static fields in CommonClientConfigKey (part of ribbon-core).

Spring Cloud also lets you take full control of the client by declaring additional configuration (on top of the RibbonClientConfiguration) using @RibbonClient. Example:

您可以使用 <client>.ribbon.* 中的外部属性配置 Ribbon 客户端的某些部分,这与本机使用 Netflix API 并无不同,只是您可以使用 Spring Boot 配置文件。本机选项可以作为 CommonClientConfigKey(ribbon-core 的一部分)中的静态字段进行检查。

通过使用 @RibbonClient 声明附加配置(在 RibbonClientConfiguration 的基础上),Spring Cloud 还能让你完全控制客户端。示例

 

@Configuration
@RibbonClient(name = "foo", configuration = FooConfiguration.class)
public class TestConfiguration {
}

 

@Configuration
public class FooConfiguration {
    @Bean
    public IPing ribbonPing(IClientConfig config) {
        return new PingUrl();
    }
}

 

In this case the client is composed from the components already in RibbonClientConfiguration together with any in FooConfiguration (where the latter generally will override the former).

在这种情况下,客户端由 RibbonClientConfiguration 中的组件和 FooConfiguration 中的组件组成(后者通常会覆盖前者)。

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

FooConfiguration 必须是 @Configuration,但要注意它不在主应用程序上下文的 @ComponentScan 中,否则它将被所有 @RibbonClients 共享。如果使用 @ComponentScan(或 @SpringBootApplication),则需要采取措施避免将其包含在内(例如,将其放在一个单独的、不重叠的包中,或在 @ComponentScan 中明确指定要扫描的包)。

 

首先服务消费者端引入Ribbon的依赖,如果已经引入了spring-cloud-starter-alibaba-nacos-discovery,Ribbon的依赖就引入了。

然后在配置类中配置RestTemplate,因为调用远程服务是通过RestTemplate调用的,而负载均衡是Ribbon完成的,因此需要对RestTemplate进行代理。

/**
     * 注入 RestTemplate
     * @param builder
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        RestTemplate restTemplate=builder.build();
        return restTemplate;
    }

在启动类中调用就行:

@RestController
@RequestMapping("/ribbon")
public class RibbonServiceController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/test")
    public String testRibbon(){
        String forObject = restTemplate.getForObject("http://stock-service/stock/getStock", String.class);
        return forObject;
    }
}

 

3.2.2、Ribbon负载均衡策略

 

Ribbon的负载均衡策略的继承图如下:

IRule

IRule这是所有负载均衡策略的父接口,里边的核心方法就是choose方法,用来选择一个服务实例。

 


package com.netflix.loadbalancer;

/**
 * 为负载均衡器定义负载均衡时使用的规则的接口
 * @author stonse
 * 
 */
public interface IRule{
    /*
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */
    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

Ribbon内置的负载均衡策略

 

负载均衡策略 解释
AbstractLoadBalancerRule AbstractLoadBalancerRule是一个抽象类,里边主要定义了一个ILoadBalancer,这里定义它的目的主要是辅助负责均衡策略选取合适的服务端实例。
RandomRule RandomRule看名字就知道,这种负载均衡策略就是随机选择一个服务实例,看源码我们知道,在RandomRule的无参构造方法中初始化了一个Random对象,然后在它重写的choose方法又调用了choose(ILoadBalancer lb, Object key)这个重载的choose方法,在这个重载的choose方法中,每次利用random对象生成一个不大于服务实例总数的随机数,并将该数作为下标所以获取一个服务实例。
RoundRobinRule RoundRobinRule这种负载均衡策略叫做线性轮询负载均衡策略。这个类的choose(ILoadBalancer lb, Object key)函数整体逻辑是这样的:开启一个计数器count,在while循环中遍历服务清单,获取清单之前先通过incrementAndGetModulo方法获取一个下标,这个下标是一个不断自增长的数先加1然后和服务清单总数取模之后获取到的(所以这个下标从来不会越界),拿着下标再去服务清单列表中取服务,每次循环计数器都会加1,如果连续10次都没有取到服务,则会报一个警告No available alive servers after 10 tries from load balancer: XXXX。
RetryRule RetryRule(在轮询的基础上进行重试)看名字就知道这种负载均衡策略带有重试功能。首先RetryRule中又定义了一个subRule,它的实现类是RoundRobinRule,然后在RetryRule的choose(ILoadBalancer lb, Object key)方法中,每次还是采用RoundRobinRule中的choose规则来选择一个服务实例,如果选到的实例正常就返回,如果选择的服务实例为null或者已经失效,则在失效时间deadline之前不断的进行重试(重试时获取服务的策略还RoundRobinRule中定义的策略),如果超过了deadline还是没取到则会返回一个null。
WeightedResponseTimeRule WeightedResponseTimeRule是RoundRobinRule的一个子类,在WeightedResponseTimeRule中对RoundRobinRule的功能进行了扩展,WeightedResponseTimeRule中会根据每一个实例的运行情况来给计算出该实例的一个权重,然后在挑选实例的时候则根据权重进行挑选,这样能够实现更优的实例调用。WeightedResponseTimeRule中有一个名叫DynamicServerWeightTask的定时任务,默认情况下每隔30秒会计算一次各个服务实例的权重,权重的计算规则也很简单,如果一个服务的平均响应时间越短则权重越大,那么该服务实例被选中执行任务的概率也就越大
NacosRule 根据nacos中配置的实例的权重配合使用。
ClientConfigEnabledRoundRobinRule ClientConfigEnabledRoundRobinRule选择策略的实现很简单,内部定义了RoundRobinRule,choose方法还是采用了RoundRobinRule的choose方法,所以它的选择策略和RoundRobinRule的选择策略一致,不赘述。
BestAvailableRule BestAvailableRule继承自ClientConfigEnabledRoundRobinRule,它在ClientConfigEnabledRoundRobinRule的基础上主要增加了根据loadBalancerStats中保存的服务实例的状态信息来过滤掉失效的服务实例的功能,然后顺便找出并发请求最小的服务实例来使用。然而loadBalancerStats有可能为null,如果loadBalancerStats为null,则BestAvailableRule将采用它的父类即ClientConfigEnabledRoundRobinRule的服务选取策略(线性轮询)。
ZoneAvoidanceRule

ZoneAvoidanceRule(默认规则,复合判断server所在区域的性能和server的可用性选择服务器。)ZoneAvoidanceRule是PredicateBasedRule的一个实现类,只不过这里多一个过滤条件,ZoneAvoidanceRule中的过滤条件是以ZoneAvoidancePredicate为主过滤条件和以AvailabilityPredicate为次过滤条件组成的一个叫做CompositePredicate的组合过滤条件,过滤成功之后,继续采用线性轮询(RoundRobinRule)的方式从过滤结果中选择一个出来。

 

3.2.3、更改默认的负载均衡策略

方法的名字必须时iRule

@Configuration
public class RibbonConfig {

    /**
     * 全局配置
     * 指定负载均衡策略,方法的名字必须时iRule
     *@return
     */
    @Bean
    public IRule iRule(){
        return new NacosRule();
    }
}

注意:此处有坑。不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方,否则自定义的配置类就会被所有的 RibbonClients共享

在启动类那里配置调用特定的服务时应该使用哪种策略

package com.java.coder.ribbon;

import com.java.coder.config.RibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
// 代表在调用stock-service时,负载均衡策略采用RibbonConfig配置类中的配置
@RibbonClient(name = "stock-service",configuration= RibbonConfig.class)
public class RibbonApplication {

    public static void main(String[] args) {
        SpringApplication springApplication=new SpringApplication();
        springApplication.run(RibbonApplication.class,args);
    }

    /**
     * 注入 RestTemplate
     * @param builder
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        RestTemplate restTemplate=builder.build();
        return restTemplate;
    }
}

如果有多个配置,可以采取数组形式配置

@RibbonClients(value = {
        @RibbonClient(name="xxx-service",configuration = XXX.class),
        @RibbonClient(name="yyy-service",configuration = YYY.class)
})

通过配置文件更改

除了通过配置类指定负载均衡策略 ,还可以通过配置文件来指定(推荐方式)。

#被调用的服务提供方的名称
stock-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

此处配置的类是负载均衡的实现类

3.2.4、自定义负载均衡策略

定义一个继承了AbstractLoadBalancerRule的类。

package com.java.coder.ribbon.config;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class CustomRule extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public Server choose(Object key) {
        // 获取父类中的loadBalancer
        ILoadBalancer loadBalancer = getLoadBalancer();

        // 获取当前服务下有可用的服务实例
        List<Server> serverList = loadBalancer.getAllServers();
        // 生产一个随机值,随机值的范围是serverList.size()返回的值
        int i = ThreadLocalRandom.current().nextInt(serverList.size());
        return serverList.get(i);
    }
}

 

在yml文件中指定自定义的类

#被调用的服务提供方的名称
stock-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.java.coder.ribbon.config.CustomRule

 

3.2.5、饥饿加载(eager-load)模式

在进行服务调用的时候,如果网络情况不好,第一次调用会超时。是因为Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端,如果让Ribbon第一次就去加载并创建客户端,可以通过配置文件配置

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

参数说明:

ribbon.eager-load.enabled:开启Ribbon的饥饿加载模式

ribbon.eager-load.clients:指定需要饥饿加载的客户端名称、服务名

 

3.2.6、使用LoadBalance替换ribbon

Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器, 用来替代Ribbon。Spring官方提供了两种负载均衡的客户端:

RestTemplate:RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。默认情况下,RestTemplate默认依赖jdk的HTTP连接工具。

WebClient:WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。

RestTemplate整合LoadBalancer

<!-- LoadBalancer -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-loadbalancer</artifactId>
 </dependency>

<!-- 提供了RestTemplate支持 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
<!-- nacos服务注册与发现 移除ribbon支持-->
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
     <exclusions>
         <exclusion>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
         </exclusion>
     </exclusions>
</dependency>

注意: nacos-discovery中引入了ribbon,需要移除ribbon的包

如果不移除,也可以在yml中配置不使用ribbon

spring:
  application:
    name: mall-user-loadbalancer-demo
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # 不使用ribbon
    loadbalancer:
      ribbon:
        enabled: false

 

原理:默认情况下,如果同时拥有RibbonLoadBalancerClient和BlockingLoadBalancerClient,

为了保持向后兼容性,将使用RibbonLoadBalancerClient。要覆盖它,可以设置spring.cloud.loadbalancer.ribbon.enabled属性为false。

3.2.6、Ribbon原理

@LoadBalanced注解

/**
     * 注入 RestTemplate
     * @param builder
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        RestTemplate restTemplate=builder.build();
        return restTemplate;
    }

@ConditionalOnClass注解

@ConditionalOnBean注解

 

https://blog.csdn.net/wk19920726/article/details/107721584

https://www.jianshu.com/p/1bd66db5dc46

https://www.jianshu.com/p/5a64abb67229

https://blog.csdn.net/zero__007/article/details/83720972

https://juejin.cn/post/7054916397893156894

https://www.hxstrive.com/article/948.htm

https://zhuanlan.zhihu.com/p/31744266

https://blog.csdn.net/zhawengan/article/details/121042628

文档:02-1 微服务负载均衡器LoadBalancer实?..

链接:http://note.youdao.com/noteshare?id=36adba6814ddc01d62363c2a54595a00&sub=E37EABF2A6A946ABBF4F676793FCD780

posted @ 2024-01-25 20:10  阿瞒123  阅读(21)  评论(0编辑  收藏  举报