match-trade最高效的交易所撮合引擎

介紹

match-trade超高效的交易所撮合引擎,採用倫敦外匯交易所LMAX開源的Disruptor框架,用Hazelcast進行分佈式內存存取,以及原子性操作。使用數據流的方式進行計算撮合序列,才用價格水平獨立撮合邏輯,實現高效大數據撮合。

優勢

  • match-engine水平價格為獨立撮合邏輯,相比於訂單隊列為撮合隊列的交易引擎來說,價格區間越小時,性能越優越。
  • match-engine不再對撮合薄進行排序,而是用並行流計算出最優撮合價格,進行撮合。
  • match-engine每個價格下的訂單都是異步完成被撮合。獨立價格下訂單不影響下一個新發生的撮合。
  • match-engine每個價格撮合都是獨立的,與下一個價格沒的關係,實現快速吃單。
  • match-engine每個新的訂單經歷撮合處理器後,後續邏輯採用並行計算,能更快速反饋數據撮合結果。
  • match-engine使用數據流反應式MQ消費,降低由MQ帶來的數據延遲。
  • match-engine撤單走獨立的邏輯,不用和下單在一個處理序列。

技術選擇

  • Disruptor: 號稱每秒鐘承載600萬訂單級別的無鎖並行計算框架,主要選擇原因還是並行計算。
  • Hazelcast: 很好進行內存處理,很強原子性保障的操作能力。同時分佈式內存實現很簡單,能自動內存集群。據說火幣也在用。
  • rocketmq: 消息可以做到0丟失,支持10億級別的消息堆積,不會因堆積導致性能下降,主要是經過雙11檢驗
  • WebFlux: 它能夠充分利用多核 CPU 的硬件資源去處理大量的併發請求。

描述

用戶輸入包括:

  • 創建新的委託單(NewOrder):一個新的委託單可以作為交易撮合引擎的輸入,引擎會嘗試將其與已有的 委託單進行撮合。
  • 取消已有的委託單(CancelOrder):用戶也可以取消一個之前輸入的委託單,如果它還沒有執行的話,即開口訂單。

委託單:

  • 限價委託單
    限價委託單是在當前的加密貨幣交易環境中最常用的委託類型。這種委託單允許用戶指定一個價格,只有當撮合引擎找到同樣價格甚至更好價格的對手單時才執行交易。
  • 市價委託單

    市價委託單的撮合會完全忽略價格因素,而致力於有限完成指定數量的成交。市價委託單在交易委託賬本中有較高的優先級,在流動性充足的市場中市價單可以保證成交。不充足時,撮合完最後一條撤銷。
  • 止損委託單
    止損委託單盡在市場價格到達指定價位時才被激活,因此它的執行方式與市價委託單相反。一旦止損委託單激活,它們可以自動轉化為市價委託單或限價委託單。(未實現)

撮合流程

限價撮合:

match-trade最高效的交易所撮合引擎

市價撮合:

match-trade最高效的交易所撮合引擎

目前就實現這兩種訂單撮合

代碼執行流程圖(感謝讀者提供的圖)

match-trade最高效的交易所撮合引擎

訂單簿為撮合簿時代碼解析

這個是一個簡單流盤口計算demo

<code>//獲取匹配的訂單薄數據
IMap<long> outMap = hzInstance.getMap(HzltUtil.getMatchKey(coinTeam, isBuy));
/**
* -★

* -使用Java 8 Stream API中的並行流來計算最優
* -能快速的拿到撮合對象,不用排序取值,降低性能消耗
*/
Order outOrder = outMap.values().parallelStream().min(HzltUtil::compareOrder).get();

//這種方式最難的,就是整理盤口深度數據了

/**
* -★
\t * -獲取行情深度
\t *
\t * @param coinTeam 交易隊
\t * @param isBuy 是否是買
\t * @return List<depth>
\t */
\tpublic List<depth> getMarketDepth(String coinTeam, Boolean isBuy) {
\t\tList<depth> depths = new ArrayList<depth>();
\t\tIMap<long> map = hzInstance.getMap(HzltUtil.getMatchKey(coinTeam, isBuy));
\t\tif (map.size() > 0) {
\t\t\t/**
\t\t\t * -這個流:主要是安價格分組和統計,使用並行流快速歸集。
\t\t\t */
\t\t\tList<depth> list = map.entrySet().parallelStream().map(mo -> mo.getValue())
\t\t\t\t\t.collect(Collectors.groupingBy(Order::getPrice)).entrySet().parallelStream()
\t\t\t\t\t.map(ml -> new Depth(ml.getKey().toString(),
\t\t\t\t\t\t\tml.getValue().stream().map(o -> o.getUnFinishNumber()).reduce(BigDecimal.ZERO, BigDecimal::add)
\t\t\t\t\t\t\t\t\t.toString(),
\t\t\t\t\t\t\t"0", 1, coinTeam, isBuy))
\t\t\t\t\t.sorted((d1, d2) -> HzltUtil.compareTo(d1, d2)).collect(Collectors.toList());
\t\t\t/**
\t\t\t * -這個流:主要是盤口的累計計算,因涉及排序選擇串行流
\t\t\t */
\t\t\tlist.stream().reduce(new Depth("0", "0", "0", 1, coinTeam, isBuy), (one, two) -> {
\t\t\t\tone.setTotal((new BigDecimal(one.getTotal()).add(new BigDecimal(two.getNumber()))).toString());
\t\t\t\tdepths.add(new Depth(two.getPrice(), two.getNumber(), one.getTotal(), two.getPlatform(),
\t\t\t\t\t\ttwo.getCoinTeam(), two.getIsBuy()));
\t\t\t\treturn one;
\t\t\t});
\t\t} else {

\t\t\tDepth depth = new Depth("0", "0", "0", 1, coinTeam, isBuy);
\t\t\tdepths.add(depth);
\t\t}
\t\treturn depths;
\t}/<depth>/<long>/<depth>/<depth>/<depth>/<depth>/<long>/<code>

測試結果

在我8cpu,16G內存的開發win10系統上測試結果:

  • Disruptor單生產者初始化10萬不能撮合的訂單耗時:約700毫秒
  • Disruptor多生產者初始化10萬不能撮合的訂單耗時:約20秒
  • 實際單吃完1-100價格內隨機數量的10萬訂單耗時:約400毫秒


分享到:


相關文章: