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

网友投稿 248 2022-08-23

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

文章目录

​​一、前言​​​​二、Ribbon内置了哪些负载均衡算法?​​​​三、随机算法 --> RandomRule​​

​​1、ThreadLocalRandom详解​​

​​1)为什么不用Random?​​​​2)ThreadLocalRandom的诞生?​​​​3)ThreadLocalRandom的错误使用场景​​

​​1> 代码示例:​​​​2> 运行结果:​​​​3> 运行结果分析:​​

​​4)ThreadLocalRandom的正确使用方式​​​​5)ThreadLocalRandom源码解析​​

​​1> nextInt(int bound)方法获取随机值​​​​2> nextSeed()方法获取下一个种子值​​​​3> 总述​​

一、前言

在前面的Ribbon系列文章:

​​【云原生&微服务一】SpringCloud之Ribbon实现负载均衡详细案例(集成Eureka、Ribbon)​​​​【云原生&微服务二】SpringCloud之Ribbon自定义负载均衡策略(含Ribbon核心API)​​​​【云原生&微服务三】SpringCloud之Ribbon是这样实现负载均衡的(源码剖析@LoadBalanced原理)​​​​【云原生&微服务四】SpringCloud之Ribbon和Erueka集成的细节全在这了(源码剖析)​​

我们聊了以下问题:

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

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

PS:Ribbon依赖Spring Cloud版本信息如下:

org.springframework.boot spring-boot-dependencies 2.3.7.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Hoxton.SR8 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.2.5.RELEASE pom import

二、Ribbon内置了哪些负载均衡算法?

RandomRule --> 随机选择一个ServerRoundRobinRule --> 轮询选择,轮询Index,选择index对应位置的Server,请求基本平摊到每个Server上。WeightedResponseTimeRule --> 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。ZoneAvoidanceRule --> 综合判断Server所在Zone的性能和Server的可用性选择server,在没Zone的环境下,类似于轮询(RoundRobinRule)。默认策略BestAvailableRule --> 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。RetryRule --> 对选定的负载均衡策略上 重试机制,在一个配置时间段内选择Server不成功,就一直尝试使用subRule(默认是RoundRobinRule)的方式选择一个可用的Server。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)​​方法;

随机算法体现在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的核心实现细节:

使用一个普通long类型的变量​​SEED​​​替换​​Random​​​中的AtomicLong类型的​​seed​​;不能同构构造函数创建ThreadLocalRandom实例,因为它的构造函数是私有的,要使用静态工厂​​ThreadLocalRandom.current()​​;它是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)); } }}

2> 运行结果:

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

3> 运行结果分析:

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

ThreadLocalRandom 类维护了一个类单例字段,线程通过调用​​ThreadLocalRandom#current()​​​ 方法来获取​​ThreadLocalRandom单例对象​​​;然后以线程维护的实例字段​​threadLocalRandomSeed​​ 为种子生成下一个随机数和下一个种子值;线程在调用 current() 方法的时候,会根据用每个线程 thread 的一个实例字段​​threadLocalRandomProbe​​ 是否为 0 来判断当前线程实例是是第一次调用随机数生成方法,进而决定是否要给当前线程初始化一个随机的 threadLocalRandomSeed 种子值。所以,如果其他线程绕过 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)); } }}

运行结果如下:

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;}

当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;}

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); }}

3> 总述

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【微服务七】Ribbon负载均衡策略之BestAvailableRule源码深度剖析
下一篇:市场营销考研可以考哪些专业?(市场营销考研可以考哪些专业知乎)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~