基于mysql唯一键实现简单分布式锁(适用多节点调度任务重复执行问题)
更优秀的开源项目
GitHub地址:https://github.com/lukas-krecan/ShedLock
解决痛点
多节点管理后台中调度任务往往只需要单机运行,未避免在多台机器重复执行的问题,通常可以简单的使用配置来进行控制,但这样会有服务单点问题,或者引入xxl-job或使用redis分布式锁等额外依赖来实现。请思考,对于后台系统来说数据库是现成的,而且服务并发低,能否基于mysql自身的一些机制,来实现一个简单的分布式锁呢?本文基于mysql唯一键机制,实现一个简单的分布式锁。在多节点调度任务场景中,任务执行前先获取锁,由获取到锁的节点处理任务,处理完成后释放锁,而其他节点则直接结束任务,等待下一次任务触发再重新获取锁。这样即避免了单点问题,同时能将任务分散到不同机器进行执行,充分利用机器性能。
表初始化
CREATE TABLE dist_locks (
    lock_name VARCHAR(50) NOT NULL comment '锁名称',
    lock_owner VARCHAR(50) comment '锁持有者',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '锁创建的时间',
    PRIMARY KEY (lock_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁表';
实现代码
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import java.sql.PreparedStatement;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
 * @author huanruke
 * @date 2025-03-24
 */
@Slf4j
@Component
public class MysqlDistLockDemo {
    @Resource
    private JdbcTemplate jdbcTemplate;
    public static final int timeout = 5 * 60 * 1000;
    /**
     * 锁清理线程 防止死锁
     */
    @PostConstruct
    public void locksClear() {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    clearTimeoutLock();
                    ThreadUtil.safeSleep(timeout);
                } catch (Exception e) {
                    log.error("Locks-clear-thread process error ", e);
                }
            }
        });
        thread.setName("Locks-clear-thread");
        thread.start();
        log.info("Locks-clear-thread start...");
    }
    /**
     * 清理超时锁
     */
    public void clearTimeoutLock() {
        try {
            String sql = "DELETE FROM dist_locks WHERE created_at < ?";
            int row = jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1, DateUtil.format(DateUtil.offsetMillisecond(new Date(), -2 * timeout), DatePattern.NORM_DATETIME_FORMAT));
                return ps;
            });
            log.info("Locks cleared {}", row);
        } catch (Exception e) {
            log.error("Locks clear error ", e);
        }
    }
    /**
     * 获取锁
     * @param lockName	锁名称
     * @param lockOwner	锁持有着
     * @return boolean
     */
    public boolean lock(String lockName, String lockOwner) {
        try {
            String sql = "INSERT INTO dist_locks (lock_name, lock_owner, created_at) VALUES (?, ?, ?)";
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1, lockName);
                ps.setString(2, lockOwner);
                ps.setString(3, DateUtil.format(new Date(), DatePattern.NORM_DATETIME_FORMAT));
                return ps;
            });
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    /**
     * 释放锁
     * @param lockName  锁名称
     * @param lockOwner 锁持有着
     */
    public void unlock(String lockName, String lockOwner) {
        try {
            String sql = "DELETE FROM dist_locks WHERE lock_name = ? AND lock_owner = ?";
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1, lockName);
                ps.setString(2, lockOwner);
                return ps;
            });
        } catch (Exception e) {
            log.error("Lock release error, lockName:{} lockOwner:{} ", lockName, lockOwner, e);
        }
    }
}
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号