ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【云原生&微服务五】Ribbon负载均衡策略之随机ThreadLocalRandom [打印本页]

作者: 卖不甜枣    时间: 2022-6-25 13:40
标题: 【云原生&微服务五】Ribbon负载均衡策略之随机ThreadLocalRandom
文章目录



一、前言

在前面的Ribbon系列文章:
     我们聊了以下问题:
     本篇文章我们继续看Ribbon内置了哪些负载均衡策略?RandomRule负载均衡策略的算法是如何实现的?
PS:Ribbon依赖Spring Cloud版本信息如下:
  1. <dependencyManagement>
  2.     <dependencies>
  3.         <dependency>
  4.             <groupId>org.springframework.boot</groupId>
  5.             <artifactId>spring-boot-dependencies</artifactId>
  6.             <version>2.3.7.RELEASE</version>
  7.             <type>pom</type>
  8.             <scope>import</scope>
  9.         </dependency>
  10.         
  11.         <dependency>
  12.             <groupId>org.springframework.cloud</groupId>
  13.             <artifactId>spring-cloud-dependencies</artifactId>
  14.             <version>Hoxton.SR8</version>
  15.             <type>pom</type>
  16.             <scope>import</scope>
  17.         </dependency>
  18.         
  19.         <dependency>
  20.             <groupId>com.alibaba.cloud</groupId>
  21.             <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  22.             <version>2.2.5.RELEASE</version>
  23.             <type>pom</type>
  24.             <scope>import</scope>
  25.         </dependency>
  26.     </dependencies>
  27. </dependencyManagement>
复制代码
二、Ribbon内置了哪些负载均衡算法?

三、随机算法 --> RandomRule

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

随机算法体现在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来避免线程竞争,但是无法避免CAS 带来的开销;考虑到性能诞生了ThreadLocalRandom;ThreadLocalRandom不是ThreadLocal包装后的Random,而是真正的使用ThreadLocal机制重新实现的Random。
ThreadLocalRandom的核心实现细节:
     3)ThreadLocalRandom的错误使用场景

1> 代码示例:

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



3> 运行结果分析:

上述代码中之所以每个线程获取到的随机值都是一样,因为:
     4)ThreadLocalRandom的正确使用方式

每次要获取随机数时,调用ThreadLocalRandom的正确使用方式是ThreadLocalRandom.current().nextX(int):
  1. public class ThreadLocalRandomTest {
  2.     public static void main(String[] args) {
  3.         for (int i = 0; i < 10; i++) {
  4.             new SonThread().start();
  5.         }
  6.     }
  7.     private static class SonThread extends Thread {
  8.         @Override
  9.         public void run() {
  10.             System.out.println(Thread.currentThread().getName() + " obtain random value is : " + ThreadLocalRandom.current().nextInt(100));
  11.         }
  12.     }
  13. }
复制代码
运行结果如下:

5)ThreadLocalRandom源码解析

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

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


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

  1. final long nextSeed() {
  2.     Thread t; long r; // read and update per-thread seed
  3.     //r = UNSAFE.getLong(t, SEED) 获取当前线程中对应的SEED值
  4.     UNSAFE.putLong(t = Thread.currentThread(), SEED,
  5.                    r = UNSAFE.getLong(t, SEED) + GAMMA);
  6.     return r;
  7. }
复制代码
  nextSeed()方法中首先使用基于主内存地址的Volatile读的方式获取老的SEED种子值,然后再使用基于主内存地址的Volatile写的方式设置新的SEED种子值;
  种子值相关常量:
  1. // Unsafe mechanics
  2. private static final sun.misc.Unsafe UNSAFE;
  3. // 种子值
  4. private static final long SEED;
  5. private static final long PROBE;
  6. private static final long SECONDARY;
  7. static {
  8.     try {
  9.         UNSAFE = sun.misc.Unsafe.getUnsafe();
  10.         Class<?> tk = Thread.class;
  11.         SEED = UNSAFE.objectFieldOffset
  12.             (tk.getDeclaredField("threadLocalRandomSeed"));
  13.         PROBE = UNSAFE.objectFieldOffset
  14.             (tk.getDeclaredField("threadLocalRandomProbe"));
  15.         SECONDARY = UNSAFE.objectFieldOffset
  16.             (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
  17.     } catch (Exception e) {
  18.         throw new Error(e);
  19.     }
  20. }
复制代码
3> 总述

     谈到基于主内存地址的Volatile读写,ConCurrentHashMap中也有大量使用,参考博文:https://blog.csdn.net/Saintmm/article/details/122911586

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4