基於Java的數字貨幣交易系統的架構設計與開發


前言

無論是股票交易系統,還是數字貨幣交易系統,都離不開撮合交易引擎,這是交易平台的心臟。同時,一個優秀的架構設計也會讓交易平台的運維和持續開發更加容易。本文基於對開源項目的深入研究,總結了數字貨幣交易系統的架構設計。

 本文參考了開源項目:https://gitee.com/cexchange/CoinExchange

 

關於撮合交易系統

撮合技術主要是從數據庫撮合技術向內存撮合技術發展,這是因為數據庫撮合技術越來越無法滿足金融交易對於高可靠性、高性能、強安全性、可擴展性以及易維護性的需求。金融(幣幣)交易撮合系統中包括以下幾個核心模塊:

  • 用戶:終端用戶委托報價與數量,生成訂單發送至交易平台。
  • 網關:負責收集用戶訂單,並將其派發給撮合引擎。
  • 撮合引擎:交易系統中的核心部分,用於接收訂單並根據業務邏輯實現訂單 撮合同時生成交易記錄,隨后給予用戶交易結果反饋。
  • 數據庫:用來存放交易過程中的訂單和交易記錄,實現數據持久化。
  • 消息隊列:一般用於訂單消息的傳輸

關於技術選型

一個交易所平台的技術架構主要考慮安全性、分布式、易擴展、容錯性、低延時、高並發等特性,以及熔斷機制、服務注冊和發現、消息服務、服務網關、安全認證、內存數據庫、關系型數據庫等各種選項,最終形成了如下技術選型:

  1.  分布式基礎進行架構SpringCloud與Dubbo之間二選一,由於SpringCloud更加知名,SpringCloud的程序員更好招聘,有利於系統的長期運維升級,而且SpringCloud是基於SpringBoot開發,比較有親切感,所以選擇了SpringCloud, 其實由於阿里系的強大影響,國內Dubbo使用更加廣泛,不同的團隊可以根據自己的情況選擇。
  2. 引入Hystrix斷路器作為容錯保護模塊,防止單個服務的故障,耗盡整個撮合系統容器的線程資源,避免分布式環境里大量級聯失敗。對通過第三方客戶端訪問依賴服務出現失敗、拒絕、超時或短路時執行回退邏輯。
  3. 采用Eureka作為服務注冊與發現中心,實現中間層服務,以達到負載均衡和中間層服務故障轉移的目的。
  4. 服務網關Spring Cloud Gateway 與 Zuul 的選型,選擇了Zuul,因為名字短一些。
  5. 引入SpringCloud Security安全認證模塊用於構建安全的應用程序和服務,SpringCloud Security在Spring Boot和Spring Security OAuth2的基礎上,可以快速創建和實現常見的安全認證方式,如單點登錄,令牌中繼和令牌交換等。
  6. 引入Redis作為內存數據庫,兼做系統數據緩存和內存計算。
  7. 使用MySQL作為關系數據庫,性能測試非常過關,而且對熟悉MYSQL的程序員非常友好。
  8. 消息隊列中間件MQ采用了Kafka, 具有超高性能體現。

關於交易所架構設計

基於SpringCloud開發基於微服務架構的交易平台,首先需要對SpringCloud的基礎架構有所了解,我們熟知的SpringCloud微服務架構如下圖所示:

由於篇幅關系,本文就不對SpringCloud的技術架構進行詳細解讀了。

在SpringCloud這個優秀的微服務框架基礎之上,如何構建一個交易系統呢?開源項目CoinExchange對交易所的架構做了如下架構設計:

將撮合交易引擎、API等拆分作為單獨的服務,基於SpringCloud構建了一個精簡的交易所架構。

部署圖如下:

 

關於撮合交易引擎

采用內存撮合的方式進行,以Kafka做撮合訂單信息傳輸,MongoDB持久化訂單成交明細,MySQL記錄訂單總體成交。其中行情模塊主要負責訂單成交持久化、行情生成、行情推送等服務,包括:

  • K線數據,間隔分別為:1分鍾、5分鍾、15分鍾、30分鍾、1小時、1天、1周、1月
  • 所有交易對的市場深度(market depth)數據
  • 所有交易對的最新價格
  • 最近成交的交易對

內存撮合交易支持的模式

  • 限價訂單與限價訂單撮合
  • 市價訂單與限價訂單撮合
  • 限價訂單與市價訂單撮合
  • 市價訂單與市價訂單撮合

撮合邏輯過程如下圖所示:

示例代碼如下:

  1     /**
  2      * 限價委托單與限價隊列匹配
  3      * @param lpList 限價對手單隊列
  4      * @param focusedOrder 交易訂單
  5      */
  6     public void matchLimitPriceWithLPList(TreeMap<BigDecimal,MergeOrder> lpList, ExchangeOrder focusedOrder,boolean canEnterList){
  7         List<ExchangeTrade> exchangeTrades = new ArrayList<>();
  8         List<ExchangeOrder> completedOrders = new ArrayList<>();
  9         synchronized (lpList) {
 10             Iterator<Map.Entry<BigDecimal,MergeOrder>> mergeOrderIterator = lpList.entrySet().iterator();
 11             boolean exitLoop = false;
 12             while (!exitLoop && mergeOrderIterator.hasNext()) {
 13                 Map.Entry<BigDecimal,MergeOrder> entry = mergeOrderIterator.next();
 14                 MergeOrder mergeOrder = entry.getValue();
 15                 Iterator<ExchangeOrder> orderIterator = mergeOrder.iterator();
 16                 //買入單需要匹配的價格不大於委托價,否則退出
 17                 if (focusedOrder.getDirection() == ExchangeOrderDirection.BUY && mergeOrder.getPrice().compareTo(focusedOrder.getPrice()) > 0) {
 18                     break;
 19                 }
 20                 //賣出單需要匹配的價格不小於委托價,否則退出
 21                 if (focusedOrder.getDirection() == ExchangeOrderDirection.SELL && mergeOrder.getPrice().compareTo(focusedOrder.getPrice()) < 0) {
 22                     break;
 23                 }
 24                 while (orderIterator.hasNext()) {
 25                     ExchangeOrder matchOrder = orderIterator.next();
 26                     //處理匹配
 27                     ExchangeTrade trade = processMatch(focusedOrder, matchOrder);
 28                     exchangeTrades.add(trade);
 29                     //判斷匹配單是否完成
 30                     if (matchOrder.isCompleted()) {
 31                         //當前匹配的訂單完成交易,刪除該訂單
 32                         orderIterator.remove();
 33                         completedOrders.add(matchOrder);
 34                     }
 35                     //判斷交易單是否完成
 36                     if (focusedOrder.isCompleted()) {
 37                         //交易完成
 38                         completedOrders.add(focusedOrder);
 39                         //退出循環
 40                         exitLoop = true;
 41                         break;
 42                     }
 43                 }
 44                 if(mergeOrder.size() == 0){
 45                     mergeOrderIterator.remove();
 46                 }
 47             }
 48         }
 49         //如果還沒有交易完,訂單壓入列表中
 50         if (focusedOrder.getTradedAmount().compareTo(focusedOrder.getAmount()) < 0 && canEnterList) {
 51             addLimitPriceOrder(focusedOrder);
 52         }
 53         //每個訂單的匹配批量推送
 54         handleExchangeTrade(exchangeTrades);
 55         if(completedOrders.size() > 0){
 56             orderCompleted(completedOrders);
 57             TradePlate plate = focusedOrder.getDirection() == ExchangeOrderDirection.BUY ? sellTradePlate : buyTradePlate;
 58             sendTradePlateMessage(plate);
 59         }
 60     }
 61 
 62     /**
 63      * 限價委托單與市價隊列匹配
 64      * @param mpList 市價對手單隊列
 65      * @param focusedOrder 交易訂單
 66      */
 67     public void matchLimitPriceWithMPList(LinkedList<ExchangeOrder> mpList,ExchangeOrder focusedOrder){
 68         List<ExchangeTrade> exchangeTrades = new ArrayList<>();
 69         List<ExchangeOrder> completedOrders = new ArrayList<>();
 70         synchronized (mpList) {
 71             Iterator<ExchangeOrder> iterator = mpList.iterator();
 72             while (iterator.hasNext()) {
 73                 ExchangeOrder matchOrder = iterator.next();
 74                 ExchangeTrade trade = processMatch(focusedOrder, matchOrder);
 75                 logger.info(">>>>>"+trade);
 76                 if(trade != null){
 77                     exchangeTrades.add(trade);
 78                 }
 79                 //判斷匹配單是否完成,市價單amount為成交量
 80                 if(matchOrder.isCompleted()){
 81                     iterator.remove();
 82                     completedOrders.add(matchOrder);
 83                 }
 84                 //判斷吃單是否完成,判斷成交量是否完成
 85                 if (focusedOrder.isCompleted()) {
 86                     //交易完成
 87                     completedOrders.add(focusedOrder);
 88                     //退出循環
 89                     break;
 90                 }
 91             }
 92         }
 93         //如果還沒有交易完,訂單壓入列表中
 94         if (focusedOrder.getTradedAmount().compareTo(focusedOrder.getAmount()) < 0) {
 95             addLimitPriceOrder(focusedOrder);
 96         }
 97         //每個訂單的匹配批量推送
 98         handleExchangeTrade(exchangeTrades);
 99         orderCompleted(completedOrders);
100     }
101 
102 
103     /**
104      * 市價委托單與限價對手單列表交易
105      * @param lpList  限價對手單列表
106      * @param focusedOrder 待交易訂單
107      */
108     public void matchMarketPriceWithLPList(TreeMap<BigDecimal,MergeOrder> lpList, ExchangeOrder focusedOrder){
109         List<ExchangeTrade> exchangeTrades = new ArrayList<>();
110         List<ExchangeOrder> completedOrders = new ArrayList<>();
111         synchronized (lpList) {
112             Iterator<Map.Entry<BigDecimal,MergeOrder>> mergeOrderIterator = lpList.entrySet().iterator();
113             boolean exitLoop = false;
114             while (!exitLoop && mergeOrderIterator.hasNext()) {
115                 Map.Entry<BigDecimal,MergeOrder> entry = mergeOrderIterator.next();
116                 MergeOrder mergeOrder = entry.getValue();
117                 Iterator<ExchangeOrder> orderIterator = mergeOrder.iterator();
118                 while (orderIterator.hasNext()) {
119                     ExchangeOrder matchOrder = orderIterator.next();
120                     //處理匹配
121                     ExchangeTrade trade = processMatch(focusedOrder, matchOrder);
122                     if (trade != null) {
123                         exchangeTrades.add(trade);
124                     }
125                     //判斷匹配單是否完成
126                     if (matchOrder.isCompleted()) {
127                         //當前匹配的訂單完成交易,刪除該訂單
128                         orderIterator.remove();
129                         completedOrders.add(matchOrder);
130                     }
131                     //判斷焦點訂單是否完成
132                     if (focusedOrder.isCompleted()) {
133                         completedOrders.add(focusedOrder);
134                         //退出循環
135                         exitLoop = true;
136                         break;
137                     }
138                 }
139                 if(mergeOrder.size() == 0){
140                     mergeOrderIterator.remove();
141                 }
142             }
143         }
144         //如果還沒有交易完,訂單壓入列表中,市價買單按成交量算
145         if (focusedOrder.getDirection() == ExchangeOrderDirection.SELL&&focusedOrder.getTradedAmount().compareTo(focusedOrder.getAmount()) < 0
146                 || focusedOrder.getDirection() == ExchangeOrderDirection.BUY&& focusedOrder.getTurnover().compareTo(focusedOrder.getAmount()) < 0) {
147             addMarketPriceOrder(focusedOrder);
148         }
149         //每個訂單的匹配批量推送
150         handleExchangeTrade(exchangeTrades);
151         if(completedOrders.size() > 0){
152             orderCompleted(completedOrders);
153             TradePlate plate = focusedOrder.getDirection() == ExchangeOrderDirection.BUY ? sellTradePlate : buyTradePlate;
154             sendTradePlateMessage(plate);
155         }
156     }

 

關於區塊鏈錢包對接

每個幣種對應不同的數據訪問方式,大部分區塊鏈項目的錢包操作方式是相同的或十分相似的,比如BTC、LTC、BCH、BSV、BCD等比特幣衍生幣,其API操作方式幾乎一樣;再比如ETH,當你掌握一個合約幣種的操作,其他基於ETH發行的數字貨幣的操作方式幾乎一樣。所以,基本上當你花時間弄懂了一個,就懂了一堆幣種。

本項目使用的錢包操作方案也是不同的,也盡可能的為大家展示了不同用法:

  • 如BTC、USDT,使用的自建全節點,現在差不多需要300G硬盤空間;
  • 如ETH,使用的是自建輕節點(參考文章),因為全節點需要硬盤空間太大;
  • 如BCH、BSV等,使用的是第三方區塊鏈瀏覽器獲取數據;
  • 如XRP,官方就已經提供了訪問區塊數據的接口(Ripple API GitHub地址

一般而言,當交易所來往資金量不大的時候,你可以自己摸索,但是當交易所資金量大了以后,如果你對自己操作錢包不太放心,你也可以使用第三方的錢包服務,當然,這需要你與錢包服務商進行談判,付個年費什么的。

下圖是關於交易平台充值邏輯的一個簡單時序圖:

 

總結

通過以上的說明及圖示,我們基本上對交易所的整體架構有了一定的認知。

 

感謝

最后感謝開源交易所項目給與我學習的機會!

Java開源交易平台項目:https://gitee.com/cexchange/CoinExchange


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM