You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同一Java进程中DefaultLockRepository的TTL失效问题及解决方案咨询

问题本质

JdbcLockRegistry 内部维护了本地锁缓存(基于ConcurrentHashMap),同一Java进程内的线程会优先读取本地缓存的锁状态,而非直接查询数据库的TTL记录。当某个线程持锁挂起时,本地缓存会一直标记该锁为“已持有”,即使数据库中的锁记录已过期,后续线程也无法触发TTL校验逻辑,导致TTL机制失效。

解决方案

针对同一进程内的锁TTL失效问题,可通过以下几种方式解决:

1. 禁用本地锁缓存

绕过JdbcLockRegistry的本地缓存逻辑,强制所有锁操作直接与数据库交互,确保每次获取锁时都校验数据库中的TTL状态。

  • 核心实现示例:
public class NoCacheJdbcLockRegistry implements LockRegistry {
    private final DefaultLockRepository lockRepository;

    public NoCacheJdbcLockRegistry(DefaultLockRepository lockRepository) {
        this.lockRepository = lockRepository;
    }

    @Override
    public Lock obtainLock(Object lockKey) {
        String key = lockKey.toString();
        return new JdbcLock(key, lockRepository);
    }

    private static class JdbcLock implements Lock {
        private final String key;
        private final DefaultLockRepository repository;

        public JdbcLock(String key, DefaultLockRepository repository) {
            this.key = key;
            this.repository = repository;
        }

        @Override
        public void lock() {
            while (!repository.acquire(key)) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException("Lock acquisition interrupted");
                }
            }
        }

        @Override
        public void unlock() {
            repository.release(key);
        }

        // 实现Lock接口的其他方法(lockInterruptibly、tryLock等),均直接调用repository对应逻辑
    }
}

2. 定时清理本地过期锁

给JdbcLockRegistry添加定时任务,定期对比本地缓存锁与数据库TTL记录,清理已过期的本地锁标记。

  • 核心实现示例:
public class TtlAwareJdbcLockRegistry extends JdbcLockRegistry {
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private final long ttlMillis;
    private final ConcurrentMap<Object, Lock> locks;

    @SuppressWarnings("unchecked")
    public TtlAwareJdbcLockRegistry(DataSource dataSource, String registryKey, long ttlMillis) {
        super(dataSource, registryKey);
        this.ttlMillis = ttlMillis;
        // 通过反射获取父类的locks缓存(或自定义时直接暴露该字段)
        try {
            Field locksField = JdbcLockRegistry.class.getDeclaredField("locks");
            locksField.setAccessible(true);
            this.locks = (ConcurrentMap<Object, Lock>) locksField.get(this);
        } catch (Exception e) {
            throw new RuntimeException("Failed to access locks cache", e);
        }
        // 每5秒执行一次过期锁清理
        scheduler.scheduleAtFixedRate(this::cleanExpiredLocks, 5, 5, TimeUnit.SECONDS);
    }

    private void cleanExpiredLocks() {
        long currentTime = System.currentTimeMillis();
        locks.keySet().forEach(key -> {
            String lockKey = key.toString();
            LockRecord record = getLockRepository().findById(lockKey).orElse(null);
            if (record != null && currentTime - record.getLastUpdated().getTime() > ttlMillis) {
                locks.remove(key);
            }
        });
    }

    public void shutdown() {
        scheduler.shutdown();
    }
}

3. 替换为进程感知的分布式锁实现

改用支持同一进程内TTL校验的锁组件,避免本地缓存带来的问题:

  • 使用Redisson锁:基于Redis的分布式锁,自带看门狗机制,同一进程内的线程会自动感知锁的过期状态,无需额外处理本地缓存。
  • 实现双重锁校验:每次获取锁时,先尝试本地锁,失败则查询数据库锁状态,若数据库锁已过期则强制清理本地锁后重试。
临时应急方案

如果无法立即修改代码,可通过以下方式临时缓解:

  • 给持锁线程添加超时中断:在调用第三方Jar包的逻辑外层设置超时,超时后中断线程,触发锁释放。
  • 缩短TTL配置:减小锁的TTL时间,降低锁被持有的阻塞窗口。

内容的提问来源于stack exchange,提问作者Cui Pengfei 崔鹏飞

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 06:25:03