定制软件【云原生&微服务五】Ribbon负载均衡策略之随机ThreadLocalRandom

文章目录

一、前言

定制软件在前面的系列文章:

定制软件我们聊了以下问题:

  1. 为什么给RestTemplate定制软件类上加上了@LoadBalanced定制软件注解就可以使用Ribbon定制软件的负载均衡?
  2. SpringCloud定制软件是如何集成Ribbon的?
  3. Ribbon定制软件如何作用到RestTemplate上的?
  4. 定制软件如何获取到Ribbon的ILoadBalancer?
  5. ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client定制软件获取到对应注册表?
  6. ZoneAwareLoadBalancer定制软件如何持续从Eureka定制软件中获取最新的注册表信息?
  7. 定制软件如何根据负载均衡器ILoadBalancer从Eureka Client获取到的List<Server>定制软件中选出一个Server?
  8. Ribbon定制软件如何发送网络HTTP请求?
  9. Ribbon如何用IPing定制软件机制动态检查服务实例是否存活?

定制软件本篇文章我们继续看Ribbon定制软件定制软件内置了哪些负载均衡策略?RandomRule定制软件负载均衡策略的算法是定制软件如何实现的?

PS:Ribbon依赖Spring Cloud定制软件版本信息如下:

<dependencyManagement>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-dependencies</artifactId>            <version>2.3.7.RELEASE</version>            <type>pom</type>            <scope>import</scope>        </dependency>        <!--整合spring cloud-->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-dependencies</artifactId>            <version>Hoxton.SR8</version>            <type>pom</type>            <scope>import</scope>        </dependency>        <!--整合spring cloud alibaba-->        <dependency>            <groupId>com.alibaba.cloud</groupId>            <artifactId>spring-cloud-alibaba-dependencies</artifactId>            <version>2.2.5.RELEASE</version>            <type>pom</type>            <scope>import</scope>        </dependency>    </dependencies></dependencyManagement>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

二、Ribbon内置了哪些?

  1. RandomRule --> 随机选择一个Server

  2. RoundRobinRule --> 轮询选择,轮询Index,选择index对应位置的Server,请求基本平摊到每个Server上。

  3. WeightedResponseTimeRule --> 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。

  4. ZoneAvoidanceRule --> 综合判断Server所在Zone的性能和Server的可用性选择server,在没Zone的环境下,类似于轮询(RoundRobinRule)。默认策略

  5. BestAvailableRule --> 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。

  6. RetryRule --> 对选定的负载均衡策略上 重试机制,在一个配置时间段内选择Server不成功,就一直尝试使用subRule(默认是RoundRobinRule)的方式选择一个可用的Server。

  7. AvailabilityFilteringRule --> 过滤掉一直连接失败的(被标记为circuit tripped的)的Server,并过滤掉那些高并发的后端Server 或者 使用一个AvailabilityPredicate来定义过滤Server的逻辑,本质上就是检查status里记录的各个Server的运行状态;其具体逻辑如下:

    先用round robin算法,轮询依次选择一台server,如果判断这个server是否是存活的、可用的,如果这台server是不可以访问的,那么就用round robin算法再次选择下一台server,依次循环往复10次,还不行,就走RoundRobin选择。

三、 --> RandomRule

我们知道Ribbon负载均衡算法体现在IRule的choose(Object key)方法中,而choose(Object key)方法中又会调用choose(ILoadBalancer lb, Object key)方法,所以我们只需要看各个IRule实现类的choose(ILoadBalancer lb, Object key)方法;

PS:allList和upList的一些疑问和解惑!

最近和一个大V聊了一下RandomRule中Server的选择,随机的下标是以allList的size为基数,而Server的选择则是拿到随机数以upList为准;当时我们考虑极端情况可能存在越界问题!

当天晚上博主又追了一下Ribbon的整个执行流程,结论如下:

  1. upList和allList是Ribbon维护在自己内存的,在服务启动时会从服务注册中心把服务实例信息拉到upList和allList;
  2. 后续无论是通过ping机制还是每30s从注册中心拉取全量服务实例列表,
    但凡all list发生变更,都会触发一个事件,然后修改本地内存的up list。
  3. 另外默认ping机制并不会定时每10s执行,因为默认的IPing实现是DummyPing,而BaseLoadBalancer#canSkipPing()里会判断IPing实现是DummyPing则不启动Timer定时做Ping机制。
    Eureka和Ribbon整合之后,EurekaRibbonClientConfiguration(spring-cloud-netflix-eureka-client包下)类中新定义了一个IPing(NIWSDiscoveryPing),此时会启动Timer每10s做一次ping操作。

随机算法体现在RandomRule#chooseRandomInt()方法:

然而,chooseRandomInt()方法中居然使用的不是Random,而是ThreadLocalRandom,并直接使用ThreadLocalRandom#nextInt(int)方法获取某个范围内的随机值,ThreadLocalRandom是个什么东东?

1、ThreadLocalRandom详解


ThreadLocalRandom位于JUC(java.util.concurrent)包下,继承自Random。

1)为什么不用Random?

从Java1.0开始,java.util.Random就已经存在,其是一个线程安全类,多线程环境下,科通通过它获取到线程之间互不相同的随机数,其线程安全性是通过原子类型AtomicLong的变量seed + CAS实现的。

尽管Random使用 CAS 操作来更新它原子类型AtomicLong的变量seed,并且在很多非阻塞式算法中使用了非阻塞式原语,但是CAS在资源高度竞争时的表现依然糟糕。

2)ThreadLocalRandom的诞生?

JAVA7在JUC包下增加了该类,意在将它和Random结合以克服Random中的CAS性能问题;
虽然可以使用ThreadLocal<Random>来避免线程竞争,但是无法避免CAS 带来的开销;考虑到性能诞生了ThreadLocalRandom;ThreadLocalRandom不是ThreadLocal包装后的Random,而是真正的使用ThreadLocal机制重新实现的Random。

ThreadLocalRandom的核心实现细节:

  1. 使用一个普通long类型的变量SEED替换Random中的AtomicLong类型的seed
  2. 不能同构构造函数创建ThreadLocalRandom实例,因为它的构造函数是私有的,要使用静态工厂ThreadLocalRandom.current()
  3. 它是CPU缓存感知式的,使用8个long虚拟域来填充64位L1高速缓存行

3)ThreadLocalRandom的错误使用场景

1> 代码示例:

package com.saint.random;import java.util.concurrent.ThreadLocalRandom;/** * @author Saint */public class ThreadLocalRandomTest {    private static final ThreadLocalRandom RANDOM =            ThreadLocalRandom.current();    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            new SonThread().start();        }    }    private static class SonThread extends Thread {        @Override        public void run() {            System.out.println(Thread.currentThread().getName() + " obtain random value is : " + RANDOM.nextInt(100));        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2> 运行结果:

  • 居然每个线程获取到的随机值都是一样的!!!

3> 运行结果分析:

上述代码中之所以每个线程获取到的随机值都是一样,因为:

  1. ThreadLocalRandom 类维护了一个类单例字段,线程通过调用 ThreadLocalRandom#current() 方法来获取 ThreadLocalRandom单例对象;然后以线程维护的实例字段 threadLocalRandomSeed 为种子生成下一个随机数和下一个种子值;
  2. 线程在调用 current() 方法的时候,会根据用每个线程 thread 的一个实例字段 threadLocalRandomProbe 是否为 0 来判断当前线程实例是是第一次调用随机数生成方法,进而决定是否要给当前线程初始化一个随机的 threadLocalRandomSeed 种子值。
  3. 所以,如果其他线程绕过 current() 方法直接调用随机数方法(比如nextInt()),那么它的种子值就是可预测的,即一样的。

4)ThreadLocalRandom的正确使用方式

每次要获取随机数时,调用ThreadLocalRandom的正确使用方式是ThreadLocalRandom.current().nextX(int)

public class ThreadLocalRandomTest {    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            new SonThread().start();        }    }    private static class SonThread extends Thread {        @Override        public void run() {            System.out.println(Thread.currentThread().getName() + " obtain random value is : " + ThreadLocalRandom.current().nextInt(100));        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

运行结果如下:

5)ThreadLocalRandom源码解析

1> nextInt(int bound)方法获取随机值

public int nextInt(int bound) {    if (bound <= 0)        throw new IllegalArgumentException(BadBound);    // 1. 使用当前种子值SEED获取新种子值,mix32()可以看到是一个扰动函数    int r = mix32(nextSeed());    int m = bound - 1;    // 2. 使用新种子值获取随机数    if ((bound & m) == 0) // power of two        r &= m;    else { // reject over-represented candidates        for (int u = r >>> 1;             u + m - (r = u % bound) < 0;             u = mix32(nextSeed()) >>> 1)            ;    }    return r;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

当bound=100时,代码执行如下:


2> nextSeed()方法获取下一个种子值

final long nextSeed() {    Thread t; long r; // read and update per-thread seed    //r = UNSAFE.getLong(t, SEED) 获取当前线程中对应的SEED值    UNSAFE.putLong(t = Thread.currentThread(), SEED,                   r = UNSAFE.getLong(t, SEED) + GAMMA);    return r;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

nextSeed()方法中首先使用基于主内存地址的Volatile读的方式获取老的SEED种子值,然后再使用基于主内存地址的Volatile写的方式设置新的SEED种子值;

种子值相关常量:

// Unsafe mechanicsprivate static final sun.misc.Unsafe UNSAFE;// 种子值private static final long SEED;private static final long PROBE;private static final long SECONDARY;static {    try {        UNSAFE = sun.misc.Unsafe.getUnsafe();        Class<?> tk = Thread.class;        SEED = UNSAFE.objectFieldOffset            (tk.getDeclaredField("threadLocalRandomSeed"));        PROBE = UNSAFE.objectFieldOffset            (tk.getDeclaredField("threadLocalRandomProbe"));        SECONDARY = UNSAFE.objectFieldOffset            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));    } catch (Exception e) {        throw new Error(e);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3> 总述

  1. ThreadLocalRandom中直接基于主内存地址的Volatile读方式读取老SEED值。
  2. ThreadLocalRandom中直接基于主内存地址的Volatile写方式将老SEED值替换为新SEED值;因为这里的种子值都是线程级别的,所以不需要原子级别的变量,也不会出现多线程竞争修改种子值的情况。

谈到基于主内存地址的Volatile读写,ConCurrentHashMap中也有大量使用,参考博文:。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发