基于Java的数字货币交易系统的架构设计与开发
前言
无论是股票交易系统,还是数字货币交易系统,都离不开撮合交易引擎,这是交易平台的心脏。同时,一个优秀的架构设计也会让交易平台的运维和持续开发更加容易。本文基于对开源项目的深入研究,总结了数字货币交易系统的架构设计。
本文参考了开源项目:https://gitee.com/cexchange/CoinExchange
关于撮合交易系统
撮合技术主要是从数据库撮合技术向内存撮合技术发展,这是因为数据库撮合技术越来越无法满足金融交易对于高可靠性、高性能、强安全性、可扩展性以及易维护性的需求。金融(币币)交易撮合系统中包括以下几个核心模块:
- 用户:终端用户委托报价与数量,生成订单发送至交易平台。
- 网关:负责收集用户订单,并将其派发给撮合引擎。
- 撮合引擎:交易系统中的核心部分,用于接收订单并根据业务逻辑实现订单 撮合同时生成交易记录,随后给予用户交易结果反馈。
- 数据库:用来存放交易过程中的订单和交易记录,实现数据持久化。
- 消息队列:一般用于订单消息的传输
关于技术选型
一个交易所平台的技术架构主要考虑安全性、分布式、易扩展、容错性、低延时、高并发等特性,以及熔断机制、服务注册和发现、消息服务、服务网关、安全认证、内存数据库、关系型数据库等各种选项,最终形成了如下技术选型:
- 分布式基础进行架构SpringCloud与Dubbo之间二选一,由于SpringCloud更加知名,SpringCloud的程序员更好招聘,有利于系统的长期运维升级,而且SpringCloud是基于SpringBoot开发,比较有亲切感,所以选择了SpringCloud, 其实由于阿里系的强大影响,国内Dubbo使用更加广泛,不同的团队可以根据自己的情况选择。
- 引入Hystrix断路器作为容错保护模块,防止单个服务的故障,耗尽整个撮合系统容器的线程资源,避免分布式环境里大量级联失败。对通过第三方客户端访问依赖服务出现失败、拒绝、超时或短路时执行回退逻辑。
- 采用Eureka作为服务注册与发现中心,实现中间层服务,以达到负载均衡和中间层服务故障转移的目的。
- 服务网关Spring Cloud Gateway 与 Zuul 的选型,选择了Zuul,因为名字短一些。
- 引入SpringCloud Security安全认证模块用于构建安全的应用程序和服务,SpringCloud Security在Spring Boot和Spring Security OAuth2的基础上,可以快速创建和实现常见的安全认证方式,如单点登录,令牌中继和令牌交换等。
- 引入Redis作为内存数据库,兼做系统数据缓存和内存计算。
- 使用MySQL作为关系数据库,性能测试非常过关,而且对熟悉MYSQL的程序员非常友好。
- 消息队列中间件MQ采用了Kafka, 具有超高性能体现。
关于交易所架构设计
基于SpringCloud开发基于微服务架构的交易平台,首先需要对SpringCloud的基础架构有所了解,我们熟知的SpringCloud微服务架构如下图所示:
由于篇幅关系,本文就不对SpringCloud的技术架构进行详细解读了。
在SpringCloud这个优秀的微服务框架基础之上,如何构建一个交易系统呢?开源项目CoinExchange对交易所的架构做了如下架构设计:
将撮合交易引擎、API等拆分作为单独的服务,基于SpringCloud构建了一个精简的交易所架构。
部署图如下:
关于撮合交易引擎
采用内存撮合的方式进行,以Kafka做撮合订单信息传输,MongoDB持久化订单成交明细,MySQL记录订单总体成交。其中行情模块主要负责订单成交持久化、行情生成、行情推送等服务,包括:
- K线数据,间隔分别为:1分钟、5分钟、15分钟、30分钟、1小时、1天、1周、1月
- 所有交易对的市场深度(market depth)数据
- 所有交易对的最新价格
- 最近成交的交易对
内存撮合交易支持的模式
- 限价订单与限价订单撮合
- 市价订单与限价订单撮合
- 限价订单与市价订单撮合
- 市价订单与市价订单撮合
撮合逻辑过程如下图所示:
示例代码如下:
/**
* 限价委托单与限价队列匹配
* @param lpList 限价对手单队列
* @param focusedOrder 交易订单
*/
public void matchLimitPriceWithLPList(TreeMap<BigDecimal,MergeOrder> lpList, ExchangeOrder focusedOrder,boolean canEnterList){
List<ExchangeTrade> exchangeTrades = new ArrayList<>();
List<ExchangeOrder> completedOrders = new ArrayList<>();
synchronized (lpList) {
Iterator<Map.Entry<BigDecimal,MergeOrder>> mergeOrderIterator = lpList.entrySet().iterator();
boolean exitLoop = false;
while (!exitLoop && mergeOrderIterator.hasNext()) {
Map.Entry<BigDecimal,MergeOrder> entry = mergeOrderIterator.next();
MergeOrder mergeOrder = entry.getValue();
Iterator<ExchangeOrder> orderIterator = mergeOrder.iterator();
//买入单需要匹配的价格不大于委托价,否则退出
if (focusedOrder.getDirection() == ExchangeOrderDirection.BUY && mergeOrder.getPrice().compareTo(focusedOrder.getPrice()) > 0) {
break;
}
//卖出单需要匹配的价格不小于委托价,否则退出
if (focusedOrder.getDirection() == ExchangeOrderDirection.SELL && mergeOrder.getPrice().compareTo(focusedOrder.getPrice()) < 0) {
break;
}
while (orderIterator.hasNext()) {
ExchangeOrder matchOrder = orderIterator.next();
//处理匹配
ExchangeTrade trade = processMatch(focusedOrder, matchOrder);
exchangeTrades.add(trade);
//判断匹配单是否完成
if (matchOrder.isCompleted()) {
//当前匹配的订单完成交易,删除该订单
orderIterator.remove();
completedOrders.add(matchOrder);
}
//判断交易单是否完成
if (focusedOrder.isCompleted()) {
//交易完成
completedOrders.add(focusedOrder);
//退出循环
exitLoop = true;
break;
}
}
if(mergeOrder.size() == 0){
mergeOrderIterator.remove();
}
}
}
//如果还没有交易完,订单压入列表中
if (focusedOrder.getTradedAmount().compareTo(focusedOrder.getAmount()) < 0 && canEnterList) {
addLimitPriceOrder(focusedOrder);
}
//每个订单的匹配批量推送
handleExchangeTrade(exchangeTrades);
if(completedOrders.size() > 0){
orderCompleted(completedOrders);
TradePlate plate = focusedOrder.getDirection() == ExchangeOrderDirection.BUY ? sellTradePlate : buyTradePlate;
sendTradePlateMessage(plate);
}
} /**
* 限价委托单与市价队列匹配
* @param mpList 市价对手单队列
* @param focusedOrder 交易订单
*/
public void matchLimitPriceWithMPList(LinkedList<ExchangeOrder> mpList,ExchangeOrder focusedOrder){
List<ExchangeTrade> exchangeTrades = new ArrayList<>();
List<ExchangeOrder> completedOrders = new ArrayList<>();
synchronized (mpList) {
Iterator<ExchangeOrder> iterator = mpList.iterator();
while (iterator.hasNext()) {
ExchangeOrder matchOrder = iterator.next();
ExchangeTrade trade = processMatch(focusedOrder, matchOrder);
logger.info(">>>>>"+trade);
if(trade != null){
exchangeTrades.add(trade);
}
//判断匹配单是否完成,市价单amount为成交量
if(matchOrder.isCompleted()){
iterator.remove();
completedOrders.add(matchOrder);
}
//判断吃单是否完成,判断成交量是否完成
if (focusedOrder.isCompleted()) {
//交易完成
completedOrders.add(focusedOrder);
//退出循环
break;
}
}
}
//如果还没有交易完,订单压入列表中
if (focusedOrder.getTradedAmount().compareTo(focusedOrder.getAmount()) < 0) {
addLimitPriceOrder(focusedOrder);
}
//每个订单的匹配批量推送
handleExchangeTrade(exchangeTrades);
orderCompleted(completedOrders);
} /**
* 市价委托单与限价对手单列表交易
* @param lpList 限价对手单列表
* @param focusedOrder 待交易订单
*/
public void matchMarketPriceWithLPList(TreeMap<BigDecimal,MergeOrder> lpList, ExchangeOrder focusedOrder){
List<ExchangeTrade> exchangeTrades = new ArrayList<>();
List<ExchangeOrder> completedOrders = new ArrayList<>();
synchronized (lpList) {
Iterator<Map.Entry<BigDecimal,MergeOrder>> mergeOrderIterator = lpList.entrySet().iterator();
boolean exitLoop = false;
while (!exitLoop && mergeOrderIterator.hasNext()) {
Map.Entry<BigDecimal,MergeOrder> entry = mergeOrderIterator.next();
MergeOrder mergeOrder = entry.getValue();
Iterator<ExchangeOrder> orderIterator = mergeOrder.iterator();
while (orderIterator.hasNext()) {
ExchangeOrder matchOrder = orderIterator.next();
//处理匹配
ExchangeTrade trade = processMatch(focusedOrder, matchOrder);
if (trade != null) {
exchangeTrades.add(trade);
}
//判断匹配单是否完成
if (matchOrder.isCompleted()) {
//当前匹配的订单完成交易,删除该订单
orderIterator.remove();
completedOrders.add(matchOrder);
}
//判断焦点订单是否完成
if (focusedOrder.isCompleted()) {
completedOrders.add(focusedOrder);
//退出循环
exitLoop = true;
break;
}
}
if(mergeOrder.size() == 0){
mergeOrderIterator.remove();
}
}
}
//如果还没有交易完,订单压入列表中,市价买单按成交量算
if (focusedOrder.getDirection() == ExchangeOrderDirection.SELL&&focusedOrder.getTradedAmount().compareTo(focusedOrder.getAmount()) < 0
|| focusedOrder.getDirection() == ExchangeOrderDirection.BUY&& focusedOrder.getTurnover().compareTo(focusedOrder.getAmount()) < 0) {
addMarketPriceOrder(focusedOrder);
}
//每个订单的匹配批量推送
handleExchangeTrade(exchangeTrades);
if(completedOrders.size() > 0){
orderCompleted(completedOrders);
TradePlate plate = focusedOrder.getDirection() == ExchangeOrderDirection.BUY ? sellTradePlate : buyTradePlate;
sendTradePlateMessage(plate);
}
}
关于区块链钱包对接
每个币种对应不同的数据访问方式,大部分区块链项目的钱包操作方式是相同的或十分相似的,比如BTC、LTC、BCH、BSV、BCD等比特币衍生币,其API操作方式几乎一样;再比如ETH,当你掌握一个合约币种的操作,其他基于ETH发行的数字货币的操作方式几乎一样。所以,基本上当你花时间弄懂了一个,就懂了一堆币种。
本项目使用的钱包操作方案也是不同的,也尽可能的为大家展示了不同用法:
- 如BTC、USDT,使用的自建全节点,现在差不多需要300G硬盘空间;
- 如ETH,使用的是自建轻节点(参考文章),因为全节点需要硬盘空间太大;
- 如BCH、BSV等,使用的是第三方区块链浏览器获取数据;
- 如XRP,官方就已经提供了访问区块数据的接口(Ripple API GitHub地址)
一般而言,当交易所来往资金量不大的时候,你可以自己摸索,但是当交易所资金量大了以后,如果你对自己操作钱包不太放心,你也可以使用第三方的钱包服务,当然,这需要你与钱包服务商进行谈判,付个年费什么的。
下图是关于交易平台充值逻辑的一个简单时序图:
总结
通过以上的说明及图示,我们基本上对交易所的整体架构有了一定的认知。
感谢
最后感谢开源交易所项目给与我学习的机会!
Java开源交易平台项目:https://gitee.com/cexchange/CoinExchange
基于Java的数字货币交易系统的架构设计与开发的更多相关文章
- 基于Java Mina框架的部标808服务器设计和开发
在开发部标GPS平台中,部标808GPS服务器是系统的核心关键,决定了部标平台的稳定性和行那个.Linux服务器是首选,为了跨平台,开发语言选择Java自不待言. 我们为客户开发的部标服务器基于Min ...
- 基于Java Mina框架的部标jt808服务器设计和开发
在开发部标GPS平台中,部标jt808GPS服务器是系统的核心关键,决定了部标平台的稳定性和行那个.Linux服务器是首选,为了跨平台,开发语言选择Java自不待言.需要购买jt808GPS服务器源码 ...
- 基于token的多平台身份认证架构设计
基于token的多平台身份认证架构设计 1 概述 在存在账号体系的信息系统中,对身份的鉴定是非常重要的事情. 随着移动互联网时代到来,客户端的类型越来越多, 逐渐出现了 一个服务器,N个客户端的格 ...
- 基于 Angularjs&Node.js 云编辑器架构设计及开发实践
基于 Angularjs&Node.js 云编辑器架构设计及开发实践 一.产品背景 二.总体架构 1. 前端架构 a.前端层次 b.核心基础模块设计 c.业务模块设计 2. Node.js端设 ...
- 【原创】基于Docker的CaaS容器云平台架构设计及市场分析
基于Docker的CaaS容器云平台架构设计及市场分析 ---转载请注明出处,多谢!--- 1 项目背景---概述: “在移动互联网时代,企业需要寻找新的软件交付流程和IT架构,从而实现架构平台化,交 ...
- Go语言学习之14 商品秒杀架构设计与开发
本节主要内容 1. 秒杀抢购背景2. 秒杀抢购架构设计&模块划分3. 秒杀抢购接入层实现 1. 秒杀抢购背景 (1)架构分析 电商网站架构 秒杀抢购1.0 (2)上述网站架构问题 和已有电商逻 ...
- WebApi 基于token的多平台身份认证架构设计
1 概述 在存在账号体系的信息系统中,对身份的鉴定是非常重要的事情. 随着移动互联网时代到来,客户端的类型越来越多, 逐渐出现了 一个服务器,N个客户端的格局 . 不同的客户端产生了不同的用户使用 ...
- Java进阶专题(十七) 系统缓存架构设计 (上)
前言 我们将先从Redis.Nginx+Lua等技术点出发,了解缓存应用的场景.通过使用缓存相关技术,解决高并发的业务场景案例,来深入理解一套成熟的企业级缓存架构如何设计的.本文Redis部分总结 ...
- spark安装配置(scala不是必须的,基于java虚拟机,因此scala可以不配,但是开发需要可以配)
下载 http://spark.apache.org/downloads.html 下载2.3.1 https://blog.csdn.net/qq_15349687/article/details/ ...
随机推荐
- python学习记录_中断正在执行的代码,执行剪切板中的代码,键盘快捷键,魔术命令,输入和输出变量,记录输入和输出变量_
2018-03-28 00:56:39 中断正在执行的代码 无论是%run执行的脚本还是长时间运行的命令ctrl + cIn [1]: KeyboardInterrupt 执行剪切板中的代码 ctrl ...
- python数据转换
主要内容 1:数字类型:算术运算 bool:判断真假,运用场景在逻辑运算里较多,比如while循环了. 字符串:可以索引取值,可以嵌套 列表:存放任意数据类型,因为是按序存放的,故可以索引取值, 字典 ...
- java多线程基础API
本次内容主要讲认识Java中的多线程.线程的启动与中止.yield()和join.线程优先级和守护线程. 1.Java程序天生就是多线程的 一个Java程序从main()方法开始执行,然后按照既定的代 ...
- vue的computed计算属性
computed可定义一些函数,这些函数叫做[计算属性] 只要data里面的数据发生变化computed会同步改变 引用[计算属性]时不要加 () ,应当普通属性使用 例:console.log(t ...
- 五分钟完成 ABP vNext 通讯录 App 开发
五分钟完成 ABP vNext 通讯录 App 开发 ABP vNext(后文简称Abp)是 Volo 公司堪称艺术品级的应用开发框架,它基于领域驱动设计(DDD)的思维,创新地采用了模块化的设计.A ...
- D2T1服务器需求——毒?瘤题(并不是
这题我第一眼居然差点错了\(OTZ\) 然后写了线段树,还写挂了-- 写好了\(query\)操作,发现似乎不需要区间查询,然后又删掉-- 看着这熟悉的操作,似乎在哪里见过-- 然后我莫名其妙把一个\ ...
- 一口气说出 6种,@Transactional注解的失效场景
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...
- Flask 请求中间件、错误处理、标签、过滤器、CBV
目录 一.请求中间件 二.请求中间件额外方法(重写源码) 三.请求错误处理 四.请求标签.过滤器 五.CBV写法 基础版 常用版 一.请求中间件 中间件: 1 before_first_request ...
- Scala尾递归
递归函数应用 首先,我们来对比两个递归方法的求值步骤. 假设有方法gcd,用来计算两个数的最大公约数.下面是欧几里得算法的实现: def gcp(a: Int, b: Int): Int = if ( ...
- VMware Tools失效的处理方案
VMware Tools是一个实现主机与虚拟机文件分享,具有可支持自由拖拽的功能的工具,如果没有VM tools,那么没有了复制粘贴切换的虚拟机是很不方便的. 长时间未开的虚拟机,一次尝试拖拽Wind ...