• 基于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

  • 相关阅读:
    html控件使用
    托盤
    托盘的实现
    ws2s函数
    网络验证
    右上角X灰化
    如何模拟一个http请求并把response的内容保存下载下来,导出到excel中(结尾福利)
    排序的几种算法(一):冒泡排序
    python中的break eturnpasscontinue用法
    python中socket模块详解
  • 原文地址:https://www.cnblogs.com/bizzan/p/12661279.html
Copyright © 2020-2023  润新知