600萬訂單每秒Disruptor +SpringBoot,如何解決消息不丟失?

文章很長,且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 爲您奉上珍貴的學習資源 :

免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
免費贈送 :《尼恩技術聖經+高併發系列PDF》 ,幫你 實現技術自由,完成職業升級, 薪酬猛漲!加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷1)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷2)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷3)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領

免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取


600萬訂單每秒Disruptor +SpringBoot,如何解決消息不丟失?

尼恩說在前面

在40歲老架構師 尼恩的讀者交流羣(50+)中,最近有小夥伴拿到了一線互聯網企業如得物、阿里、滴滴、極兔、有贊、shein 希音、百度、網易的面試資格,遇到很多很重要的面試題:

Disruptor 官方說能達到每秒600w OPS訂單處理能力,怎麼實現的?

Disruptor 什麼情況下發生消費數據丟失? 該怎麼解決?

小夥伴 沒有回答好,導致面試掛了。

Disruptor 是隊列之王,相關面試題也是一個非常常見的面試題,考察的是高性能的基本功。

如何才能回答得很漂亮,才能 讓面試官刮目相看、口水直流呢?

這裏,尼恩給大家做一下系統化、體系化的梳理,讓面試官愛到 “不能自已、口水直流”,然後幫大家 實現 ”offer自由”

當然,這道面試題,以及參考答案,也會收入咱們的 《尼恩Java面試寶典》V175版本PDF集羣,供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水平。

注:本文以 PDF 持續更新,最新尼恩 架構筆記、面試題 的PDF文件,請關注本公衆號【技術自由圈】獲取。

隊列之王 Disruptor 簡介

Disruptor是英國外匯交易公司LMAX開發的一個高性能隊列,研發的初衷是解決內存隊列的延遲問題(在性能測試中發現竟然與I/O操作處於同樣的數量級)。

基於Disruptor開發的系統單線程能支撐每秒600萬訂單,2010年在QCon演講後,獲得了業界關注。

在這裏插入圖片描述

2011年,企業應用軟件專家Martin Fowler專門撰寫長文介紹Disruptor。

2011年,Disruptor還獲得了Oracle官方的Duke大獎

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

Disruptor通過以下設計來,來實現 單線程能支撐每秒600萬訂單的問題:

  • 核心架構1:無鎖架構
    生產和消費,都是無鎖架構。具體來說,生產者位點/消費者位點的操作,都是無鎖操作,或者使用輕量級CAS原子操作。

    無鎖架構好處是,既沒有鎖的競爭, 也沒有線程的內核態、用戶態切換的開銷。 關於內核態、用戶態的原理請參見尼恩的葵花寶典。

  • 核心架構2:環形數組架構

    數組元素不會被回收,避免頻繁的GC,所以,爲了避免垃圾回收,採用數組而非鏈表。

    同時,數組對處理器的緩存機制更加友好。

    數組長度2^n,通過位運算,加快定位的速度。

    下標採取遞增的形式。不用擔心index溢出的問題。

    index是long類型,即使100萬QPS的處理速度,也需要30萬年才能用完。

  • 核心架構3:cache line padding

    兩個維度的CPU 緩存行加速,享受到 CPU Cache 那風馳電掣的速度帶來的紅利:

    第一維度: 對 位點等核心組件進行 CPU cache line padding,實現高併發訪問(修改和取值)。

    第二個維度: ringbuffer 是一個數據,加載的時候一般也會塞滿整個 CPU cache line。也就是說 從內存加載數據到 CPU Cache 裏面的時候, 如果是加載數組裏面的數據(如 Disruptor),那麼 CPU 就會加載到數組裏面連續的多個數據。
    所以,Disruptor 數組的遍歷、還是位點的增長, 很容易享受到 CPU Cache 那風馳電掣的速度帶來的紅利。

SpringBoot + Disruptor 使用實戰

有關 Disruptor的 簡單實戰,請參見 尼恩的 《Disruptor 學習聖經 V3》, 由於過於簡單,這裏不做囉嗦。

下面,來看一個SpringBoot + Disruptor的 使用實戰

使用 Disruptor 實現一個生產消費模型步驟是:

  • 準備好簡單的一個springboot應用

  • 定義事件(Event) : 你可以把 Event 理解爲存放在隊列中等待消費的消息對象。

  • 創建事件工廠 :事件工廠用於生產事件,我們在初始化 Disruptor 類的時候需要用到。

  • 創建處理事件的 Handler :Event 在對應的 Handler 中被處理,你可以將其理解爲生產消費者模型中的消費者。

  • 創建並裝配 Disruptor : 事件的生產和消費需要用到Disruptor 對象。

  • 定義生產者,並使用生產者發消息

  • 對簡單的SpringBoot + Disruptor 進行擴容,實現 容量監控預警+ 動態擴容

定義一個Event和工廠

首先定義一個Event來包含需要傳遞的數據:

在這裏插入圖片描述

由於需要讓Disruptor爲我們創建事件,我們同時還聲明瞭一個EventFactory來創建Event對象。

public class LongEventFactory implements EventFactory { 
    @Override 
    public Object newInstance() { 
        return new LongEvent(); 
    } 
} 

在這裏插入圖片描述定義

事件處理器(消費者)

我們還需要一個事件消費者,也就是一個事件處理器。

這個例子中,事件處理器的工作,就是簡單地把事件中存儲的數據打印到終端:

    /** 
     * 類似於消費者
     *  disruptor會回調此處理器的方法
     */
    static class LongEventHandler implements EventHandler<LongEvent> {
        @Override
        public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
            System.out.println(longEvent.getValue());
        }
    }

disruptor會回調此處理器的方法

定義事件源(生產者)

事件都會有一個生成事件的源,類似於 生產者的角色.

注意,這是一個 600wqps 能力的 異步生產者。 這裏定義兩個版本:

在這裏插入圖片描述

生產者的角色的接口定義如下

在這裏插入圖片描述

入門級:一個簡單 DisruptorProducer 生產者的定義和使用

定義一個簡單 DisruptorProducer 生產者

大致的代碼如下

package com.crazymaker.cloud.disruptor.demo.business.impl;


@Slf4j
public class DisruptorProducer implements AsyncProducer {

    //一個translator可以看做一個事件初始化器,publicEvent方法會調用它
    //填充Event
    private static final EventTranslatorOneArg<LongEvent, Long> TRANSLATOR =
            new EventTranslatorOneArg<LongEvent, Long>() {
                public void translateTo(LongEvent event, long sequence, Long data) {
                    event.setValue(data);
                }
            };
    private final RingBuffer<LongEvent> ringBuffer;

    public DisruptorProducer() {
        this.ringBuffer = disruptor().getRingBuffer();
    }

    public void publishData(Long data) {
        log.info("生產一個數據:" + data + " | ringBuffer.remainingCapacity()= " + ringBuffer.remainingCapacity());

        ringBuffer.publishEvent(TRANSLATOR, data);

    }

 
    private Disruptor<LongEvent> disruptor() {

        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("DisruptorThreadPool").build();

        LongEventFactory eventFactory = new LongEventFactory();
        int bufferSize = 1024;
        Disruptor<LongEvent> disruptor = new Disruptor<>(eventFactory, bufferSize, namedThreadFactory,
                ProducerType.MULTI, new BlockingWaitStrategy());

        // 連接 消費者 處理器 ,兩個消費者
        LongEventWorkHandler1 handler1 = new LongEventWorkHandler1();
        LongEventWorkHandler2 handler2 = new LongEventWorkHandler2();
        disruptor.handleEventsWith(handler1, handler2);
        //爲消費者配置異常處理器
        disruptor.handleExceptionsFor(handler1).with(exceptionHandler);
        disruptor.handleExceptionsFor(handler2).with(exceptionHandler);

        // 開啓 分裂者(事件分發)
        disruptor.start();
        return disruptor;
    }


    ExceptionHandler exceptionHandler =...//省略非核心代碼  異常處理器實現
}


上面的代碼,通過 disruptor() 方法創建和裝配 一個Disruptor對象 ,Disruptor 裏邊有一個環形隊列。然後 disruptor() 方法給 Disruptor對象設置消費者,並且爲消費者設置異常處理器。

使用這一個簡單 DisruptorProducer 生產者

定義一個配置類,用於實例化 生產者

在這裏插入圖片描述

定義controller, 注入這個 生產者,就可以異步發佈數據 給消費者了

在這裏插入圖片描述

springboot應用啓動之後, 可以通過 httpclient 工具,測試一下:

在這裏插入圖片描述

看一下測試數據

在這裏插入圖片描述

具體的代碼和,演示過程,後面參考尼恩錄製和發佈《尼恩Java面試寶典》配套視頻。

Disruptor:消費數據丟失問題的分析與解決

在處理高併發、大數據量等場景時,Disruptor雖然其高性能、低延遲,然而,在使用過程中,一些用戶可能會遇到消費數據丟失問題。

爲了解決這些問題,我們需要深入瞭解Disruptor的工作原理,並採取相應的解決方案。

消費數據丟失問題的根因

消費線程丟失問題通常發生在消費者處理速度跟不上生產者的時候。

由於Disruptor採用環形隊列來存儲數據,當隊列滿時,新的數據會覆蓋舊的數據。

Disruptor 中,生產和消費的index是long類型,即使100萬QPS的處理速度,也需要30萬年才能用完。

生產和消費的index 下標採取遞增的形式。不用擔心index溢出的問題。

生產和消費的index 是通過 取模, 映射到 ring 環形數據的。

在這裏插入圖片描述

如果消費者速度慢, 生產者快,消費跟不上,生產的index(/Sequence)就會越來越大,取模的時候,又會從0開始,去覆蓋ring前面的數據,從而導致沒有消費的數據被丟失。

在這裏插入圖片描述

從上圖可以看到,只要生產者 的Sequence 大於消費者 一個ring 的數量, 就開始 覆蓋舊的數據,也就是開始丟失數據。

消費數據丟失問題解決方案:

  1. 增加消費者數量:增加消費者線程的數量,可以並行處理更多的數據,提高消費速度。

    同時,合理配置消費者與生產者的數量比例,確保隊列生產者 的Sequence 不會大於消費者 一個ring 的數量。

  2. 增加ring環形數組的大小:通過增加數組的大小,從而保證一個環可以存放足夠多的數據,但這個可能會導致OOM。

  3. 剩餘容量監控與告警:
    通過Prometheus 對 remainingCapacity剩餘容量 進行實時監控,當remainingCapacity 超過80%(閾值)及時發出告警通知相關人員處理,進行微服務實例的 HPA 橫向擴容,或者進行 Disruptor 隊列進行動態擴容。

  4. Disruptor 動態擴容
    對 Disruptor 框架進行合理封裝,從單個Disruptor 隊列模式,變成 ringbuffer 數組的形式,並且可以結合nacos 或者 Zookeeper 這種發佈訂閱組件, 對 ringbuffer 數組 進行在線擴容。

總之,通過增加消費者數量、增加ring環形數組的大小、剩餘容量監控與告警, Disruptor 動態擴容等方式,可以有效解決 消費數據丟失問題。

高級版:Spring Boot + Prometheus 監控剩餘容量 大小

我們的微服務項目中使用了 spring boot,集成 prometheus。

在這裏插入圖片描述

我們可以通過將remainingCapacity 作爲指標暴露到 prometheus 中,通過如下代碼:

在這裏插入圖片描述

增加這個代碼之後,請求 /actuator/prometheus 之後,可以看到對應的返回:

在這裏插入圖片描述

這樣,當這個值低於20%,我們就認爲這個剩餘空間不夠,可以擴容了。

Disruptor 如何 動態擴容?

關於 Disruptor 動態擴容的方案,可以實現一個可以擴容的子類

在這裏插入圖片描述

定義一個環形隊列的數據

    private Disruptor<LongEvent>[] executors;

在構造函數中,初始化爲1:

    public ResizableDisruptorProducer() {
        executors = new Disruptor[1];
        executors[0] = disruptor();
        this.ringBuffer = executors[0].getRingBuffer();

    }

發佈事件的時候,通過取模的方式,確定使用executors 數組 其中的一個RingBuffer

在這裏插入圖片描述

在next方法,執行indx 取模和 獲取 ringbuffer 的操作

在這裏插入圖片描述

這裏參考了netty源碼裏邊 PowerOfTwoEventExecutorChooser 反應器選擇的方式,使用位運算取模,從而實現高性能取模。

什麼時候擴容呢? 當監控發現超過80%的閾值後,運維會收到告警,然後可以通過naocos、Zookeeper的發佈訂閱, 通知微服務進行擴容。

微服務 擴容要回調下面的risize方法

在這裏插入圖片描述

具體的代碼和,演示過程,後面參考尼恩錄製和發佈《尼恩Java面試寶典》配套視頻。

在使用Disruptor框架時,需要根據實際情況選擇合適的監控和擴容解決方案,並不斷優化和調整系統配置,以滿足日益增長的業務需求。

說在最後

以上的內容,如果大家能對答如流,如數家珍,基本上 面試官會被你 震驚到、吸引到。最終,讓面試官愛到 “不能自已、口水直流”。offer, 也就來了。

在面試之前,建議大家系統化的刷一波 5000頁《尼恩Java面試寶典》V174,在刷題過程中,如果有啥問題,大家可以來 找 40歲老架構師尼恩交流。

另外,如果沒有面試機會,可以找尼恩來幫扶、領路。

尼恩已經指導了大量的就業困難的小夥伴上岸,前段時間,幫助一個40歲+就業困難小夥伴拿到了一個年薪100W的offer,小夥伴實現了 逆天改命 。

技術自由的實現路徑:

實現你的 架構自由:

喫透8圖1模板,人人可以做架構

10Wqps評論中臺,如何架構?B站是這麼做的!!!

阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了

峯值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?

100億級訂單怎麼調度,來一個大廠的極品方案

2個大廠 100億級 超大流量 紅包 架構方案

… 更多架構文章,正在添加中

實現你的 響應式 自由:

響應式聖經:10W字,實現Spring響應式編程自由

這是老版本 《Flux、Mono、Reactor 實戰(史上最全)

實現你的 spring cloud 自由:

Spring cloud Alibaba 學習聖經》 PDF

分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關係(史上最全)

實現你的 linux 自由:

Linux命令大全:2W多字,一次實現Linux自由

實現你的 網絡 自由:

TCP協議詳解 (史上最全)

網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由!!

實現你的 分佈式鎖 自由:

Redis分佈式鎖(圖解 - 秒懂 - 史上最全)

Zookeeper 分佈式鎖 - 圖解 - 秒懂

實現你的 王者組件 自由:

隊列之王: Disruptor 原理、架構、源碼 一文穿透

緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)

緩存之王:Caffeine 的使用(史上最全)

Java Agent 探針、字節碼增強 ByteBuddy(史上最全)

實現你的 面試題 自由:

4800頁《尼恩Java面試寶典 》 40個專題

免費獲取11個技術聖經PDF:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章