使用Disruptor處理多請求響應 1. Disruptor簡介 2.問題的提出 3.代碼實現 4 小結

1. Disruptor簡介

  Disruptor是一個用於在線程間通信的高效低延時的消息組件,像個增強的隊列,是LMAX 公司在開發金融交易系統中的一個關鍵創新。
  關於Disruptor的相關知識可以從“http://lmax-exchange.github.io/disruptor/”處獲取。Disruptor是一個輕量的框架組件,其類圖如下圖所示:


  Disruptor以RingBuffer爲核心,發佈、處理註冊到其上的事件。初識Disruptor,我的感覺是很像nodejs的請求處理框架和windows的窗口過程的處理方式。調用方不斷在Disruptor上發佈事件,而Disruptor則通過預置的事件處理函數(消費者)來快速處理這些事件。幸運的是,Disruptor提供了多種靈活的消費模式,可用於解決一些常見問題。
  本文中,我們希望先通過一個使用Disruptor的應用實例先熟悉Disruptor的使用,再去剖析Disruptor的架構和實現原理。

2.問題的提出

  在分佈式系統中,經常會出現同一條信令發往多個節點,匯聚這多個節點的響應後,再一併發給調用方的場景。
  使用Disruptor的消費者模型,如下圖所示:



  在這個模型中,P1是事件發佈(生產)過程,即將要發送的信令發佈到Disruptor上,C1和C2是第一層消費者,即將信令發送到不同的節點(C1和C2代表不同節點),C3是第二層消費者,待C1和C2都完成之後才執行,即匯聚來自不同的節點的響應並返回給調用方。

3.代碼實現

  示例代碼見https://github.com/solarkai/Disruptor4MultiResponseExample

3.1 SignalDisruptorService

  實現disruptor的啓動,停止,事件註冊等操作。
  該服務中,定義了等待信令響應的信號量全局MAP,和信令返回響應的全局MAP,如下:

@Getter
    // key爲消息的唯一標識,value爲本條信令對應的信號量
    private final ConcurrentHashMap<Long, CountDownLatch> cdlMap = new ConcurrentHashMap<Long, CountDownLatch>();

    @Getter
    // key爲消息的唯一標識,value爲本條信令對應的迴應列表(來自多網)
    private final ConcurrentHashMap<Long, List<SignalResponse>> responseMap = new ConcurrentHashMap<Long, List<SignalResponse>>();

  在啓動disruptor時,根據配置的節點動態構建第一層消費者(發送信令),代碼如下:

if (!isDisruptorStarted) {
            Send2NodeEventHandler[] send2NodeEventHandlerArray = new Send2NodeEventHandler[nodeNameList.size()];
            for (int i = 0; i < nodeNameList.size(); i++) {
                String nodeName = nodeNameList.get(i);
                Send2NodeEventHandler handler = new Send2NodeEventHandler();
                handler.setNodeName(nodeName);
                send2NodeEventHandlerArray[i] = handler;
            }

            // 設置消費者依賴圖
            disruptor.handleEventsWith(send2NodeEventHandlerArray).then(rceh);
            disruptor.start();
            isDisruptorStarted = true;
        }

3.2 事件處理定義

  信令的請求和響應通過信令的唯一標示ID字段關聯。第一層消費者使用Send2NodeEventHandler定義,只完成發送信令到節點的處理,第二層消費者使用ResponseCollectionEventHandler定義,完成信令在各節點響應的匯聚工作。
  在disputor上發佈一條信令事件的代碼如下,發佈一條信令後,同時也初始化了該條信令對應的信號量(CountDownLatch):

RingBuffer<SignalSendEvent> rb = disruptor.getRingBuffer();
        long sequence = rb.next();
        try {
            SignalSendEvent event = rb.get(sequence);
            event.setDsm(dsm);

            // 放入回調函數可訪問的map
            this.cdlMap.put(dsm.getId(), new CountDownLatch(nodeNameList.size()));
            this.responseMap.put(dsm.getId(), new ArrayList<SignalResponse>());
        } finally {
            rb.publish(sequence);
        }
        return sequence;

  ResponseCollectionEventHandler在此信令對應的信號量上等待,匯聚所有響應,代碼如下:

long id = event.getDsm().getId();
        // 阻塞等待所有節點對信令的響應
        CountDownLatch latch = sds.getCdlMap().get(id);
        if (null != latch) {
            log.info("start to wait response by id:{}",id);
            latch.await(SIGNAL_TIMEOUT, TimeUnit.MILLISECONDS);
            // 阻塞結束,處理所有響應
            List<SignalResponse> respList = sds.getResponseMap().get(id);
            handleRespList(respList);
            // 清除資源
            sds.getCdlMap().remove(event.getDsm().getId());
            sds.getResponseMap().remove(event.getDsm().getId());
        } else {
            log.error("event not set latch:{}", event);
        }

3.3 模擬信令響應

  使用SignalDisruptorController中的函數responseSignal模擬節點對信令的響應,代碼如下:

@RequestMapping(value = "/responsesignal", method = RequestMethod.POST)
    @ApiOperation(value = "siganl響應", notes = "siganl響應")
    public long responseSignal(@RequestBody(required = true) SignalResponse sr) {
        long id = sr.getId();
        CountDownLatch cdl = sds.getCdlMap().get(id);
        if (null != cdl) {
            cdl.countDown();
        }
        List<SignalResponse> respList = sds.getResponseMap().get(id);
        if (null != respList) {
            respList.add(sr);
        }
        return id;
    }

3.4 執行結果

  執行該代碼,發現在模擬一條信令發佈後,disruptor執行第一層消費者,使用3個線程(代碼中配了3個節點)分別模擬發送信令過程,第二層消費者(響應匯聚)在第一層消費者都執行完之後再執行,並分配到一個新的線程。
  從代碼執行打印,可以看到同一個EventHandler一直在同一個線程上執行,因而在編程中需關注某個事件的EventHandler處理時間過長會阻塞後一個事件的處理。

4 小結

  通過disruptor的消費依賴圖定義,的確簡化了多請求響應的代碼處理。後面我們會再根據執行結果分析一下Disruptor的實現原理。

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