Axon框架抛出LockAcquisitionFailedException异常且尝试次数为100问题咨询
Axon Framework 锁获取失败问题排查与解决
最大尝试次数为100的原因
你使用的Axon 4.3.3属于4.4之前的旧版本,PessimisticLockFactory 在4.4.0版本之前的默认最大尝试次数就是100,4.4.0版本之后官方才将默认值调整为Integer.MAX_VALUE(即2147483647),这就是你和其他用户报错数值不同的核心原因,和自定义配置无关。
锁超时问题根因
虽然你的命令处理逻辑没有重计算,但聚合根锁的持有周期是从聚合加载开始,到所有产生的事件全部持久化完成才释放,以下场景会拉长锁持有时间导致竞争失败:
- 聚合生命周期长达数年,即使配置了200次事件触发快照,每次加载聚合时仍需要加载快照生成后产生的所有事件,事件数量越多加载耗时越长
- 事件溯源Handler中存在连锁事件触发逻辑:
SeatFreedEvent处理时如果预约队列不为空会触发SeatReservedEvent,如果队列长度较大,单次锁持有周期内会连续处理大量事件,大幅拉长锁占用时间 - 事件存储查询性能不足,没有为事件表的
aggregateIdentifier、sequenceNumber字段建立联合索引,导致聚合事件查询过慢
解决方案
- 调整锁配置:如果暂时无法升级Axon版本,可手动自定义
LockFactoryBean,调高最大尝试次数,示例配置如下:
@Bean public LockFactory lockFactory() { return PessimisticLockFactory.builder() .maxAttempts(Integer.MAX_VALUE) .acquireAttemptsPerSecond(10) // 调整锁尝试频率,避免CPU空转 .queueSizeThreshold(10000) .build(); }
- 优化快照策略:将快照触发阈值从200调低到50~100,减少每次聚合加载时需要回溯的事件数量,缩短锁持有时间
- 优化连锁事件逻辑:预约队列的批量消费逻辑不要放在单次聚合事件处理中执行,可引入Saga组件分批处理队列元素,避免单次锁持有周期过长
- 优化事件存储性能:给事件存储表的
aggregateIdentifier、sequenceNumber字段添加联合索引,提升聚合事件查询速度 - 聚合拆分:如果该聚合并发访问量持续升高,可将预约队列等高频变更逻辑拆分为独立子聚合,缩小锁竞争的范围
聚合根锁监控方法
- 自定义带监控能力的
LockFactory装饰器,在锁获取、释放节点埋点,采集锁持有时间、等待队列长度、获取失败次数等指标,上报到监控系统,示例代码如下:
public class MonitoredLockFactory implements LockFactory { private final LockFactory delegate; private final MeterRegistry meterRegistry; public MonitoredLockFactory(LockFactory delegate, MeterRegistry meterRegistry) { this.delegate = delegate; this.meterRegistry = meterRegistry; } @Override public Lock obtain(String aggregateIdentifier) { Timer.Sample waitSample = Timer.start(meterRegistry); Lock delegateLock = delegate.obtain(aggregateIdentifier); waitSample.stop(meterRegistry.timer("axon.lock.wait.time", "aggregateId", aggregateIdentifier)); return () -> { Timer.Sample holdSample = Timer.start(meterRegistry); try { delegateLock.release(); } finally { holdSample.stop(meterRegistry.timer("axon.lock.hold.time", "aggregateId", aggregateIdentifier)); } }; } }
- 对
LockAcquisitionFailedException异常的发生次数配置监控告警,出现异常时及时触发通知
定位锁持有进程的方法
- 开启Axon框架的debug级日志,在日志格式中添加线程ID、实例IP字段,出现锁超时时根据报错的聚合ID搜索日志,找到上一个成功获取该聚合锁的线程ID和对应实例
- 导出对应实例的JVM线程栈,根据线程ID查找线程当前的执行栈,即可定位到线程卡住的具体逻辑(常见为事件存储IO阻塞、死锁、第三方调用阻塞等)
- 如果是多实例部署,可在锁埋点日志中增加实例标识,优先定位到锁被哪个实例持有,再排查对应实例的运行状态
内容的提问来源于stack exchange,提问作者Nader Kahwaji
相关产品推荐
相关产品推荐

