最近线上出了个区块链应用的Bug,用户反馈充值不到账。
我从晚上九点开始排查,一直到第二天早上七点,整整一夜,终于找到了问题的根源。
本文分享这次排查的完整过程,包括问题现象、排查思路、定位过程、根本原因、解决方案,以及踩过的坑。
一、问题背景
先说说这个区块链应用的背景。
1. 应用介绍
我们做的是一个基于区块链的数字资产平台,主要功能:
- 用户充值:用户把加密货币充值到平台地址
- 用户提现:用户把资产从平台提走
- 内部转账:用户之间的资产转移
- 资产兑换:不同币种之间的兑换
技术栈:
- 公链:以太坊(Ethereum)
- 智能合约:Solidity编写的多签钱包合约
- 后端:Java + Spring Boot
- 数据库:MySQL
- 缓存:Redis
- 消息队列:Kafka
2. 问题现象
晚上九点,客服接到用户反馈:充值了ETH,但账户余额没有增加。
一开始以为是个别用户的问题,让客服查了一下,发现有十几个用户都反馈了同样的问题。而且都是最近一小时内的充值。
我意识到问题严重了,赶紧登录系统查看。
二、初步排查
1. 查看监控
首先看系统监控:
- 服务状态:正常,没有宕机
- 数据库:正常,连接数和CPU都正常
- Redis:正常
- Kafka:正常,没有消息堆积
- 区块链节点:同步正常,区块高度正常
服务都正常,但用户充值不到账,问题出在哪里?
2. 查看日志
查看充值服务的日志,发现:
- 充值请求正常接收
- 区块链节点查询正常
- 但事件监听服务有报错:"交易确认数不足,等待中"
哦,可能是交易确认的问题。
3. 查看链上交易
用用户提供的交易哈希,去区块链浏览器上查:
- 交易存在,已经被打包
- 交易确认数:12个确认(以太坊通常12个确认就足够了)
- 交易状态:成功
- 接收地址:是我们的充值地址
链上交易是成功的,但系统没有给用户入账。问题出在链下的处理逻辑。
三、深入排查
1. 事件监听机制
我们的充值监听机制是这样的:
- 启动一个事件监听器,监听合约的Deposit事件
- 监听到事件后,检查交易确认数
- 确认数达到阈值(12个)后,给用户入账
- 入账后,记录到数据库,发送通知
理论上,这个流程是没问题的。但为什么会漏单?
2. 检查事件监听日志
仔细看事件监听服务的日志,发现一个奇怪的现象:
- 有些交易,监听到了事件,但确认数一直显示为0
- 有些交易,确认数正常增长
- 确认数为0的交易,一直卡在"等待确认"的状态
为什么确认数会是0?链上明明已经有12个确认了。
3. 检查区块同步
怀疑是区块链节点同步的问题。
查看节点的同步状态:
- 当前区块高度:正常,和公链一致
- 最新区块时间:正常
- 节点同步状态:已同步
节点同步没问题。那为什么确认数是0?
4. 检查确认数计算逻辑
看确认数的计算代码:
long currentBlock = web3j.ethBlockNumber().send().getBlockNumber().longValue();
long txBlock = transactionReceipt.getBlockNumber().longValue();
long confirmations = currentBlock - txBlock;逻辑很简单:当前区块高度 - 交易所在区块高度 = 确认数。
但问题是,transactionReceipt是从哪里来的?
看代码,交易回执是从缓存里取的:
TransactionReceipt receipt = redisTemplate.opsForValue().get("tx:receipt:" + txHash);5. 发现问题
问题找到了!
交易回执是在交易刚被打包时获取的,然后缓存到Redis里。但缓存的交易回执里,blockNumber是交易刚被打包时的区块高度。
之后,虽然区块链在继续出块,但缓存的交易回执没有更新。所以计算确认数的时候,用的是缓存里的旧区块高度,导致确认数一直是0(或者很小)。
正常情况下,缓存会在一定时间后过期,然后重新获取最新的交易回执。但我们的缓存过期时间设得太长了(24小时),而且没有主动更新机制。
所以,交易被打包后,缓存了旧的回执,确认数一直不够,就一直卡在等待确认的状态。24小时后缓存过期,才会重新获取,这时候确认数够了,才会入账。但用户等不了24小时啊!
四、为什么之前没出问题
这个Bug其实一直存在,但为什么之前没爆发?
1. 之前的流量小
之前充值的用户不多,偶尔有几个漏单,客服手动处理了,没有引起重视。
2. 最近流量增大
最近做了活动,充值用户暴增,漏单的数量也多了,才暴露出来。
3. 以太坊升级
以太坊最近进行了合并(The Merge,2022年9月),出块时间从平均13秒变成了固定12秒。出块时间的变化,可能影响了确认数的计算逻辑。
虽然这不是根本原因,但可能加剧了问题。
五、解决方案
找到问题后,开始修复。
1. 紧急修复
首先,紧急处理已经漏单的交易:
- 写了一个脚本,扫描所有"等待确认"状态超过1小时的交易
- 重新从区块链获取最新的交易回执
- 确认数够了的,手动入账
- 一共处理了37笔漏单交易
处理完后,用户的余额都到账了,客服反馈用户满意。
2. 根本修复
然后,修复代码逻辑:
方案一:缩短缓存时间
- 把交易回执的缓存时间从24小时改成5分钟
- 这样,5分钟后就会重新获取最新的回执
- 缺点:频繁查询区块链节点,增加节点压力
方案二:不缓存交易回执
- 每次计算确认数时,都重新获取交易回执
- 缺点:性能差,节点压力大
方案三:缓存交易回执,但定期更新
- 缓存交易回执,但后台有个定时任务,定期更新待确认交易的回执
- 优点:平衡了性能和准确性
- 我们选择了这个方案
具体实现:
// 监听到事件后,缓存交易回执,但标记为"待确认"
// 定时任务每分钟扫描"待确认"的交易
// 重新获取交易回执,更新确认数
// 确认数达到阈值后,入账并标记为"已确认"3. 增加告警
增加监控和告警:
- "待确认"状态超过30分钟的交易数量,超过阈值就告警
- 事件监听服务的错误率,超过阈值就告警
- 充值入账延迟,超过阈值就告警
这样,以后再出问题,能及时发现,不用等用户反馈。
六、排查过程中的其他坑
在排查过程中,还遇到了其他一些坑。
坑一:区块链节点的不同步
我们用了两个区块链节点做负载均衡。
有一次,查交易回执,一个节点返回正常,另一个节点返回"交易不存在"。
原因是,两个节点的同步状态不一致,一个同步到了最新区块,另一个还在同步中。
解决:
- 检查所有节点的同步状态
- 只向已同步的节点发送请求
- 增加节点健康检查
坑二:智能合约事件的顺序
智能合约的事件,可能在同一个区块里触发多次。
我们的事件监听,没有处理同一个区块里多个事件的顺序问题,导致有时候入账顺序错乱。
解决:
- 事件处理时,按交易索引和日志索引排序
- 同一个区块的事件,按顺序处理
- 增加幂等性处理,防止重复入账
坑三:数据库事务和链上状态的一致性
有一次,数据库入账成功了,但链上交易实际上失败了(因为gas不足)。
原因是,我们只检查了交易是否被打包,没有检查交易的状态(status字段)。
解决:
- 入账前,检查交易的status字段,必须是成功(1)
- 失败的交易,不入账,记录异常
- 增加对账机制,定期核对链上和链下数据
坑四:重放攻击
有用户尝试用同一笔交易,在不同的账户上重复充值。
原因是,我们没有检查交易的from地址是否和用户绑定的地址一致。
解决:
- 充值时,验证交易的from地址是否是用户绑定的地址
- 交易哈希做唯一索引,防止重复入账
- 增加风控规则,异常充值自动拦截
七、经验总结
这次排查,让我对区块链应用的开发有了更深的理解。
1. 区块链应用的特殊性
区块链应用和传统应用有很大不同:
- 数据最终一致性:链上数据是最终一致的,不是实时的
- 交易不可篡改:一旦上链,无法修改
- 确认时间:交易需要确认,不能实时到账
- 节点同步:节点可能不同步,数据可能不一致
开发区块链应用,要充分考虑这些特殊性。
2. 缓存的使用要谨慎
在区块链应用中,缓存的使用要特别谨慎:
- 链上数据是不断变化的,缓存可能过期
- 缓存时间不能太长,否则数据不一致
- 关键数据(如交易回执),要定期更新
- 缓存要有主动失效机制
3. 监控和告警很重要
区块链应用的问题,往往不是服务宕机,而是数据不一致。
传统的监控(CPU、内存、服务状态)发现不了这些问题。需要针对区块链应用的特点,增加专门的监控:
- 交易确认延迟
- 事件监听延迟
- 链上链下数据一致性
- 漏单率和重复率
4. 幂等性和对账
区块链应用,一定要做好幂等性和对账:
- 所有操作都要幂等,防止重复处理
- 定期对账,核对链上和链下数据
- 发现不一致,及时告警和修复
- 异常交易要有处理流程
5. 测试要充分
区块链应用的测试,要覆盖各种边界情况:
- 交易确认数不足
- 节点不同步
- 交易失败
- 事件重复
- 区块重组(reorg)
这些情况在测试环境中不容易模拟,但在生产环境中可能出现。
八、写在最后
这次排查,从晚上九点到第二天早上七点,整整一夜。
虽然很累,但收获很大。我对区块链应用的开发、区块链的特性、问题排查的思路,都有了更深的理解。
区块链技术还在发展中,很多基础设施还不够成熟。开发区块链应用,需要比传统应用更加谨慎,更加注重数据一致性和异常处理。
2022年了,区块链从概念走向落地,越来越多的应用开始使用区块链技术。但落地的过程中,会遇到各种各样的问题。作为开发者,我们需要不断学习,不断踩坑,才能把区块链应用做好。
最后,用一句话总结:"区块链应用的坑,大部分来自链上和链下的数据不一致。做好缓存、监控、幂等、对账,就能避开大部分坑。"
愿你的区块链应用,没有Bug,不用熬夜排查。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录