基于MySQL的分布式锁实现(Spring Boot + MyBatis)

发布于:2025-06-29 ⋅ 阅读:(18) ⋅ 点赞:(0)

基于MySQL的分布式锁实现(Spring Boot + MyBatis)

实现原理

基于数据库的唯一索引特性实现分布式锁,通过插入唯一索引记录表示获取锁,删除记录表示释放锁。

1. 创建锁表

首先需要在MySQL中创建一个锁表,用于存储锁信息:

CREATE TABLE `distributed_lock` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `lock_key` varchar(64) NOT NULL COMMENT '锁标识',
  `request_id` varchar(128) NOT NULL COMMENT '请求唯一标识',
  `expire_time` datetime NOT NULL COMMENT '过期时间',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_lock_key` (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁表';
2. 定义数据访问层

创建Lock实体类和Mapper接口:

// Lock.java
@Data
public class Lock {
    private Long id;
    private String lockKey;
    private String requestId;
    private LocalDateTime expireTime;
    private LocalDateTime createTime;
}

// LockMapper.java
public interface LockMapper {
    /**
     * 获取锁(插入记录)
     */
    int insertLock(Lock lock);
    
    /**
     * 释放锁(删除记录)
     */
    int deleteLock(@Param("lockKey") String lockKey, @Param("requestId") String requestId);
    
    /**
     * 检查锁是否存在
     */
    int checkLockExists(String lockKey);
    
    /**
     * 清除过期的锁
     */
    int clearExpiredLocks();
}

// LockMapper.xml
<mapper namespace="com.example.mapper.LockMapper">
    <insert id="insertLock">
        INSERT INTO distributed_lock (lock_key, request_id, expire_time)
        VALUES (#{lockKey}, #{requestId}, #{expireTime})
    </insert>
    
    <delete id="deleteLock">
        DELETE FROM distributed_lock 
        WHERE lock_key = #{lockKey} AND request_id = #{requestId}
    </delete>
    
    <select id="checkLockExists" resultType="int">
        SELECT COUNT(1) FROM distributed_lock WHERE lock_key = #{lockKey}
    </select>
    
    <delete id="clearExpiredLocks">
        DELETE FROM distributed_lock WHERE expire_time < NOW()
    </delete>
</mapper>
3. 实现分布式锁服务

创建分布式锁服务类,实现锁的获取和释放逻辑:

// DistributedLockService.java
@Service
public class DistributedLockService {
    private static final Logger logger = LoggerFactory.getLogger(DistributedLockService.class);
    
    @Autowired
    private LockMapper lockMapper;
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    /**
     * 获取锁
     * @param lockKey 锁标识
     * @param expireSeconds 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean acquireLock(String lockKey, int expireSeconds) {
        String requestId = UUID.randomUUID().toString();
        Lock lock = new Lock();
        lock.setLockKey(lockKey);
        lock.setRequestId(requestId);
        lock.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        
        try {
            // 利用唯一索引特性,插入成功则获取锁成功
            int result = lockMapper.insertLock(lock);
            if (result > 0) {
                // 将requestId存入ThreadLocal,用于释放锁时验证
                ThreadLocalUtil.set("requestId", requestId);
                return true;
            }
        } catch (DuplicateKeyException e) {
            // 锁已被其他线程持有
            logger.debug("获取锁失败,锁已存在: {}", lockKey);
        } catch (Exception e) {
            logger.error("获取锁异常", e);
        }
        
        // 清除过期锁(可优化为定时任务)
        clearExpiredLocks();
        return false;
    }
    
    /**
     * 释放锁
     * @param lockKey 锁标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey) {
        String requestId = (String) ThreadLocalUtil.get("requestId");
        if (requestId == null) {
            logger.warn("未找到requestId,可能未获取锁或已释放锁");
            return false;
        }
        
        try {
            // 在事务中删除锁,确保原子性
            return transactionTemplate.execute(status -> {
                int result = lockMapper.deleteLock(lockKey, requestId);
                if (result > 0) {
                    ThreadLocalUtil.remove("requestId");
                    return true;
                }
                return false;
            });
        } catch (Exception e) {
            logger.error("释放锁异常", e);
            return false;
        }
    }
    
    /**
     * 清除过期的锁
     */
    private void clearExpiredLocks() {
        try {
            lockMapper.clearExpiredLocks();
        } catch (Exception e) {
            logger.error("清除过期锁异常", e);
        }
    }
}
4. 使用分布式锁

在需要使用分布式锁的业务方法中调用锁服务:

// OrderService.java
@Service
public class OrderService {
    @Autowired
    private DistributedLockService lockService;
    
    @Autowired
    private StockService stockService;
    
    /**
     * 下单扣库存(使用分布式锁)
     */
    public void placeOrder(String productId, int quantity) {
        String lockKey = "product_lock:" + productId;
        boolean lockAcquired = false;
        
        try {
            // 获取锁,设置超时时间为10秒
            lockAcquired = lockService.acquireLock(lockKey, 10);
            if (lockAcquired) {
                // 获取锁成功,执行扣库存操作
                stockService.reduceStock(productId, quantity);
                // 其他业务逻辑...
            } else {
                // 获取锁失败,处理重试或返回失败
                throw new BusinessException("系统繁忙,请稍后重试");
            }
        } finally {
            // 释放锁
            if (lockAcquired) {
                lockService.releaseLock(lockKey);
            }
        }
    }
}
5. 定时任务清理过期锁

为避免数据库中积累过多过期锁记录,添加定时任务定期清理:

// LockCleanupTask.java
@Component
public class LockCleanupTask {
    @Autowired
    private LockMapper lockMapper;
    
    @Scheduled(fixedDelay = 60 * 1000) // 每分钟执行一次
    public void cleanupExpiredLocks() {
        try {
            int count = lockMapper.clearExpiredLocks();
            logger.info("清理过期锁完成,共清理: {}", count);
        } catch (Exception e) {
            logger.error("清理过期锁异常", e);
        }
    }
}
实现说明
  1. 获取锁:通过向数据库插入带有唯一索引的记录实现,插入成功则获取锁成功
  2. 释放锁:通过删除对应记录实现,需验证requestId确保安全性
  3. 锁超时:通过设置expire_time字段实现,配合定时任务清理过期锁
  4. 防误释放:使用ThreadLocal存储requestId,确保锁只能被持有者释放