分布式锁的实现原理

分布式锁的核心是确保在分布式系统中同一时刻只有一个节点能执行关键代码段。MySQL可通过唯一键约束或乐观锁实现,结合Java事务管理确保原子性。

基于MySQL唯一键的实现

创建锁表结构,利用唯一键冲突避免多节点获取锁:

CREATE TABLE `distributed_lock` (
  `lock_key` varchar(64) NOT NULL,
  `lock_owner` varchar(36) NOT NULL,
  `expire_time` datetime NOT NULL,
  PRIMARY KEY (`lock_key`),
  KEY `idx_expire` (`expire_time`)
);

Java实现获取锁逻辑:

public boolean tryLock(String lockKey, String ownerId, int expireSeconds) {
    try {
        // 尝试插入锁记录
        jdbcTemplate.update(
            "INSERT INTO distributed_lock(lock_key, lock_owner, expire_time) " +
            "VALUES(?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND)) " +
            "ON DUPLICATE KEY UPDATE " +
            "lock_owner = IF(expire_time < NOW(), VALUES(lock_owner), lock_owner), " +
            "expire_time = IF(expire_time < NOW(), VALUES(expire_time), expire_time)",
            lockKey, ownerId, expireSeconds
        );
        
        // 验证是否获得锁
        String actualOwner = jdbcTemplate.queryForObject(
            "SELECT lock_owner FROM distributed_lock WHERE lock_key = ? AND expire_time > NOW()",
            String.class, lockKey
        );
        return ownerId.equals(actualOwner);
    } catch (EmptyResultDataAccessException e) {
        return false;
    }
}

基于版本号的乐观锁实现

创建带版本号的业务表:

CREATE TABLE `inventory` (
  `id` bigint NOT NULL,
  `product_code` varchar(32) NOT NULL,
  `stock` int NOT NULL,
  `version` int NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_product` (`product_code`)
);

Java乐观锁更新示例:

@Transactional
public boolean deductStock(String productCode, int quantity) {
    // 查询当前版本号
    Inventory inventory = jdbcTemplate.queryForObject(
        "SELECT id, stock, version FROM inventory WHERE product_code = ?",
        (rs, rowNum) -> new Inventory(
            rs.getLong("id"),
            rs.getInt("stock"),
            rs.getInt("version")
        ),
        productCode
    );
    
    // 检查库存
    if (inventory.getStock() < quantity) {
        return false;
    }
    
    // 尝试更新
    int updated = jdbcTemplate.update(
        "UPDATE inventory SET stock = stock - ?, version = version + 1 " +
        "WHERE product_code = ? AND version = ?",
        quantity, productCode, inventory.getVersion()
    );
    
    return updated > 0;
}

死锁预防与超时处理

设置获取锁的超时机制:

public boolean lockWithTimeout(String lockKey, String ownerId, 
    int expireSeconds, int timeoutMs) {
    
    long endTime = System.currentTimeMillis() + timeoutMs;
    while (System.currentTimeMillis() < endTime) {
        if (tryLock(lockKey, ownerId, expireSeconds)) {
            return true;
        }
        try {
            Thread.sleep(100); // 避免CPU忙等
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    return false;
}

锁释放的正确方式

确保锁释放的原子性和幂等性:

public void releaseLock(String lockKey, String ownerId) {
    jdbcTemplate.update(
        "DELETE FROM distributed_lock " +
        "WHERE lock_key = ? AND lock_owner = ?",
        lockKey, ownerId
    );
}

锁续期机制实现

通过定时任务延长锁有效期:

@Scheduled(fixedDelay = 5000)
public void extendLockExpiration() {
    List<String> activeLocks = jdbcTemplate.queryForList(
        "SELECT lock_key FROM distributed_lock " +
        "WHERE lock_owner = ? AND expire_time > NOW()",
        String.class, currentOwnerId
    );
    
    activeLocks.forEach(lockKey -> 
        jdbcTemplate.update(
            "UPDATE distributed_lock SET " +
            "expire_time = DATE_ADD(NOW(), INTERVAL 30 SECOND) " +
            "WHERE lock_key = ? AND lock_owner = ?",
            lockKey, currentOwnerId
        )
    );
}

异常处理与事务回滚

使用Spring事务注解确保异常时锁能释放:

@Transactional(rollbackFor = Exception.class)
public void processWithLock(String lockKey) {
    if (!tryLock(lockKey, "TXN_" + UUID.randomUUID(), 30)) {
        throw new RuntimeException("获取锁失败");
    }
    try {
        // 业务处理逻辑
        businessService.process();
    } finally {
        releaseLock(lockKey);
    }
}

性能优化建议

  1. 为锁表添加适当的索引:CREATE INDEX idx_expire ON distributed_lock(expire_time)
  2. 使用连接池管理数据库连接:配置HikariCP等高性能连接池
  3. 减少锁持有时间:将业务逻辑分为获取锁前准备和锁内关键操作两部分
  4. 考虑使用MySQL的GET_LOCK()函数实现命名锁:
SELECT GET_LOCK('order_lock', 10); -- 等待10秒
SELECT RELEASE_LOCK('order_lock');

以上方案适用于中小规模分布式系统,对于更高并发场景建议结合Redis或Zookeeper实现。

更多推荐