微服务(Spring Cloud)——负载均衡

一、概述

负载均衡
组件库: Spring Cloud Netflix 组件库
组件: Ribbon

二、负载均衡介绍

(一)、什么是负载均衡
负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。—— 百度百科

(二)、微服务中客户端负载均衡模式和服务端负载均衡模式之间如何选择
1.客户端负载均衡模式
微服务(Spring Cloud)——负载均衡_第1张图片
使用背景: 客户端在注册时,能通过Eureka的服务发现获取微服务集群中的服务节点列表和
节点状态。
前提: 1.机器列表要保存在客户端;2.机器列表要能够及时的动态更新,以便于获取节点的最新状态。
使用: 有了上诉背景和前提条件,客户端本地就能实现负载均衡策略了

2.服务端实现负载均衡策略
微服务(Spring Cloud)——负载均衡_第2张图片
使用背景: 使用背景同样是微服务(spring cloud)集群服务节点
前提:与客户端负载均衡策略不同的是,服务端负载均衡策略需要由中间件来实现,例如Nginx、F5。
使用: 有了上诉背景和前提条件,就可以使用服务端负载均衡策略了。

3.spring cloud中两种负载均衡模式的比较

  • List item
客户端负载均衡模式 服务端负载均衡模式
开发团队灵活修改 一般位于网关层,由NetOpts把控修改
运维成本低 运维成本较高
非常依赖注册中心 通常不依赖
一般仅适用于微服务框架 适用于多种环境,比如Tomcat,Jboss等

4.总结
一般而言,大型应用通常是客户端+服务端负载均衡搭配来使用的。

三、深入Ribbon

(一)、Ribbon实现负载均衡策略和原理,加载方式,IPing机制
1.特点:
组件库丰富:整套负载均衡由7个具体的策略组成,无论你是什么特殊需求,都有合适的策略供你选择
适配性强:给谁都能用,跟多个组件都能搭配,能跟Sprig Cloud里的多个组件(eureka、feign、gateway、zuul、hystrix)搭配使用;
此外,Ribbon可以脱离SpringCloud应用在一般项目中

2.Ribbon的基本工作模式(IPing+IRule)
微服务(Spring Cloud)——负载均衡_第3张图片
当Eureka接到Http请求时,此时Eureka虽然拥有所有服务节点的信息,但它不知道该如何选择才能满足负载均衡,所以Eureka将请求转到Ribbon。

  • IPing:IPing是Ribbon的healthcheck机制,检查目标节点是否还在线,一般不会主动向服务节点发起healthcheck,而是Ribbon后台静默处理。
  • IRule: Ribbon 组件库,各种负载均衡策略都继承自IRule接口。所有经过Ribbon的请求,都会先请求IRule,由IRule通过负载均衡策略选择指定的目标机器。
    3.Ribbon负载均衡七龙珠之底层七种负载均衡策略
    Ribbon的LoadBalancer的更底层内置了7中负载均衡策略
    第一种:随机策略(RandomRule),自然是随性而为,随机选择服务名称对应的服务节点。
    第二种:轮询策略(RoundRobinRule),一个一个来,谁不用急,底层使用了CAS+自旋锁的方式来保证线程安全。
    第三种:重试策略(RetryRule),一次不行再来第二次,使用了类似于装饰器设计模式,相当于重试+实际模式的方式,所以需要指定真正被执行的负载均衡策略。
    第四种:权重策略(WeightedResponseTimeRule),谁响应快,那么谁被选中的几率就高,该规则会自动根据服务节点的响应时间来计算权重,响应快则权重高。该Rule继承自RoundRobinRule,所以服务器刚启动时采用轮询策略,当权重数据积累足够后才使用WeightedResponseTimeRule模式。
    第五种:空闲策略(BestAvailableRule),谁最闲谁先来,在过滤掉故障服务后,选择过去30分钟类并发量最小的服务节点。当然服务器刚启动时,统计结果未生成时,依然使用轮询的方式。
    第六种:下限策略(AvailabilityFilteringRule),基于RoundRobinRule来选择服务节点,但必须满足它的最低要求,否在不予采纳,10次以内选择种为止。
    第七种:自主策略(ZoneAvoidanceRule),以机房大区(Zone)和可用性作为过滤条件,选择可用的服务节点。满足大区要求,且服务可用且并发量不大的节点才会被选中。

(二)、源码解析-LoadBalancer底层机制&可扩展性
1.通过源码解读7中负载均衡策略

RandomRule
实现关键:
微服务(Spring Cloud)——负载均衡_第4张图片
RoundRobinRule
实现关键:
微服务(Spring Cloud)——负载均衡_第5张图片
BestAvailableRule
实现关键:
父类是基于RoundRobinRule来实现的
微服务(Spring Cloud)——负载均衡_第6张图片

RetryRule
实现关键:
RetryRule只是在底层实际策略上加了一层重试机制,下图的“answer”就是实际的策略,默认使用RoundRobinRule,但是可以替换的。
微服务(Spring Cloud)——负载均衡_第7张图片

2.了解ribbon中的自旋锁(cas)
微服务(Spring Cloud)——负载均衡_第8张图片
无限循环 + 退出条件

for (;;) {
    int current = nextServerCyclicCounter.get();
    int next = (current + 1) % modulo;
    if (nextServerCyclicCounter.compareAndSet(current, next))
        return next;
}

对于 nextServerCyclicCounter.compareAndSet(current, next) 解释;
这表示我当前拿到的这个current变量在内存中没有被改变过,则可以设置为+1后的值
官方解释:
1.取得当前值
2.计算+1后的值
3.如果当前值还有效(没有被)的话设置那个+1后的值
4.如果设置没成功(当前值已经无效了即被别的线程改过了), 再从1开始.

3.编程好习惯之防御性编程
假定所有的输入项都是不安全的,对所有不安全的可能加以判断
例如:RandomRule中的参数判断和线程状态判断
微服务(Spring Cloud)——负载均衡_第9张图片
4.负载均衡器LoadBalancer原理解析
Ribbon中一共用7中负载均衡策略,那么我们在调用的过程中,由谁来调用这些具体的策略呢?
**答案:**关键就在于Ribbon对RestTemplate的改造。
关键: @LoadBalancer注解

微服务(Spring Cloud)——负载均衡_第10张图片
由@LoadBalancer注解标记RestTemplate,然后Ribbon将带有负载均衡功能的拦截器注入标记好的RestTemplate中,以此来实现负载均衡。

5.IPing机制
IPing机制是一种主动机制,他能主动判断当前服务器节点的状态,并决定该节点是否可作为目标节点,只用可用的节点才会作为负载均衡的目标节点。
三种机制:
Dummy:默认返回true,即认为所有节点都是可用的
NIWSDiscoveryPing:借助Eureka服务发现机制获取节点状态,节点状态为UP则认为节点可用
PingUrl: 主动向服务节点发起HTTP调用,如节点响应,才认为节点可用。
在与Eureka组件搭配使用时,默认使用NIWSDiscoveryPing机制,以此减少服务节点负担。

(三)、工作中如何选择负载均衡策略
Ribbon 一共提供了七种负载均衡策略,默认使用的RoundRobinRule(轮询)策略,那么我们该如如何选择呢?
提前说一下,如非必要,不用更改负载均衡策略,除非有相应的业务要求或者其他情况。
1.连接数敏感模型
当正常负载下,连接数与业务复杂度是非线性相关的接口,可以采用基于可用连接数的负载均衡策略(BestAvailableRule)。通俗的讲就是接口本身处理业务的时间不会因参数的变化而有较大的波动的接口

2.RT数敏感模型
当正常负载下,就是接口本身处理业务的时间会因参数的变化而有较大的波动的接口,可以采用基于服务响应时间的负载均衡策略(WeightedResponseTimeRule)

3.成功率敏感模型
当正常负载下,业务对于响应时间没有太高的要求,但是对成功率要求很高的系统,比如我曾经历过的报文系统,他对响应时间并没有太高的要求,但要求报文发送一定要成功,这种可以采用重试策略(RetryRule)

(四)、Ribbon Demo演示
1.创建ribbon-consumer
总览:
微服务(Spring Cloud)——负载均衡_第11张图片

依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-demo2</artifactId>
        <groupId>com.lys</groupId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>ribbon-consumer</artifactId>
    <packaging>jar</packaging>
    <name>ribbon-consumer</name>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

</project>

启动类:

package com.lys.springcloud;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/2/4
 * Time: 22:44
 * Description: No Description
 */
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonConsumerApplication {

    @Bean  //相当于xml中的bean标签,主要是用于调用当前方法获取到指定对象
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder(RibbonConsumerApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }

}


用于测试的Controller

package com.lys.springcloud;

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

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/3/7
 * Time: 22:19
 * Description: No Description
 */
@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/hello")
    public  String hello() {
        return restTemplate.getForObject("http://eureka-client/hello", String.class);
    }

}

2.启动多个应用
(1)启动注册中心
(2)启动两个eureka-client(服务节点)
eureka-client是本次验证负载均衡时需要访问的服务节点,其中需要有接收请求的controller,如果大家想用本例来进行验证,请创建一个eureka-client,并使用不同的端口(除端口外,其他配置尽量保持一致)启动两次,然后再进行访问验证。因为篇幅原因就不演示怎么创建服务节点了。不过在我的其他文章中详细讲述了怎么搭建注册中心和创建服务提供者,详情参见:spring cloud 初体验之服务注册Eureka
(3)启动本次Demo猪脚——ribbon-Consumer
微服务(Spring Cloud)——负载均衡_第12张图片
在注册中心查看各个应用是否注册成功:
微服务(Spring Cloud)——负载均衡_第13张图片

3.访问测试
不断发起调用检查是否具有负载均衡效果
微服务(Spring Cloud)——负载均衡_第14张图片

4.将负载均衡策略应用到全局或指定服务
Ribbon默认使用的是RoundRobinRule(轮询策略)

  • 全局配置
    如何改变默认的负载均衡策略
    在ribbon-consumer项目中创建配置类 RibbonConfiguration,创建完成后依然启动注册中心,启动多个eureka-client,再启动ribbon-consumer不断访问测试,检查设置是否生效。
    RibbonConfiguration.java文件如下:
package com.lys.springcloud;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/3/10
 * Time: 23:06
 * Description: No Description
 */
@Configuration
public class RibbonConfiguration {

    @Bean
    public IRule defaultLBStrategy() {
        //指定默认策略为随机策略,如不指定,系统默认策略是 RoundRobinRule(轮询策略)
        return new RandomRule();
    }
}

  • 指定服务配置
    相对于全局配置,指定服务配置优先级更高
    方式一:
    配置文件方式微服务(Spring Cloud)——负载均衡_第15张图片
#指定服务配置 key格式:需要应用该策略的服务名 + .ribbon.NFLoadBalancerRuleClassName;
#例如:eureka-client.ribbon.NFLoadBalancerRuleClassName
eureka-client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

方式二:
注解方式

package com.lys.springcloud;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/3/10
 * Time: 23:06
 * Description: No Description
 */
@Configuration
@RibbonClient(name = "eureka-client", configuration = {com.netflix.loadbalancer.RandomRule.class})
public class RibbonConfiguration {

//    @Bean
//    public IRule defaultLBStrategy() {
//        //指定默认策略为随机策略,如不指定,系统默认策略是 RoundRobinRule(轮询策略)
//        return new RandomRule();
//    }
}

使用Ribbon接口TimeOut怎么破?

现象:
总有些奇怪的问题发生在程序员的周遭,我们通常称之为”玄学“,也通常甩锅给环境
实际上,真正的环境问题其实是比较少见的,对于发生在ribbon身上的玄学——接口访问超时,很有可能是Ribbon中的LoadBalancer懒加载导致的,当你的接口方法本身耗时较长,而Ribbon又是在请求时才加载LoadBalancer,此时就极有可能导致接口超时,我们仔细观察log可以发现初始化时间发生在首次超时前。图示:
微服务(Spring Cloud)——负载均衡_第16张图片
解决:
知道原因还不够,怎么破?一手配置搞定
配置Ribbon中LoadBalancer使用饥饿加载模式
微服务(Spring Cloud)——负载均衡_第17张图片

ribbon.eager-load.enabled=true
ribbon.eager-load.clients=ribbon-consumer

此时我们发现,系统启动后Ribbon中LoadBalancer就直接被加载了。
微服务(Spring Cloud)——负载均衡_第18张图片

你可能感兴趣的:(spring,cloud,ribbon,负载均衡)