NFT数字藏品在2021年爆火,很多平台都上线了数字藏品业务。但是数字藏品的抢购场景并发量极高,对系统架构提出了很大的挑战。本文从架构设计的角度,详细讲解NFT数字藏品平台的高可用高并发架构,包括整体架构、抢购流程、库存管理、链上交互、缓存策略、限流降级等。如果你在做数字藏品相关的系统,希望这篇文章能给你一些参考。

一、业务背景和挑战

先说说NFT数字藏品业务的背景和技术挑战。

NFT(Non-Fungible Token,非同质化代币)是一种基于区块链的数字资产凭证,每个NFT都是独一无二的,不可复制、不可篡改。数字藏品就是NFT的一种应用,把艺术品、收藏品、文创产品等做成NFT,在平台上发行和交易。

数字藏品的业务模式一般是:平台发行一定数量的数字藏品,用户在指定时间抢购,抢购成功后支付,然后平台把NFT铸造到用户的区块链地址上。用户可以在平台内展示、转赠或者交易自己的数字藏品。

这个业务看起来不复杂,但是技术挑战很大,主要有几个方面:

1. 高并发抢购

数字藏品的发售一般是限量的,比如发行10000份,但是可能有几十万人同时抢购。在开售的那一瞬间,并发量会达到峰值,对系统的压力非常大。

2. 库存超卖

因为库存有限,抢购的人多,很容易出现超卖的问题。如果10000份藏品卖出去了12000份,那就是严重的事故。所以库存管理必须非常严格,绝对不能超卖。

3. 区块链交互

NFT需要铸造到区块链上,区块链的交易确认有延迟,而且Gas费波动大。如何高效地和区块链交互,保证铸造的成功率和效率,是一个挑战。

4. 高可用

数字藏品的发售时间是固定的,到点必须能正常抢购。如果系统在发售的时候宕机了,影响会非常大,用户体验很差,还可能造成经济损失。所以系统必须高可用。

5. 防黄牛和防作弊

数字藏品很抢手,黄牛会用脚本批量抢购。如何识别和防止黄牛,保证公平性,也是一个重要的问题。

二、整体架构设计

针对这些挑战,我们设计了一套高可用高并发的架构。整体架构分为几个层次:接入层、应用层、服务层、数据层、区块链层。

接入层

接入层负责接收用户请求,包括负载均衡、API网关、CDN等。

  • 负载均衡:用Nginx或者云厂商的负载均衡,把请求分发到多个应用服务器
  • API网关:负责鉴权、限流、路由、日志等
  • CDN:静态资源(图片、前端页面)放在CDN上,减轻源站压力

接入层是系统的第一道防线,要能承受住大流量的冲击。

应用层

应用层是具体的业务服务,包括用户服务、商品服务、订单服务、支付服务、NFT服务等。

每个服务都是无状态的,可以水平扩展。通过增加服务器数量来应对高并发。

服务之间用RPC或者消息队列通信,解耦和异步处理。

服务层

服务层提供一些公共的基础服务,比如缓存服务、消息队列、分布式锁、限流服务等。

  • 缓存服务:Redis集群,用来缓存商品信息、库存、用户信息等
  • 消息队列:Kafka或者RocketMQ,用来异步处理订单、铸造等耗时操作
  • 分布式锁:基于Redis或者ZooKeeper,用来保证库存不超卖
  • 限流服务:基于令牌桶或者漏桶算法,对请求进行限流

数据层

数据层负责数据的存储,包括关系型数据库、NoSQL数据库、搜索引擎等。

  • MySQL:存储用户、商品、订单等核心业务数据,主从架构,读写分离
  • Redis:缓存和热点数据存储
  • MongoDB:存储NFT的元数据、用户操作日志等
  • Elasticsearch:用于商品搜索和订单查询

区块链层

区块链层负责和区块链交互,包括节点管理、钱包管理、智能合约交互等。

  • 区块链节点:自己部署或者用第三方的节点服务
  • 钱包管理:管理平台的热钱包和冷钱包
  • 智能合约:NFT的铸造、转移、查询等
  • Gas管理:管理Gas费,优化交易成本

三、抢购流程设计

抢购是数字藏品业务的核心场景,也是并发量最大的场景。我们的抢购流程设计如下:

第一步:预检和限流

用户点击抢购按钮之后,请求先到达接入层。接入层做几个检查:

  • 用户是否登录
  • 用户是否有抢购资格(比如是否实名认证、是否在黑名单)
  • 是否在抢购时间内
  • 请求频率是否过高(限流)

如果检查不通过,直接返回错误。如果通过,进入下一步。

这一步要尽量快,在网关层就完成,不要打到后端服务。

第二步:库存预扣减

通过预检之后,请求到达订单服务。订单服务首先要扣减库存。

库存不能直接在数据库里扣减,因为数据库的并发性能不够。我们用Redis来做库存的预扣减。

具体做法是:在开售之前,把库存数量加载到Redis中。抢购的时候,用Redis的原子操作(DECR)来扣减库存。如果扣减之后库存大于等于0,说明抢购成功;如果小于0,说明已经卖完了,返回"已售罄"。

用Redis的原子操作可以保证库存不会超卖,因为DECR是原子的,并发情况下也不会出错。

第三步:创建订单

库存预扣减成功之后,创建订单。订单的状态是"待支付",设置一个支付超时时间(比如15分钟)。

创建订单的时候,要把订单信息写入数据库,同时写入Redis缓存。订单号用分布式ID生成器生成,保证全局唯一。

创建订单成功之后,返回给用户"抢购成功,请支付"。

第四步:支付

用户在规定时间内完成支付。支付成功之后,支付服务回调订单服务,把订单状态改成"已支付"。

同时,发送一条消息到消息队列,通知NFT服务开始铸造NFT。

如果用户超时未支付,订单自动取消,库存回滚到Redis中。

第五步:NFT铸造

NFT服务收到消息之后,开始铸造NFT。铸造的过程是:

  1. 从钱包池中取出一个可用的地址作为NFT的接收地址(或者用用户自己的地址)
  2. 调用智能合约的mint函数,铸造NFT
  3. 等待区块链确认
  4. 确认成功之后,把NFT的信息(tokenId、交易哈希等)存入数据库
  5. 通知用户铸造成功

因为区块链的确认需要时间,铸造是异步的。用户支付之后不需要等待铸造完成,可以先看到"铸造中"的状态,铸造完成之后再通知用户。

四、库存管理

库存管理是抢购系统的核心,绝对不能超卖。我们用了多层的库存管理机制。

Redis预扣减

如前所述,用Redis的DECR原子操作做库存的预扣减。这是第一道防线,也是最关键的一道。

在开售之前,用SET命令把库存数量写入Redis。抢购的时候用DECR扣减。DECR是原子操作,并发安全。

数据库最终扣减

Redis预扣减成功之后,还要在数据库里做最终的扣减。数据库的库存字段用乐观锁(版本号)来保证并发安全。

具体做法是:更新库存的时候,用UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = ? AND version = ? AND stock > 0。如果更新成功,说明扣减成功;如果失败,说明并发冲突,需要重试或者返回失败。

数据库的扣减是最终的一致性保证,即使Redis出了问题,数据库也不会超卖。

库存回滚

如果用户超时未支付,或者订单取消,需要把库存回滚。回滚的时候,先回滚数据库,再回滚Redis(INCR操作)。

回滚的时候要注意幂等性,避免重复回滚导致库存增加错误。

库存预热

开售之前,要把商品信息和库存预热到Redis中。预热的时候要注意,不要在开售的瞬间才加载,应该提前几分钟加载好。

而且预热之后要验证一下,确保Redis中的库存数量和数据库一致。

五、缓存策略

缓存是提升性能的关键,我们用了多级缓存策略。

本地缓存

应用服务器的本地缓存(比如Caffeine或者Guava Cache),用来缓存一些不经常变化的数据,比如商品的基本信息、配置信息等。

本地缓存的访问速度最快,但是容量有限,而且多台服务器之间不共享。适合缓存小而热的数据。

Redis分布式缓存

Redis是主要的缓存层,缓存商品信息、库存、用户信息、订单信息等。

Redis的缓存要注意几个问题:

  • 缓存穿透:查询不存在的数据,直接打到数据库。用布隆过滤器或者缓存空值来解决
  • 缓存击穿:热点key过期,大量请求同时打到数据库。用互斥锁或者永不过期来解决
  • 缓存雪崩:大量key同时过期,数据库压力骤增。用过期时间加随机值来解决

数据库读写分离

数据库用主从架构,写操作走主库,读操作走从库。这样可以减轻主库的压力,提升读性能。

对于抢购这种读多写少的场景,读写分离的效果很明显。

六、限流和降级

高并发场景下,限流和降级是保护系统的重要手段。

限流

我们在多个层次做了限流:

  • 接入层限流:在API网关层对IP或者用户做限流,比如每秒最多10个请求
  • 服务层限流:对每个服务的接口做限流,保护后端服务
  • 用户级限流:对单个用户的抢购请求做限制,防止脚本刷接口

限流算法用的是令牌桶算法,可以平滑地处理突发流量。

降级

当系统压力过大的时候,要能自动降级,保证核心功能可用。

降级策略包括:

  • 关闭非核心功能,比如推荐、评论、排行榜等
  • 简化返回数据,比如只返回必要的字段
  • 直接返回缓存数据,不访问数据库
  • 对非核心的写操作异步处理

降级可以通过配置中心动态开关,在高峰期开启,低峰期关闭。

熔断

当某个服务出现故障的时候,要能快速熔断,避免故障蔓延。用Hystrix或者Sentinel做熔断,当失败率达到阈值的时候,自动熔断,一段时间之后再尝试恢复。

七、区块链交互优化

区块链交互是数字藏品系统的一个特殊部分,也是一个性能瓶颈。

批量铸造

如果每个用户的NFT都单独铸造一次,交易数量太多,Gas费也高。我们用批量铸造的方式,把多个NFT的铸造合并到一个交易中,一次铸造多个。

批量铸造可以大大减少交易数量,降低Gas费,提升铸造效率。

异步铸造

铸造是异步的,用户支付之后不需要等待。NFT服务从消息队列中取出任务,批量处理,铸造完成之后再通知用户。

异步处理可以把高峰的铸造任务平滑到一段时间内处理,避免区块链节点压力过大。

Gas优化

Gas费是区块链交互的重要成本。我们做了一些优化:

  • 选择Gas价格低的时候铸造(比如凌晨)
  • 用EIP-1559的交易类型,动态调整Gas费
  • 优化智能合约,减少Gas消耗
  • 批量铸造,分摊Gas成本

节点高可用

区块链节点是单点,如果节点挂了,铸造就会失败。我们用了多个节点,主备切换,保证节点的高可用。

而且节点前面加了一层代理,自动选择可用的节点,实现负载均衡和故障转移。

八、防黄牛和防作弊

数字藏品很抢手,黄牛会用各种手段作弊。我们做了一些防作弊的措施。

实名认证

抢购之前必须实名认证,一个身份证只能买一份。这样可以防止一个人注册多个账号批量抢购。

行为风控

分析用户的行为,识别异常的抢购行为。比如请求频率异常、IP地址异常、设备指纹异常等。对于可疑用户,限制抢购资格或者直接拉黑。

验证码

抢购的时候需要验证码,防止脚本自动抢购。验证码可以用图形验证码、滑块验证码、行为验证码等。

随机开售时间

有时候可以把开售时间设置在一个随机的时间点,防止黄牛提前准备脚本。不过这个会影响用户体验,要谨慎使用。

九、监控和告警

高并发系统必须有完善的监控和告警。

监控指标

我们监控的指标包括:

  • 系统指标:CPU、内存、磁盘、网络
  • 应用指标:QPS、响应时间、错误率
  • 业务指标:抢购成功率、订单量、支付成功率、铸造成功率
  • 中间件指标:Redis的命中率和延迟、消息队列的堆积量、数据库的连接数和慢查询

告警

设置告警阈值,当指标异常的时候,通过短信、电话、邮件等方式通知运维人员。

比如:错误率超过1%告警,响应时间超过1秒告警,消息队列堆积超过10000条告警。

链路追踪

用SkyWalking或者Jaeger做分布式链路追踪,当出现问题的时候,可以快速定位是哪个服务、哪个接口出了问题。

十、压测和演练

上线之前一定要做充分的压测和故障演练。

压测

用JMeter或者Gatling做压力测试,模拟高并发的抢购场景。压测的时候要注意:

  • 压测环境要和生产环境一致
  • 压测的数据要真实,比如用户信息、商品信息
  • 逐步增加并发量,找到系统的瓶颈
  • 压测之后要分析结果,优化瓶颈

我们的目标是支撑10万并发的抢购,压测的时候要测到15万甚至20万,留有余量。

故障演练

模拟各种故障场景,测试系统的容错能力。比如:

  • 某台应用服务器宕机
  • Redis主节点故障
  • 数据库主库故障
  • 区块链节点不可用
  • 消息队列堆积

通过故障演练,验证系统的高可用设计是否有效,发现潜在的问题。

十一、写在最后

NFT数字藏品的架构设计,核心是在高并发的场景下,保证系统的高可用和数据的一致性。抢购、库存、铸造、支付,每个环节都不能出问题。

本文介绍的架构和方法,是我们在实际项目中总结出来的经验。当然,每个项目的情况不一样,具体的架构还要根据业务需求来调整。

如果你在做数字藏品相关的系统,希望这篇文章能给你一些参考。如果有不同的见解或者更好的方案,欢迎在评论区交流。

最后用一句话结束本文:"高并发架构没有银弹,只有不断地压测、优化、再压测、再优化。"愿每一个系统都能扛住流量的冲击,稳定运行。