12

基于Java的数字货币交易系统的架构设计与开发

 4 years ago
source link: http://www.cnblogs.com/bizzan/p/12661279.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

无论是股票交易系统,还是数字货币交易系统,都离不开撮合交易引擎,这是交易平台的心脏。同时,一个优秀的架构设计也会让交易平台的运维和持续开发更加容易。本文基于对开源项目的深入研究,总结了数字货币交易系统的架构设计。

关于撮合交易系统

撮合技术主要是从数据库撮合技术向内存撮合技术发展,这是因为数据库撮合技术越来越无法满足金融交易对于高可靠性、高性能、强安全性、可扩展性以及易维护性的需求。金融(币币)交易撮合系统中包括以下几个核心模块:

  • 用户:终端用户委托报价与数量,生成订单发送至交易平台。
  • 网关:负责收集用户订单,并将其派发给撮合引擎。
  • 撮合引擎:交易系统中的核心部分,用于接收订单并根据业务逻辑实现订单 撮合同时生成交易记录,随后给予用户交易结果反馈。
  • 数据库:用来存放交易过程中的订单和交易记录,实现数据持久化。
  • 消息队列:一般用于订单消息的传输

关于技术选型

一个交易所平台的技术架构主要考虑安全性、分布式、易扩展、容错性、低延时、高并发等特性,以及熔断机制、服务注册和发现、消息服务、服务网关、安全认证、内存数据库、关系型数据库等各种选项,最终形成了如下技术选型:

  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微服务架构如下图所示:

mmmAFvf.png!web

由于篇幅关系,本文就不对SpringCloud的技术架构进行详细解读了。

在SpringCloud这个优秀的微服务框架基础之上,如何构建一个交易系统呢?开源项目CoinExchange对交易所的架构做了如下架构设计:

3ARjuqn.png!web

将撮合交易引擎、API等拆分作为单独的服务,基于SpringCloud构建了一个精简的交易所架构。

部署图如下:

rY3ARrB.png!web

关于撮合交易引擎

采用内存撮合的方式进行,以Kafka做撮合订单信息传输,MongoDB持久化订单成交明细,MySQL记录订单总体成交。其中行情模块主要负责订单成交持久化、行情生成、行情推送等服务,包括:

  • K线数据,间隔分别为:1分钟、5分钟、15分钟、30分钟、1小时、1天、1周、1月
  • 所有交易对的市场深度(market depth)数据
  • 所有交易对的最新价格
  • 最近成交的交易对

内存撮合交易支持的模式

  • 限价订单与限价订单撮合
  • 市价订单与限价订单撮合
  • 限价订单与市价订单撮合
  • 市价订单与市价订单撮合

撮合逻辑过程如下图所示:

2uUnuar.png!web

示例代码如下:

  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地址

一般而言,当交易所来往资金量不大的时候,你可以自己摸索,但是当交易所资金量大了以后,如果你对自己操作钱包不太放心,你也可以使用第三方的钱包服务,当然,这需要你与钱包服务商进行谈判,付个年费什么的。

下图是关于交易平台充值逻辑的一个简单时序图:

iqeEJrY.png!web

总结

通过以上的说明及图示,我们基本上对交易所的整体架构有了一定的认知。

感谢

最后感谢开源交易所项目给与我学习的机会!

Java开源交易平台项目: https://gitee.com/cexchange/CoinExchange


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK