分布式锁算法——基于数据库的分布式锁全面解析

打印 上一主题 下一主题

主题 2113|帖子 2113|积分 6339


Java实现基于数据库的分布式锁全面解析

一、核心原理与筹划考量

1. 分布式锁基本要求

     2. 数据库实现方案对比

方案范例实现方式优点缺点唯一索引法利用唯一约束插入记录实现简朴无主动过期机制乐观锁方案版本号/时间戳更新克制长事件高并发性能差悲观锁方案SELECT FOR UPDATE强一致性连接池耗尽风险存储过程方案数据库函数实现锁逻辑减少网络往返数据库移植性差 二、基础实现方案

1. 唯一索引方案实现

  1. // 数据库表结构
  2. CREATE TABLE distributed_lock (
  3.     lock_key VARCHAR(64) PRIMARY KEY,
  4.     client_id VARCHAR(36) NOT NULL,
  5.     expire_time DATETIME NOT NULL,
  6.     version INT DEFAULT 0
  7. );
  8. // Java实现类
  9. public class UniqueIndexLock {
  10.     private final DataSource dataSource;
  11.    
  12.     public boolean tryLock(String lockKey, String clientId, int expireSeconds) {
  13.         String sql = "INSERT INTO distributed_lock (lock_key, client_id, expire_time) "
  14.                    + "VALUES (?, ?, NOW() + INTERVAL ? SECOND) "
  15.                    + "ON DUPLICATE KEY UPDATE "
  16.                    + "client_id = IF(expire_time < NOW(), VALUES(client_id), client_id), "
  17.                    + "expire_time = IF(expire_time < NOW(), VALUES(expire_time), expire_time)";
  18.         
  19.         try (Connection conn = dataSource.getConnection();
  20.              PreparedStatement ps = conn.prepareStatement(sql)) {
  21.             
  22.             ps.setString(1, lockKey);
  23.             ps.setString(2, clientId);
  24.             ps.setInt(3, expireSeconds);
  25.             
  26.             return ps.executeUpdate() > 0;
  27.         } catch (SQLException e) {
  28.             if (e.getErrorCode() == 1062) { // 唯一键冲突
  29.                 return false;
  30.             }
  31.             throw new LockException("获取锁失败", e);
  32.         }
  33.     }
  34.    
  35.     public void unlock(String lockKey, String clientId) {
  36.         String sql = "DELETE FROM distributed_lock WHERE lock_key = ? AND client_id = ?";
  37.         try (Connection conn = dataSource.getConnection();
  38.              PreparedStatement ps = conn.prepareStatement(sql)) {
  39.             
  40.             ps.setString(1, lockKey);
  41.             ps.setString(2, clientId);
  42.             int affected = ps.executeUpdate();
  43.             
  44.             if (affected == 0) {
  45.                 throw new LockException("释放锁失败:锁不存在或客户端不匹配");
  46.             }
  47.         } catch (SQLException e) {
  48.             throw new LockException("释放锁异常", e);
  49.         }
  50.     }
  51. }
复制代码
2. 乐观锁版本控制方案

  1. public class OptimisticLock {
  2.     public boolean tryLock(String lockKey, String clientId, int expireSeconds) {
  3.         Connection conn = null;
  4.         try {
  5.             conn = dataSource.getConnection();
  6.             conn.setAutoCommit(false);
  7.             
  8.             // 1. 查询当前锁状态
  9.             LockRecord record = selectForUpdate(conn, lockKey);
  10.             
  11.             if (record == null) {
  12.                 insertNewLock(conn, lockKey, clientId, expireSeconds);
  13.                 return true;
  14.             }
  15.             
  16.             if (record.isExpired()) {
  17.                 int updated = updateLockOwner(conn, lockKey, clientId,
  18.                     record.getVersion(), expireSeconds);
  19.                 return updated > 0;
  20.             }
  21.             
  22.             return false;
  23.         } finally {
  24.             if (conn != null) {
  25.                 try {
  26.                     conn.commit();
  27.                     conn.close();
  28.                 } catch (SQLException e) {
  29.                     // 处理异常
  30.                 }
  31.             }
  32.         }
  33.     }
  34.    
  35.     private int updateLockOwner(Connection conn, String lockKey, String clientId,
  36.                               int oldVersion, int expireSeconds) throws SQLException {
  37.         String sql = "UPDATE distributed_lock SET "
  38.                    + "client_id = ?, "
  39.                    + "expire_time = NOW() + INTERVAL ? SECOND, "
  40.                    + "version = version + 1 "
  41.                    + "WHERE lock_key = ? AND version = ?";
  42.         
  43.         try (PreparedStatement ps = conn.prepareStatement(sql)) {
  44.             ps.setString(1, clientId);
  45.             ps.setInt(2, expireSeconds);
  46.             ps.setString(3, lockKey);
  47.             ps.setInt(4, oldVersion);
  48.             return ps.executeUpdate();
  49.         }
  50.     }
  51. }
复制代码
三、生产级优化实现

1. 锁续期守护线程

  1. public class LockRenewalDaemon {
  2.     private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
  3.     private final ConcurrentMap<String, Lease> heldLocks = new ConcurrentHashMap<>();
  4.    
  5.     class Lease {
  6.         final String lockKey;
  7.         final String clientId;
  8.         volatile long expiryTime;
  9.         
  10.         Lease(String lockKey, String clientId, int ttl) {
  11.             this.lockKey = lockKey;
  12.             this.clientId = clientId;
  13.             this.expiryTime = System.currentTimeMillis() + ttl * 1000;
  14.         }
  15.     }
  16.    
  17.     public void startRenewal(String lockKey, String clientId, int ttl) {
  18.         Lease lease = new Lease(lockKey, clientId, ttl);
  19.         heldLocks.put(lockKey, lease);
  20.         
  21.         scheduler.scheduleAtFixedRate(() -> {
  22.             try {
  23.                 renewLock(lease);
  24.             } catch (Exception e) {
  25.                 // 处理续期失败
  26.             }
  27.         }, ttl / 3 * 1000, ttl / 3 * 1000, TimeUnit.MILLISECONDS);
  28.     }
  29.    
  30.     private void renewLock(Lease lease) {
  31.         String sql = "UPDATE distributed_lock SET "
  32.                    + "expire_time = NOW() + INTERVAL ? SECOND "
  33.                    + "WHERE lock_key = ? AND client_id = ?";
  34.         
  35.         try (Connection conn = dataSource.getConnection();
  36.              PreparedStatement ps = conn.prepareStatement(sql)) {
  37.             
  38.             ps.setInt(1, lease.ttl);
  39.             ps.setString(2, lease.lockKey);
  40.             ps.setString(3, lease.clientId);
  41.             
  42.             if (ps.executeUpdate() > 0) {
  43.                 lease.expiryTime = System.currentTimeMillis() + lease.ttl * 1000;
  44.             }
  45.         } catch (SQLException e) {
  46.             // 处理续期失败
  47.         }
  48.     }
  49. }
复制代码
2. 锁获取重试策略

  1. public class RetryPolicy {
  2.     private static final int MAX_RETRIES = 5;
  3.     private static final long BASE_DELAY = 100;
  4.     private static final double JITTER_FACTOR = 0.2;
  5.    
  6.     public boolean acquireWithRetry(String lockKey, String clientId) {
  7.         int attempt = 0;
  8.         while (attempt < MAX_RETRIES) {
  9.             if (tryAcquire(lockKey, clientId)) {
  10.                 return true;
  11.             }
  12.             
  13.             long delay = calculateBackoff(attempt);
  14.             try {
  15.                 Thread.sleep(delay);
  16.             } catch (InterruptedException e) {
  17.                 Thread.currentThread().interrupt();
  18.                 break;
  19.             }
  20.             attempt++;
  21.         }
  22.         return false;
  23.     }
  24.    
  25.     private long calculateBackoff(int attempt) {
  26.         double jitter = (Math.random() * 2 - 1) * JITTER_FACTOR * BASE_DELAY;
  27.         return (long) (Math.pow(2, attempt) * BASE_DELAY + jitter);
  28.     }
  29. }
复制代码
四、高可用架构筹划

1. 多数据库实例部署

     2. 跨数据库锁同步方案

  1. public class MultiDBSyncLock {
  2.     private final List<DataSource> dataSources;
  3.    
  4.     public boolean crossDBlock(String lockKey, String clientId) {
  5.         int successCount = 0;
  6.         for (DataSource ds : dataSources) {
  7.             if (tryLockOnDB(ds, lockKey, clientId)) {
  8.                 successCount++;
  9.             }
  10.         }
  11.         return successCount > dataSources.size() / 2;
  12.     }
  13.    
  14.     private boolean tryLockOnDB(DataSource ds, String lockKey, String clientId) {
  15.         // 实现单数据库锁获取逻辑
  16.     }
  17.    
  18.     public void releaseCrossDBLock(String lockKey, String clientId) {
  19.         dataSources.parallelStream().forEach(ds -> {
  20.             try {
  21.                 releaseLockOnDB(ds, lockKey, clientId);
  22.             } catch (Exception e) {
  23.                 // 记录释放失败
  24.             }
  25.         });
  26.     }
  27. }
复制代码
五、性能优化策略

1. 数据库连接池设置

  1. # HikariCP配置示例
  2. hikari.maximumPoolSize=20
  3. hikari.minimumIdle=5
  4. hikari.idleTimeout=30000
  5. hikari.connectionTimeout=2000
  6. hikari.leakDetectionThreshold=5000
复制代码
2. 锁表索引优化

  1. ALTER TABLE distributed_lock
  2. ADD INDEX idx_expire_time (expire_time),
  3. ADD INDEX idx_client (client_id);
复制代码
3. 批量锁操作优化

  1. public class BatchLockOperator {
  2.     public Map<String, Boolean> batchLock(List<String> keys, String clientId) {
  3.         String sql = "INSERT INTO distributed_lock (lock_key, client_id, expire_time) "
  4.                    + "VALUES %s ON DUPLICATE KEY UPDATE "
  5.                    + "client_id = IF(expire_time < NOW(), VALUES(client_id), client_id)";
  6.         
  7.         String values = keys.stream()
  8.             .map(k -> String.format("('%s', '%s', NOW() + INTERVAL 30 SECOND)", k, clientId))
  9.             .collect(Collectors.joining(","));
  10.         
  11.         try (Connection conn = dataSource.getConnection();
  12.              Statement stmt = conn.createStatement()) {
  13.             
  14.             int affected = stmt.executeUpdate(String.format(sql, values));
  15.             return parseBatchResult(keys, affected);
  16.         }
  17.     }
  18. }
复制代码
六、非常处置惩罚机制

1. 锁超时主动开释

  1. public class LockTimeoutCleaner {
  2.     private final ScheduledExecutorService cleaner = Executors.newSingleThreadScheduledExecutor();
  3.    
  4.     @PostConstruct
  5.     public void init() {
  6.         cleaner.scheduleAtFixedRate(this::cleanExpiredLocks,
  7.             1, 1, TimeUnit.MINUTES);
  8.     }
  9.    
  10.     private void cleanExpiredLocks() {
  11.         String sql = "DELETE FROM distributed_lock WHERE expire_time < NOW()";
  12.         try (Connection conn = dataSource.getConnection();
  13.              PreparedStatement ps = conn.prepareStatement(sql)) {
  14.             ps.executeUpdate();
  15.         } catch (SQLException e) {
  16.             // 记录清理失败
  17.         }
  18.     }
  19. }
复制代码
2. 数据库故障转移处置惩罚

  1. public class FailoverHandler {
  2.     private volatile DataSource activeDataSource;
  3.     private final List<DataSource> standbyDataSources;
  4.    
  5.     public boolean tryLockWithFailover(String lockKey, String clientId) {
  6.         for (DataSource ds : getAllDataSources()) {
  7.             try {
  8.                 if (tryLockOnDB(ds, lockKey, clientId)) {
  9.                     activeDataSource = ds;
  10.                     return true;
  11.                 }
  12.             } catch (SQLException e) {
  13.                 // 标记数据库不可用
  14.                 markDBNotAvailable(ds);
  15.             }
  16.         }
  17.         return false;
  18.     }
  19. }
复制代码
七、生产环境监控

1. 关键监控指标

  1. class LockMetrics {
  2.     // 锁操作统计
  3.     Counter lockSuccess = new Counter();
  4.     Counter lockFailure = new Counter();
  5.     Histogram lockTime = new Histogram();
  6.    
  7.     // 数据库连接池状态
  8.     Gauge connectionPoolUsage = new Gauge();
  9.     Counter connectionTimeout = new Counter();
  10.    
  11.     // 锁持有时间分布
  12.     Timer lockDuration = new Timer();
  13. }
复制代码
2. 慢查询监控

  1. -- MySQL慢查询日志分析
  2. SELECT
  3.     lock_key,
  4.     COUNT(*) AS lock_operations,
  5.     AVG(query_time) AS avg_time,
  6.     MAX(query_time) AS max_time
  7. FROM
  8.     mysql.slow_log
  9. WHERE
  10.     sql_text LIKE '%distributed_lock%'
  11. GROUP BY
  12.     lock_key;
复制代码
八、与其他方案对比

特性数据库方案Redis方案ZooKeeper方案一致性强一致性最终一致性强一致性性能中等(千级QPS)高(万级QPS)中等(千级QPS)实现复杂度中等简朴复杂依赖基础办法需要数据库集群需要Redis集群需要ZK集群主动过期需自行实现原生支持临时节点主动删除可重入性需自行实现需自行实现原生支持 九、典范应用场景

1. 库存扣减操作

  1. public class InventoryService {
  2.     private final DistributedLock lock;
  3.    
  4.     @Transactional
  5.     public void deductInventory(String itemId, int quantity) {
  6.         String lockKey = "inventory_lock:" + itemId;
  7.         if (lock.tryLock(lockKey, 30)) {
  8.             try {
  9.                 Item item = itemDAO.selectForUpdate(itemId);
  10.                 if (item.getStock() >= quantity) {
  11.                     itemDAO.updateStock(itemId, -quantity);
  12.                 }
  13.             } finally {
  14.                 lock.unlock(lockKey);
  15.             }
  16.         } else {
  17.             throw new BusinessException("系统繁忙,请稍后重试");
  18.         }
  19.     }
  20. }
复制代码
2. 定时任务调治

  1. public class DistributedJobScheduler {
  2.     private final DistributedLock lock;
  3.    
  4.     @Scheduled(fixedRate = 60000)
  5.     public void runReportGeneration() {
  6.         if (lock.tryLock("job_report_gen", 60)) {
  7.             try {
  8.                 generateDailyReport();
  9.             } finally {
  10.                 lock.unlock("job_report_gen");
  11.             }
  12.         }
  13.     }
  14. }
复制代码
十、总结与最佳实践

1. 方案上风



  • 数据强一致:基于数据库事件保证锁状态一致性
  • 无额外依赖:复用现有数据库基础办法
  • 简朴可靠:实现方案直接,恰当中小规模系统
2. 适用场景



  • 已有数据库集群且不希望引入新组件
  • 对锁精度要求高的金融交易场景
  • 低频但需要强一致性的关键操作
3. 注意事项



  • 克制长事件:锁持有时间应只管收缩
  • 索引优化:确保锁表的查询效率
  • 连接池管理:防止锁竞争导致连接耗尽
  • 监控告警:及时跟踪锁争用环境
部署建议:

  • 使用主从数据库集群提高可用性
  • 定期实行锁表维护(清理过期锁、重建索引)
  • 为锁操作设置独立数据库用户和连接池
  • 结合应用层重试和数据库事件优化性能
更多资源:

http://sj.ysok.net/jydoraemon 访问码:JYAM
本文发表于【纪元A梦】,关注我,获取更多免费实用教程/资源!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

知者何南

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表