如此狂妄,自稱高性能隊列的Disruptor有啥來頭?

来源:https://www.cnblogs.com/jiagooushi/archive/2022/09/19/16707593.html
-Advertisement-
Play Games

併發框架Disruptor 1. Disruptor概述 1.1 背景 ​ Disruptor是英國外匯交易公司LMAX開發的一個高性能隊列,研發的初衷是解決記憶體隊列的延遲問題(在性能測試中發現竟然與I/O操作處於同樣的數量級),基於Disruptor開發的系統單線程能支撐每秒600萬訂單,2010 ...


併發框架Disruptor

file

1. Disruptor概述

1.1 背景

​ Disruptor是英國外匯交易公司LMAX開發的一個高性能隊列,研發的初衷是解決記憶體隊列的延遲問題(在性能測試中發現竟然與I/O操作處於同樣的數量級),基於Disruptor開發的系統單線程能支撐每秒600萬訂單,2010年在QCon演講後,獲得了業界關註,2011年,企業應用軟體專家Martin Fowler專門撰寫長文介紹。同年它還獲得了Oracle官方的Duke大獎。

​ 目前,包括Apache Storm、Camel、Log4j 2在內的很多知名項目都應用了Disruptor以獲取高性能。

​ 需要特別指出的是,這裡所說的隊列是系統內部的記憶體隊列,而不是Kafka這樣的分散式隊列。

有界無鎖 高併發隊列

1.2 什麼是Disruptor

​ Disruptor是用於一個JVM中多個線程之間的消息隊列,作用與ArrayBlockingQueue有相似之處,但是Disruptor從功能、性能都遠好於ArrayBlockingQueue,當多個線程之間傳遞大量數據或對性能要求較高時,可以考慮使用Disruptor作為ArrayBlockingQueue的替代者。

​ 官方也對Disruptor和ArrayBlockingQueue的性能在不同的應用場景下做了對比,目測性能只有有5~10倍左右的提升。

1.3 為什麼使用Disruptor

​ 傳統阻塞的隊列使用鎖保證線程安全,而鎖通過操作系統內核上下文切換實現,會暫停線程去等待鎖,直到鎖釋放。

​ 執行這樣的上下文切換,會丟失之前保存的數據和指令。由於消費者和生產者之間的速度差異,隊列總是接近滿或者空的狀態,這種狀態會導致高水平的寫入爭用。

1.3.1 傳統隊列問題

首先這裡說的隊列也僅限於Java內部的消息隊列

隊列 有界性 結構 隊列類型
ArrayBlockingQueue 有界 加鎖 數組 阻塞
LinkedBlockingQueue 可選 加鎖 鏈表 阻塞
ConcurrentLinkedQueue 無界 無鎖 鏈表 非阻塞
LinkedTransferQueue 無界 無鎖 鏈表 阻塞
PriorityBlockingQueue 無界 加鎖 阻塞
DelayQueue 無界 加鎖 阻塞
1.3.2 Disruptor應用場景

參考使用到disruptor的一些框架.

1.3.2.1 log4j2

​ Log4j2非同步日誌使用到了disruptor, 日誌一般是有緩衝區, 滿了才寫到文件, 增量追加文件結合NIO等應該也比較快, 所以無論是EventHandler還是WorkHandler處理應該延遲比較小的, 寫的文件也不多, 所以場景是比較合適的。

1.3.2.2 Jstorm

​ 在流處理中不同線程中數據交換,數據計算可能蠻多記憶體中計算, 流計算快進快出,disruptor應該不錯的選擇。

1.3.2.3 百度uid-generator

​ 部分使用Ring buffer和去偽共用等思路緩存已生成的uid, 應該也部分參考了disruptor吧。

1.4 Disruptor 的核心概念

先從瞭解 Disruptor 的核心概念開始,來瞭解它是如何運作的。下麵介紹的概念模型,既是領域對象,也是映射到代碼實現上的核心對象。

1.4.1 Ring Buffer

Disruptor中的數據結構,用於存儲生產者生產的數據

​ 如其名,環形的緩衝區。曾經 RingBuffer 是 Disruptor 中的最主要的對象,但從3.0版本開始,其職責被簡化為僅僅負責對通過 Disruptor 進行交換的數據(事件)進行存儲和更新。在一些更高級的應用場景中,Ring Buffer 可以由用戶的自定義實現來完全替代。

1.4.2 Sequence

序號,在Disruptor框架中,任何地方都有序號

​ 生產者生產的數據放在RingBuffer中的哪個位置,消費者應該消費哪個位置的數據,RingBuffer中的某個位置的數據是什麼,這些都是由這個序號來決定的。這個序號可以簡單的理解為一個AtomicLong類型的變數。其使用了padding的方法去消除緩存的偽共用問題。

1.4.3 Sequencer

序號生成器,這個類主要是用來協調生產者的

​ 在生產者生產數據的時候,Sequencer會產生一個可用的序號(Sequence),然後生產者就就知道數據放在環形隊列的那個位置了。

​ Sequencer是Disruptor的真正核心,此介面有兩個實現類 SingleProducerSequencer、MultiProducerSequencer ,它們定義在生產者和消費者之間快速、正確地傳遞數據的併發演算法。

1.4.4 Sequence Barrier

序號屏障

​ 我們都知道,消費者在消費數據的時候,需要知道消費哪個位置的數據。消費者總不能自己想取哪個數據消費,就取哪個數據消費吧。這個SequencerBarrier起到的就是這樣一個“柵欄”般的阻隔作用。你消費者想消費數據,得,我告訴你一個序號(Sequence),你去消費那個位置上的數據。要是沒有數據,就好好等著吧

1.4.5 Wait Strategy

Wait Strategy決定了一個消費者怎麼等待生產者將事件(Event)放入Disruptor中。

​ 設想一種這樣的情景:生產者生產的非常慢,而消費者消費的非常快。那麼必然會出現數據不夠的情況,這個時候消費者怎麼進行等待呢?WaitStrategy就是為瞭解決問題而誕生的。

1.4.6 Event

​ 從生產者到消費者傳遞的數據叫做Event。它不是一個被 Disruptor 定義的特定類型,而是由 Disruptor 的使用者定義並指定。

1.4.7 EventHandler

​ Disruptor 定義的事件處理介面,由用戶實現,用於處理事件,是 Consumer 的真正實現。

1.4.8 Producer

​ 即生產者,只是泛指調用 Disruptor 發佈事件的用戶代碼,Disruptor 沒有定義特定介面或類型。

1.5 Disruptor特性

​ Disruptor其實就像一個隊列一樣,用於在不同的線程之間遷移數據,但是Disruptor也實現了一些其他隊列沒有的特性,如:

  • 同一個“事件”可以有多個消費者,消費者之間既可以並行處理,也可以相互依賴形成處理的先後次序(形成一個依賴圖);
  • 預分配用於存儲事件內容的記憶體空間;
  • 針對極高的性能目標而實現的極度優化和無鎖的設計;

2. Disruptor入門

我們使用一個簡單的例子來體驗一下Disruptor,生產者會傳遞一個long類型的值到消費者,消費者接受到這個值後會列印出這個值。

2.1 添加依賴

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>

2.2 Disruptor API

Disruptor 的 API 十分簡單,主要有以下幾個步驟

2.2.1 定義事件

首先創建一個 LongEvent 類,這個類將會被放入環形隊列中作為消息內容。

事件(Event)就是通過 Disruptor 進行交換的數據類型。

public class LongEvent {
    private long value;

    public void set(long value) {
        this.value = value;
    }

    public long getValue() {
        return value;
    }
}
2.2.2 定義事件工廠

為了使用Disruptor的記憶體預分配event,我們需要定義一個EventFactory

​ 事件工廠(Event Factory)定義瞭如何實例化前面第1步中定義的事件(Event),需要實現介面 com.lmax.disruptor.EventFactory<T>。

Disruptor 通過 EventFactory 在 RingBuffer 中預創建 Event 的實例。

​ 一個 Event 實例實際上被用作一個“數據槽”,發佈者發佈前,先從 RingBuffer 獲得一個 Event 的實例,然後往 Event 實例中填充數據,之後再發佈到 RingBuffer 中,之後由 Consumer 獲得該 Event 實例並從中讀取數據。

public class LongEventFactory implements EventFactory<LongEvent> {
    public LongEvent newInstance() {
        return new LongEvent();
    }
}
2.2.3 定義事件處理的具體實現

為了讓消費者處理這些事件,所以我們這裡定義一個事件處理器,負責列印event

通過實現介面 com.lmax.disruptor.EventHandler<T> 定義事件處理的具體實現。

public class LongEventHandler implements EventHandler<LongEvent> {
    public void onEvent(LongEvent event, long sequence, boolean endOfBatch) {
        //CommonUtils.accumulation();
        System.out.println("consumer:" + Thread.currentThread().getName() + " Event: value=" + event.getValue() + ",sequence=" + sequence);
    }
}
2.2.4 指定等待策略

Disruptor 定義了 com.lmax.disruptor.WaitStrategy 介面用於抽象 Consumer 如何等待新事件,這是策略模式的應用

WaitStrategy YIELDING_WAIT = new YieldingWaitStrategy();
2.2.5 啟動 Disruptor

註意ringBufferSize的大小必須是2的N次方

// 指定事件工廠
LongEventFactory factory = new LongEventFactory();

// 指定 ring buffer位元組大小, 必須是2的N次方
int bufferSize = 1024;

//單線程模式,獲取額外的性能
Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(factory,
                                                          bufferSize, Executors.defaultThreadFactory(),
                                                          ProducerType.SINGLE,
                                                          new YieldingWaitStrategy());
//設置事件業務處理器---消費者
disruptor.handleEventsWith(new LongEventHandler());

//啟動disruptor線程
disruptor.start();
2.2.6 使用Translators發佈事件

在Disruptor的3.0版本中,由於加入了豐富的Lambda風格的API,可以用來幫組開發人員簡化流程。所以在3.0版本後首選使用Event Publisher/Event Translator來發佈事件。

public class LongEventProducerWithTranslator {
    private final RingBuffer<LongEvent> ringBuffer;

    public LongEventProducerWithTranslator(RingBuffer<LongEvent> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }

    private static final EventTranslatorOneArg<LongEvent, Long> TRANSLATOR =
            new EventTranslatorOneArg<LongEvent, Long>() {
                public void translateTo(LongEvent event, long sequence, Long data) {
                    event.set(data);
                }
            };

    public void onData(Long data) {
        ringBuffer.publishEvent(TRANSLATOR, data);
    }
}
2.2.7 關閉 Disruptor
disruptor.shutdown();//關閉 disruptor,方法會堵塞,直至所有的事件都得到處理

2.3 代碼整合

2.3.1 LongEventMain

消費者-生產者啟動類,其依靠構造Disruptor對象,調用start()方法完成啟動線程。Disruptor 需要ringbuffer環,消費者數據處理工廠,WaitStrategy等

  • ByteBuffer 類位元組buffer,用於包裝消息。

  • ProducerType.SINGLE為單線程 ,可以提高性能

public class LongEventMain {
    public static void main(String[] args) {
        // 指定事件工廠
        LongEventFactory factory = new LongEventFactory();

        // 指定 ring buffer位元組大小, 必須是2的N次方
        int bufferSize = 1024;

        //單線程模式,獲取額外的性能
        Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(factory,
                bufferSize, Executors.defaultThreadFactory(),
                ProducerType.SINGLE,
                new YieldingWaitStrategy());

        //設置事件業務處理器---消費者
        disruptor.handleEventsWith(new LongEventHandler());

        //啟動disruptor線程
        disruptor.start();
        // 獲取 ring buffer環,用於接取生產者生產的事件
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();

        //為 ring buffer指定事件生產者
        LongEventProducerWithTranslator producer = new LongEventProducerWithTranslator(ringBuffer);
        //迴圈遍歷
        for (int i = 0; i < 100; i++) {
            //獲取一個隨機數
            long value = (long) ((Math.random() * 1000000) + 1);
            //發佈數據
            producer.onData(value);
        }
        //停止disruptor線程
        disruptor.shutdown();
    }
}
2.3.2 運行測試

測試結果

consumer:pool-1-thread-1 Event: value=579797,sequence=0
consumer:pool-1-thread-1 Event: value=974942,sequence=1
consumer:pool-1-thread-1 Event: value=978977,sequence=2
consumer:pool-1-thread-1 Event: value=398080,sequence=3
consumer:pool-1-thread-1 Event: value=867251,sequence=4
consumer:pool-1-thread-1 Event: value=796707,sequence=5
consumer:pool-1-thread-1 Event: value=786555,sequence=6
consumer:pool-1-thread-1 Event: value=182193,sequence=7
.....

Event: value = 為消費者接收到的數據,sequence為數據在ringbuffer環的位置。
本文由傳智教育博學谷教研團隊發佈。

如果本文對您有幫助,歡迎關註點贊;如果您有任何建議也可留言評論私信,您的支持是我堅持創作的動力。

轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天帶大家一起來看看網上流傳關於程式員的經典硬核段子,快來看看你是否能get到笑點。 白嫖福利,傳送門 段子1 昨天晚上下班回家,一民警迎面巡邏而來。突然對我大喊:站住! 民警:int 類型占幾個位元組? 我:4 個。 民警:你可以走了。 我感到很詫異。 我:為什麼問這樣的問題? 民警:深夜還在街上走 ...
  • 前言 準備工作 步驟 1 配置fiddler和WX環境 fiddler配置 其他的照我截的圖片配置就好 這樣 fiddler 就配置好,是不是很簡單 WX配置 配置代理 註:埠號得和fiddler配置的一致,也就是這個位置 至於ip地址,使用這個即可 黑框調出方式:win+R,輸入cmd然後回車, ...
  • 我們先瞭解下Servlet的生命周期 Servlet部署在容器里,其生命周期由容器管理。 概括為以下幾個階段: 1)容器載入Servlet類。 當第一次有Web客戶請求Servlet服務或當Web服務啟動時。 2)創建Servlet對象實例。 容器環境根據客戶請求,創建一個或多個Servlet對象實 ...
  • 1 耳返功能簡介 ZEGO Express SDK 提供了Flutter耳返和雙聲道的功能,在視頻直播、K歌、音頻錄製等場景下廣泛應用,開發者可根據實際業務場景需要設置,一套代碼可實現跨平臺音視頻耳返功能,節省開發成本。 實時音視頻的耳返作用就是在嘈雜的環境下,清楚地聽伴奏和自己的聲音,來鑒定自己有 ...
  • 最近在完成軟體體繫結構上機實驗時,遇到一個有點點小難度的選做題,題目信息如下: 利用套接字技術實現應用程式中對資料庫的訪問。應用程式只是利用套接字連接向伺服器發送一個查詢的條件,而伺服器負責對資料庫的查詢,然後伺服器再將查詢的結果利用建立的套接字返回給客戶端,如下圖所示。 本來吧,選做題,不太想做的 ...
  • 一、bean被創建的時間 考慮一個問題,我們都知道spring通過xml的配置創建bean,那麼bean是什麼時間被創建的呢?是在我們getBean()的時候創建的嗎? 我們來做一個測試: 1.首先建立一個User類: package com.jms.pojo; public class User ...
  • Microsoft Word 提供了許多易於使用的文檔操作工具,同時也提供了豐富的功能集供創建複雜的文檔使用。在使用的時候,你可能需要複製一個文檔裡面的內容到另一個文檔。複製的內容可支持包括文本、圖片、表格、超鏈接、書簽、批註、形狀、編號列表、腳註、章節附註等等在內的多種元素。 ...
  • 摘要:本文講述圖像金字塔知識,瞭解專門用於圖像向上採樣和向下採樣的pyrUp()和pyrDown()函數。 本文分享自華為雲社區《[Python圖像處理] 二十一.圖像金字塔之圖像向下取樣和向上取樣》,作者:eastmount。 一.圖像金字塔 圖像金字塔是指由一組圖像且不同分別率的子圖集合,它是圖 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...