java多線程之併發框架Disruptor

什麼是Disruptor

Disruptor是一個高性能的無鎖線程間通訊框架,作者英國LMAX公司,其用於自己一種新型零售金融交易平臺,它能夠以很低的延遲產生大量交易,這個系統是建立在JVM平臺上,其核心是一個業務邏輯處理器,它能夠在一個線程裏每秒處理6百萬訂單。業務邏輯處理器完全是運行在內存中,使用事件源驅動方式。業務邏輯處理器的核心是Disruptor。其最大的特點就是能夠在無鎖的情況下實現網絡的Queue併發操作。

Disruptor的特性

上面提到了Disruptor是一個無鎖的併發框架,那麼在多線程情況下如何避免線程間競爭的呢?答案就是在需要確保操作是線程安全的地方Disruptor使用CAS(Compare And Swap/Set)操作,這是一個CPU級別的指令,工作方式類似於樂觀鎖,跟新值的時候如果想要改變的值不再是原來的值,操作失敗(有其他操作已經改變了這個值),否則操作成功。
Disruptor在器數據結構上也非常特別使用了RingBuffer(環形緩衝區)來實現,是一種用於表示一個固定尺寸、頭尾相連的緩衝區的數據結構,適合緩存數據流。RingBuffer 通常採用數組實現,對 CPU 緩存友好,性能比鏈表好。
在這裏插入圖片描述
Disruptor的高性能還表現在其避免GC上,其利用環形緩衝區提前分配緩衝區元素類型對象,Ring Buffer會首先將整個緩衝區填滿爲Factory所產生的實例,新添加時只需要設置其中的值即可。

Disruptor入門例子

Disruptor是通過事件源通知方式進行消息通訊,使用Disruptor需要知道三個重要的角色:事件、生產者和消費者,生產者和消費者均可以一個或者多個。有這樣一個例子:在一個停車場的入口,每一輛車輛進來需要做三步處理,處理完後下一輛車才能進來,這三步分別是車輛數據入庫、車輛數據進入Kafka消息隊列、最後發送短信通知車主。通過分析我們可以知道車輛停車動作是事件,三步操作是消費者,而車輛則由生產者產生。

package com.luke.study.disruptor.event;

/**
 * @Descrtption 事件(消息)
 * @Author luke
 * @Date 2019/8/29
 **/
public class InParkingDataEvent {

    private String carLicense;

    public String getCarLicense() {
        return carLicense;
    }

    public void setCarLicense(String carLicense) {
        this.carLicense = carLicense;
    }

}
package com.luke.study.disruptor.handler;

import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.WorkHandler;
import com.luke.study.disruptor.event.InParkingDataEvent;

/**
 * @Descrtption ParkingDataInDbHandler 車輛數據存入數據庫處理器
 * @Author luke
 * @Date 2019/8/29
 **/
public class ParkingDataInDbHandler implements EventHandler<InParkingDataEvent>, WorkHandler<InParkingDataEvent> {

    @Override
    public void onEvent(InParkingDataEvent event) throws Exception {
        long threadId = Thread.currentThread().getId();
        String carLicense = event.getCarLicense();
        System.out.println(String.format("Thread Id %s save %s into db ....",threadId,carLicense));
    }

    @Override
    public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception {
        this.onEvent(event);
    }

}

package com.luke.study.disruptor.handler;

import com.lmax.disruptor.EventHandler;
import com.luke.study.disruptor.event.InParkingDataEvent;

/**
 * @Descrtption ParkingDataInSmsHandler 車輛數據發送到kafka處理器
 * @Author luke
 * @Date 2019/8/29
 **/
public class ParkingDataInKafkaHandler implements EventHandler<InParkingDataEvent> {

    @Override
    public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception {
        long threadId = Thread.currentThread().getId();
        String carLicense = event.getCarLicense();
        System.out.println(String.format("Thread Id %s send %s in plaza sms to kafka",threadId,carLicense));
    }

}

package com.luke.study.disruptor.handler;

import com.lmax.disruptor.EventHandler;
import com.luke.study.disruptor.event.InParkingDataEvent;

/**
 * @Descrtption ParkingDataInSmsHandler 車輛數據發送短信處理器
 * @Author luke
 * @Date 2019/8/29
 **/
public class ParkingDataInSmsHandler implements EventHandler<InParkingDataEvent> {

    @Override
    public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception {
        long threadId = Thread.currentThread().getId();
        String carLicense = event.getCarLicense();
        System.out.println(String.format("Thread Id %s send %s in plaza sms to user",threadId,carLicense));
    }

}

package com.luke.study.disruptor.publisher;

import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.dsl.Disruptor;
import com.luke.study.disruptor.event.InParkingDataEvent;
import java.util.concurrent.CountDownLatch;

/**
 * @Descrtption Publisher 事件發佈類(生產者)
 * @Author luke
 * @Date 2019/8/29
 **/
public class InParkingDataEventPublisher implements Runnable{

    private Disruptor<InParkingDataEvent> disruptor;

    private CountDownLatch latch;

    public InParkingDataEventPublisher(Disruptor<InParkingDataEvent> disruptor, CountDownLatch latch) {
        this.disruptor = disruptor;
        this.latch = latch;
    }

    /**模擬10輛車入場*/
    private static int LOOP = 100;

    @Override
    public void run() {
        InParkingDataEventTranslator tradeTranslator = new InParkingDataEventTranslator();
        for(int i = 0; i < LOOP; i++){
            disruptor.publishEvent(tradeTranslator);
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        latch.countDown();
        System.out.println("生產者寫完"+LOOP+"個消息");
    }

    class InParkingDataEventTranslator implements EventTranslator<InParkingDataEvent>{

        @Override
        public void translateTo(InParkingDataEvent event, long sequence) {
            this.generateTradeTransaction(event);
        }

        private InParkingDataEvent generateTradeTransaction(InParkingDataEvent event){
            int num = (int)(Math.random()*8000);
            num = num + 1000;
            event.setCarLicense("京Z"+num);
            System.out.println("Thread Id "+Thread.currentThread().getId()+" 寫完一個event");
            return event;
        }

    }

}

package com.luke.study.disruptor.consumer;

import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.EventHandlerGroup;
import com.luke.study.disruptor.event.InParkingDataEvent;
import com.luke.study.disruptor.handler.ParkingDataInDbHandler;
import com.luke.study.disruptor.handler.ParkingDataInKafkaHandler;
import com.luke.study.disruptor.handler.ParkingDataInSmsHandler;

/**
 * @Descrtption 事件消費者
 * @Author luke
 * @Date 2019/8/29
 **/
public class InParkingDataEventConsumer {

    /**
     * 監聽事件
     * @param disruptor
     */
    public void  listenEvent(Disruptor<InParkingDataEvent> disruptor){
        //創建消費者組
        EventHandlerGroup<InParkingDataEvent> handlerGroup =
                disruptor.handleEventsWith(new ParkingDataInKafkaHandler(),new ParkingDataInDbHandler());
        ParkingDataInSmsHandler smsHandler = new ParkingDataInSmsHandler();
            handlerGroup.then(smsHandler);
    }

}

package com.luke.study.disruptor;

import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.luke.study.disruptor.consumer.InParkingDataEventConsumer;
import com.luke.study.disruptor.event.InParkingDataEvent;
import com.luke.study.disruptor.publisher.InParkingDataEventPublisher;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Descrtption MainTest 一個生產者三個訂閱者(消費者)
 * Disruptor:其本質是一種無鎖線程間通訊框架
 * Disruptor 唯一需要處理訪問衝突的地方,是多個生產者寫入 Ring Buffer 的場景
 * 參考:https://blog.csdn.net/mawming/article/details/52582776
 * @Author luke
 * @Date 2019/8/29
 **/
public class MainTest {

    public static void main(String[] args) throws InterruptedException {
        long beginTime = System.currentTimeMillis();

        //構建disruptor
        int bufferSize = 1024;
        ExecutorService executor = Executors.newFixedThreadPool(4);
        Disruptor<InParkingDataEvent> disruptor = new Disruptor<>(() -> new InParkingDataEvent(),
                bufferSize,Executors.defaultThreadFactory(),ProducerType.SINGLE,new YieldingWaitStrategy());

        //消費者開始監聽事件
        InParkingDataEventConsumer consumer = new InParkingDataEventConsumer();
        consumer.listenEvent(disruptor);

        //生產者生產消息
        disruptor.start();
        CountDownLatch latch = new CountDownLatch(1);
        executor.submit(new InParkingDataEventPublisher(disruptor,latch));
        latch.await();

        disruptor.shutdown();
        executor.shutdown();
        System.out.println("總耗時:"+(System.currentTimeMillis()-beginTime)/1000+"秒");

    }

}

例子結果

Thread Id 16 寫完一個event
Thread Id 13 send 京Z8890 in plaza sms to kafka
Thread Id 14 save 京Z8890 into db …
Thread Id 15 send 京Z8890 in plaza sms to user
Thread Id 16 寫完一個event
Thread Id 14 save 京Z4691 into db …
Thread Id 13 send 京Z4691 in plaza sms to kafka
Thread Id 15 send 京Z4691 in plaza sms to user
Thread Id 16 寫完一個event
Thread Id 13 send 京Z7250 in plaza sms to kafka
Thread Id 14 save 京Z7250 into db …
Thread Id 15 send 京Z7250 in plaza sms to user
Thread Id 16 寫完一個event
Thread Id 14 save 京Z2750 into db …
Thread Id 13 send 京Z2750 in plaza sms to kafka
Thread Id 15 send 京Z2750 in plaza sms to user
生產者寫完100個消息
總耗時:100秒

參考

http://ifeve.com/disruptor/
https://www.cnblogs.com/shenck/p/4002456.html
https://blog.csdn.net/mawming/article/details/52582776

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