要点
币安账本负责存储账户余额和交易,同时赋能服务进行交易。
它为币安平台的高吞吐量、全天候运行和位级数据准确性创造了必要条件。
充当幕后角色的币安账本是币安最重要的技术之一。本文将介绍币安账本的具体运作原理及其在币安平台的运作中所解决的问题。
您是否思考过,究竟是什么使币安得以全天候运行?币安每日需要处理来自庞大用户群的数百万笔交易,我们有必要了解一下币安的幕后功臣。
为币安的技术运营提供支撑的是其账本。币安账本负责存储账户余额和交易,同时赋能服务进行交易。
币安对账本的要求很高
如您所想,由于币安账本需要满足海量用户需求,因此币安对其要求很高,主要有三个方面需要考虑:
具备高吞吐量,能够在峰值时段处理大量TPS(每秒交易量)。
能够全天候运行不停机。
拥有位级数据准确性,没有资金损失或交易错误。
我们以币安账本上的一个基本条目为例:以下是一笔常见交易,其中账户1将1 BTC转入账户2。
交易前余额
账户编号 | 资产 | 余额 |
1 | BTC | 10 |
2 | BTC | 0.1 |
表-1
交易后余额
账户编号 | 资产 | 余额 |
1 | BTC | 9 |
2 | BTC | 1.1 |
表-2
该交易包含两个指令:
账户1:-1 BTC
账户2:+1 BTC
交易完成后,币安账本将存储两个余额日志以供审计和对账。
账户编号 | 资产 | DELTA | 交易编号 | 时间 |
100001 | BTC | -1 | tx-001 | 2022年01月01日01:02:03 |
100002 | BTC | +1 | tx-001 | 2022年01月01日01:02:03 |
表-3
标准行业解决方案
一种标准的行业账本解决方案是基于关系数据库。回到前述示例,可以将该交易中的两个指令转换成两条SQL语句,并在数据库交易中予以执行(t表-4)。
begin transaction; |
UPDATE balance_1 set balance = balance - 1 WHERE account_id=1 and asset = ‘BTC’ and balance - 1 >= 0; If 0 row is affected then rollback; |
UPDATE balance_2 set balance = balance + 1 WHERE account_id=2 and asset = ‘BTC’ and balance + 1 >= 0; If 0 row is affected then rollback; |
commit; |
表-4
上述解决方案的优势
该解决方案实施起来十分简单,
可轻松通过常见的数据库调优技术(如读/写拆分和分片)来提高性能。
对于开发者而言,从故障转移中恢复以及监控和维护商业数据库并不困难。
上述解决方案的劣势
当因行锁而出现竞态条件时,TPS会急剧下降。
难以进行横向扩展以提高性能。
热点账户问题
然而,上述演示的行业解决方案并不能满足币安的高要求。当交易发生时,该解决方案必须持有所涉及的每一行的行锁。虽然部分账户需要处理的交易相对较少,但当然也有部分需要处理许多并发交易的繁忙账户。在这种情况下,只有一笔交易能够持有该账户的行锁。
这样一来,其他交易只能等待行锁被释放,除此之外什么也做不了。我们将此种情况称之为热点账户问题,内部测试表明,TPS在该情况下将至少降低10倍。这一问题如以下表5所示。
热点账户示例:
Tx1(将1 BTC从账户1转入账户2) | Tx2(将2 BTC从账户1转入账户3) |
begin transaction; | |
begin transaction; | |
UPDATE balance set balance = balance - 1 WHERE account_id=1 and asset = ‘BTC’ and balance - 1 >= 0; (row locked: account_id=1 and asset = ‘BTC’) If 0 row is affected then rollback; | UPDATE balance set balance = balance - 2 WHERE account_id=1 and asset = ‘BTC’ and balance - 2 >= 0; If 0 row is affected then rollback; |
UPDATE balance set balance = balance + 1 WHERE account_id=2 and asset = ‘BTC’ and balance + 1 >= 0; If 0 row is affected then rollback; | wait lock |
commit; | wait lock |
get lock, execute | |
UPDATE balance set balance = balance + 2 WHERE account_id=3 and asset = ‘BTC’ and balance + 1 >= 0; If 0 row is affected then rollback; | |
commit; |
表-5
币安账本解决方案
如何解决热点账户问题?
通过创新性地将多线程模型转换为单线程模式也许可行。这样一来便避免了竞态条件问题,因此不会出现热点账户问题。
新线程模型
基于消息的通信
继实现新线程模型之后,我们还需要解决一个通信问题。由于状态机层是单线程的,而网络层是多线程的,那么我们如何在这两者之间进行有效的通信呢?
解决该难题的下一步是使用干扰器[1]。干扰器可基于环形缓冲区设计创建出一个无锁的高性能队列。
高度可用性
截至目前,我们已通过使用内存模型和RocksDB [2]本地存储实现了高性能。然而,新的挑战再次出现。现在我们需要解决数据的高度可用性问题。
为确保节点间数据的一致性,我们采用了Raft共识算法[3]。这意味着,数据备份的数量等于所有非领导者节点的数量。该算法还确保系统在至少一半节点健康的情况下仍能运作,从而协助提供高服务可用性。
Raft域角色:
领导者(Leader)。领导者负责处理所有客户请求,并将操作复制给所有跟随者。
跟随者(Follower)。跟随者跟随领导者进行所有操作。如果领导者失败,其中一名跟随者将被选为新的领导者。
学习者(Learner)。学习者是无投票权的跟随者,他们将每个幂等/交易更改记录发送至其他服务。
Raft域角色
CQRS(命令查询职责分离)
我们希望确保的另一个关键标准是,对于多样化程度更高的查询条件,币安账本需要具备更强大的写入性能和能力。为此,我们需要创建不同的域。raft域提供基于rocksdb+raft的更高效的写入,而视图域则监听raft域的消息,并将其保存至关系数据库中以供外部查询。此外,我们还可以在架构级别上实现命令查询职责分离。
账本架构
整体架构
Raft和账本之间的条款:
Raft | Ledger |
复制状态机 | 账本节点 |
状态 | 余额 |
命令 | 交易 |
表-6
查看域角色
Raft账本中心
吸收学习者生成的消息,并将交易和余额数据存储在MySQL中以供查询。
请求处理中
交易请求将首先经过网络层、账本层(请求处理器)和raft层(raft日志同步)。随后它将返回至账本层(状态机)、网络层(请求处理器),最终向客户返回响应。
数据通过两层之间的队列进行传递。
网络层 – 对远程过程调用(RPC)请求进行反序列化,并将其放入请求队列。
账本层 – 从队列中获取请求并准备上下文。随后,它会将请求元数据放入raft队列。
Raft层 – 从raft队列中获取请求元数据,并在所有跟随者之间进行同步。随后,它会将结果放入应用队列。
账本层 – 从应用队列中获取数据并更新状态机。随后,它会将结果放入响应队列。
网络层 – 从响应队列中获取结果,并对响应数据进行构建和序列化,然后将其返回给客户。
请求处理中
数据恢复
每个账本节点将根据时间段触发通用快照。此外,我们还实施了一致快照。每个节点会在相同的raft日志索引下触发,以确保当各节点触发快照时状态机完全相同。接着快照将上传至S3,由检查者(Checker)进行验证,并作为冷备份。
当账本重新启动时,它会读取本地快照并重建状态机。随后,账本会回放本地raft日志,并同步来自领导者的最新日志,直至赶上最新的日志索引。如本地快照或raft日志不存在,则账本将从领导者处获取。
快照与恢复
容灾能力
为提高可用性和容错性,账本节点被部署至不同的区域。只要超过一半的节点处于健康状态,数据便不会丢失,故障转移也将在一秒钟内完成。
即使整个集群出现故障(概率非常低),我们仍然可以通过Amazon S3中存储的一致快照恢复集群,并通过下游系统找回最新丢失的数据。
容错性
性能
下表显示了性能测试的硬件规格:
组件 | 实例类型 | 网络带宽(Gbps) | 弹性块存储(EBS)带宽(Gbps) | 弹性块存储(EBS)类型 |
领导者/跟随者 | M6i.4xlarge 16c64g | 不超过12.5 | 不超过10 | 2T GP3 * 3 IOPS6000 625MB/s |
学习者 | M6i.4xlarge 16c64g | 不超过12.5 | 不超过10 | 2T GP3 * 3 IOPS6000 625MB/s |
基准 | C5.4xlarge 16c32g | 不超过10 | 4.750 | 仅根卷 |
内部测试证明,一个由4个节点组成的集群(一个领导者、两个跟随者和一个学习者)可处理超过10,000笔TPS。根据设计,集群会逐一处理所有交易,根本不会出现行锁和竞态条件问题。因此,热点账户场景下的TPS与正常场景下的一样高。
热点账户TPS
下图显示了每笔交易的延迟。多数交易可在10毫秒内完成。速度较慢的交易可在25毫秒内完成。
延迟毫秒
币安账本业内领先
对于币安目前已取得的成果和独特的账本解决方案,我们感到无比自豪。如您所见,业内解决热点账户问题的传统方法并不能满足币安及其客户的需求。通过使用专为币安基础架构设计的方法,我们最终打造出了业内最为顺畅的交易平台和产品体验之一。很高兴与您分享我们的经验,希望您更好地了解币安等服务得以运作的深层原因。
敬请阅读下列文章,详细了解币安技术基础架构:
(币安博客)《使用MLOps构建实时端到端机器学习渠道》
(币安博客)《对话币安CTO Rohit:关于加密货币、区块链、Web3及其在币安第一个月任职经历的思考》
参考资料
[1] LMAX干扰器
[2] RocksDB
[3] Raft共识算法
推荐阅读: