高并发线程内存事件处理器 disruptor 三 高性能原理

news2024/11/25 1:45:53

一 disruptor为什么快的核心原理

  1. 属性填充:通过添加额外的无用信息,避免伪共享问题
    1. 什么是共享内存
      1.  在系统内存中,我们的数据存在于cpu缓存中,cpu缓存的基础缓存单位为 cache line,通常cache line的大小为64字节,每个cache line指向一个主内存的地址,已知java中一个long类型占用内存8byte 那么一个缓存行就可以缓存8个long类型值,当我们访问其中一个值时,cpu会将当前缓存行都加载到内存中,此时我们可以快速的访问其他7个long类型数据不用在cpu缓存中重新查找,这就是共享内存。
    2. 什么是cache line

       

      1. 为了解决计算机系统中主内存与cpu之间运行速度差问题,会在cpu与主内存之间添加一级或多级高速缓冲存储器(cache)。这个cache一般是被集成在cpu内部,所以也叫cpu cache
      2. cache内部按行存储,同时缓存行也是cpu跟主内存交换数据的基本单位
    3. 什么是伪共享
      1. 当我们要访问某一变量时,cpu首先会查看cache内部是否有缓存,如果有则读取变量所在缓存行数据,然后对其进行读写操作,而每个缓存行只能同时被一个线程读写,此时如果有其他线程也对此缓存行进行读写,则会大大降低缓存行性能,这个并发对缓存行的读写行为称为伪共享。
    4. 如何避免伪共享产生
      1. 让每个变量独占一个缓存行,对空余位置进行填充
      2. 已知每个缓存行长度为64,每个long类型占8位,那么在存储long类型元素同时存储7个其他无用的long类型,达到让变量独占一行的目的
      3. disruptor 代码
        class LhsPadding {
            protected long p1, p2, p3, p4, p5, p6, p7;
        }
        
        class Value extends LhsPadding {
            protected volatile long value;
        }
        
        class RhsPadding extends Value {
            protected long p9, p10, p11, p12, p13, p14, p15;
        }
        
        public class Sequence extends RhsPadding{
            static final long INITIAL_VALUE=-1L;
            private static final Unsafe UNSAFE;
            private static final long VALUE_OFFSET;
            
            static {
                UNSAFE= Util.getUnsafe();
                try {
                    VALUE_OFFSET=UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
                }catch (final Exception e){
                    throw new RuntimeException(e);
                }
            }
            
            public Sequence(){
                this(INITIAL_VALUE);
            }
            
            public Sequence(final long initialValue){
                UNSAFE.putOrderedLong(this,VALUE_OFFSET,initialValue);
            }
        }
        
        public class VolatileData extends RhsPadding {
            // 占用 8个字节 +48 + 对象头 = 64字节
        
            //需要操作的数据
            volatile long value;
        
            public VolatileData() {
            }
        
            public VolatileData(long defValue) {
                value = defValue;
            }
        
            public long accumulationAdd() {
                //因为单线程操作不需要加锁
                return ++value;
            }
        
            public long getValue() {
                return value;
            }
        }
        
        1. 在disruptor中有一个重要的成员sequece,在生产者和消费者中都有一个独立的sequence,它在ringbuffer中标识着写入或消费位置。
  2. 无锁的设计:采用CAS无锁方式,保证线程的安全性
    1. 锁机制产生的问题
      1. 多线程对临界资源的锁标记获取会对cpu资源产生过多的占用。
      2. 加锁/解锁/唤醒等待锁线程/阻塞 等过程中会导致上下文切换和调度延迟,尤其是在进行上下文切换时会导致cpu缓存的指令和数据失效需要重新加载,此操作会导致性能问题的产生。
      3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致导致优先级反转,引起性能问题。
    2. cas
      1. 如果目标引用值=预期值,则将值修改为目标值
      2. if (a = 1) {
            a = b; 
            return b;
        } else { 
            return a;
        }
    3. disruptor的无锁设计
      1. 生产者多线程do while 获取下一个可用地址,如果不可用则重新进行获取。
      2. do{ 
            current = cursor.get(); 
            next = current + n; 
            if (!hasAvailableCapacity(gatingSequences, n, current)) { 
                throw InsufficientCapacityException.INSTANCE; 
            } 
        } while (!cursor.compareAndSet(current, next)); 
            //next 类比于ArrayBlockQueue的数组索引index 
        return next;
        
        
  3.  引入环形的数组结构:数组元素不会被回收,避免频繁的GC
    1. 什么是环形数组
      1. 通过下标访问数组内元素,当下标达到数组最大值是将下标归0,重新开始新一轮
    2. 为什么使用环形数组
      1. 可以有效避免垃圾回收,降低对象回收和重新分配内存过程产生的消耗
      2. 数组通过下标访问数据,访问效率更高
      3. 数组数据存在于一块连续的内存地址中,访问更高效
      4. 对数组中数据采用数据覆盖的方式,避免jvm执行gc
  4. 元素位置的定位:采用跟一致性哈希一样的方式,一个索引,进行递增

二 等待策略

Disruptor 定义了多种不同的策略,针对不同的场景,提供了不一样的性能表现,根据实际运行环境的 CPU 的硬件特点选择恰当的策略,并配合特定的 JVM 的配置参数,能够实现不同的性能提升

  1. blockingWaitStrategy
    1. 默认策略,在blockingWaitStrategy内部使用锁和condition来控制线程的唤醒。
    2. 它是最低效的策略,但其对cpu的消耗最小,并且在各种不同环境部署时,能提供更加一致性的性能表现。
  2. sleepingwaitStrategy
    1. 它的性能表现跟BlockingWaitStrategy差不多,对cpu的消耗差不多,但其对生产者线程的影响最小,通过使用lockSupport.parkNanos(1)实现循环等待,适合用于异步日志蕾丝的场景。
  3. yieldingWaitStrategy
    1. 它是可以使用在低延迟系统的策略之一,它将自旋以等待序列增加到适当的值。在循环体内,将调用Thread.yieId() 以允许其他排队的线程巡行。在要求极高性能且事件处理数小于cpu逻辑核心数的场景中,推荐使用此策略
  4. busySpinWaitStrategy
    1. 性能最好,适合用于低延迟的系统。在要求极高性能且事件处理线程数小于cpu逻辑核心数的场景中,推荐使用此策略
  5. phasedBackoffWaitStrategy
    1. 自旋+yieId+自定义策略,适合用于cpu资源紧缺,吞吐量和延迟不重要的场景。

三 Disruptor的工作原理。
Disruptor 不像传统的队列,分为一个队头指针和一个队尾指针,而是只有一个角标(上面的seq),那么这个是如何保证生产的消息不会覆盖没有消费掉的消息呢。
在Disruptor中生产者分为单生产者和多生产者,而消费者并没有区分。
单生产者情况下,就是普通的生产者向RingBuffer中放置数据,消费者获取最大可消费的位置,并进行消费。而多生产者时候,又多出了一个跟RingBuffer同样大小的Buffer,称为AvailableBuffer。
在多生产者中,每个生产者首先通过CAS竞争获取可以写的空间,然后再进行慢慢往里放数据,如果正好这个时候消费者要消费数据,那么每个消费者都需要获取最大可消费的下标,这个下标是在AvailableBuffer进行获取得到的最长连续的序列下标

例:当前我要写入2个数据,那么在ringbuffer中判断当前next后面是否有两个位置(通过cas判断),如果有则将next后移两位,当前两个数据位置由当前这个线程使用,其他线程从next后面在进行获取可写入位置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/555234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是测试策略?怎么制定测试策略?测试管理篇

之前说了太多的测试技术和测试用例设计方法,猛地发现有点“偏科“了。今天我们放松一些,泡一杯茶,一起来聊一聊测试策略吧。 当然,文章脉络肯定是咱们老三样:什么是测试策略,为什么要制定测试策略&#xff…

5个最好的WooCommerce商城自动化动作来增加销售量

您是否正在寻找简单智能的方法来自动执行任务并增加 WooCommerce 商店的销售额? 通过在线商店中的自动化任务,您可以在发展业务和增加销售额的同时节省时间和金钱。 在本文中,我们将向您展示如何使用 WooCommerce商城自动化来增加销售额。 …

Puppeteer入门实践

环境 1、安装nodejs 官网:https://nodejs.org/zh-cn 下载安装好nodejs只后 验证:node -v 出现版本号表示安装成功,否则需要配置环境变量 2、创建node项目并初始化 随便新建一个文件夹 进入文件夹搜索cmd回车 执行npm init -y 安装依赖 …

RabbitMq--- 惰性队列

前言 消息堆积是Mq消费时常见的问题,这里我们展开说一下消息堆积的原因,以及RabbitMq 中是如何解决这个问题的。 1. 消息堆积问题 当生产者发送消息时的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储…

【Linux】shell脚本—正则表达式

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、正则表达式概述二、基本的正则表达式三、操作演示 一、正则表达式概述 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、…

WIN11+CLion+CMake+MINGW+OPENCV编译需注意问题

安装编译教程可参考以下链接: CLion OpenCV cmake,源码编译及使用_clion编译opencv_拜阳的博客-CSDN博客 使用CLion开发openCV——环境搭建全记录_-Willing-的博客-CSDN博客 此文主要是记录自己在编译过程中遇到的问题和解决方法 1、版本问题 C…

Windows 10 主机上的 VMware Workstation 和设备/凭据防护不兼容“错误

Windows 10 主机上的 VMware Workstation 和设备/凭据防护不兼容“错误 VMware Workstation 和 Device/Credential Guard 不兼容。VMware 工作站可以在禁用设备/凭据防护后运行。 排查错误的过程: 要解决错误,请按照以下步骤操作: 如果您…

LDR6020 【USB_C显示器方案】台式显示器方案介绍

首先介绍一下什么是Type-c接口? 现在显示器上常见的有这几种接口:HDMI、DP、USB-A、USB-C(USB Type-c接口)、3.5mm和电源接口,像之前流行的VGA接口和DVI接口,基本上在新显示器上,尤其是中高端显…

什么是频谱型温振变送器(附常见振动故障特征)

在机械振动方面,振动分析是一项十分重要的技术。这项技术是预测维修程序中的基础,是机器状态的指示器,为了避免机械设备异常振动所带来的损失,对工业机械设备做振动分析是非常有必要的! 频谱型温振变送器是一款选用了M…

阿里高级工程师对C语言预处理指令的理解

目录 预处理器指令列表 预处理器指令的流程 四种主要类型的预处理器指令 结论 我们可以将预处理器视为一个编译过程,该过程在开发人员运行程序时运行。它是使用 c/c 语言执行程序的预处理。若要初始化预处理器命令的进程,必须使用哈希符号 &#xf…

【Devops运维】Docker搭建jenkins自动化编译hadoop/spark/flink/hive/kyuubi/trino大数据组件

Docker搭建jenkins DevOps概念Docker部署Jenkins制作Jenkins镜像Dockerfile及所依赖的脚本build镜像 利用docker-compose部署jenkins 配置Jenkins管理员密码插件安装系统配置全局工具配置MAVEN 配置JDK 配置GIT 配置MAVEN 配置 Jenkins Maven Git 自动化编译找到token生成界面…

ChatGPT:使用OpenAI创建自己的AI网站,使用 flask web框架快速搭建网站主体

使用OpenAI创建自己的AI网站 如果你还是一个OpenAI的小白,有OpenAI的账号,但想调用OpenAI的API搞一些有意思的事,那么这一系列的教程将仔细的为你讲解如何使用OpenAI的API制作属于自己的AI网站。 使用 flask web框架快速搭建网站主体 之前…

C++:布隆过滤器和哈希切分

目录 一. 什么是布隆过滤器 二. 布隆过滤器的实现 2.1 数据插入函数set 2.2 判断数据是否存在函数test 2.3 布隆过滤器数据的删除 三. 哈希切分 一. 什么是布隆过滤器 在我之前的博客C:使用位图处理海量数据_【Shine】光芒的博客-CSDN博客中,介绍了…

【LinuxShell】linux防火墙之firewalld防火墙

文章目录 前言一、firewalld概述1. 概念2. firewalld和iptables的关系 二、firewalld网络区域1. firewalld区域的概念2. firewalld预定义区域3. firewalld数据包的处理3.1 firewalld数据处理流程3.2 firewalld检查数据包的源地址的规则3.3 总结 三、firewalld防火墙的配置方法1…

“GPT+医疗健康”:给予医疗领域新机遇

现如今,GPT十分火热。随着人们对健康医疗的关注越来越热切,GPT已逐渐成为健康医疗领域的重要角色之一。GPT可以用于许多医疗语境中,如医学咨询、病症诊断、健康建议、在线问诊、患者教育、健康数据跟踪等。 GPT是一种基于深度学习的自然语言处…

结构体-C语言

🤩本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 🥰内容专栏:这里是《C知识系统分享》专栏,笔者用重金(时间和精力)打造,基础知识一网打尽&#xff0c…

【Python办公自动化】python实现将图片插入到word中指定位置并将word转换为图片

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、…

垃圾站养殖场除臭杀菌解决方案

养殖场和垃圾站都会产生大量的有机废气和垃圾,这些废气和垃圾会产生难闻的臭味,影响周围环境和居民健康。这些地方又是病菌和细菌的滋生地,这些细菌和病菌会对人类和动物的健康造成威胁。除臭杀菌系统可以杀灭这些细菌和病菌,也可…

换个思维方式,你离网工天花板会更近一点

大家好,我是许公子。 收到老杨的邀请,我正式加入网络工程师俱乐部了,未来会给你分享更多网工硬核内容。 和老杨聊天的过程中,我想起了在刚入社会一两年,我去参加了一个高中同学聚餐。 里面有自主创业的,…

软件测试被00后整顿职场了?

00后带来的压力 公司一位工作3年的老油条工资还没有刚来的00后高,她心中不平,对这件事情有不小的怨气,她觉得自己来公司三年了,三年内迟到次数都不超过5次,每天勤勤恳恳,要加班的时候也愿意加班&#xff0…