基於Mongodb分散式鎖簡單實現,解決定時任務併發執行問題

蒲公英的狂想發表於2023-04-18

前言

我們日常開發過程,會有一些定時任務的程式碼來統計一些系統執行資料,但是我們應用有需要部署多個例項,傳統的透過配置檔案來控制定時任務是否啟動又太過繁瑣,而且還經常出錯,導致一些異常資料的產生

網上有很多分散式鎖的實現方案,基於redis、zk、等有很多,但是我的就是一個用了mysql和mongo的小應用,不準備引入其他三方中介軟體來解決這個問題,擼一個簡單的分散式鎖來解決定時任務併發執行的問題,加鎖操作的原子性和防死鎖也都要支援,這裡我使用mongodb寫了AllInOne的工具類

All in one Code

先上程式碼

@Component
@Slf4j
public class MongoDBLock {

    private static final int DEFAULT_LOCK_TIMEOUT = 30;//鎖的預設超時時間,單位秒

    private MongoTemplate mongoTemplate;
    private int lockTimeout;

    public MongoDBLock(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
        this.lockTimeout = DEFAULT_LOCK_TIMEOUT;
    }

    /**
     * 嘗試獲取分散式鎖
     *
     * @param lockKey 鎖的key
     * @return true:獲取鎖成功,false:獲取鎖失敗
     */
    private boolean acquireLock(String lockKey) {
        LockDocument document = new LockDocument();
        document.setId(lockKey);
        document.setExpireAt(Instant.ofEpochMilli(Instant.now().toEpochMilli() + lockTimeout * 1000));
        try {
            mongoTemplate.insert(document);
            return true;
        } catch (Exception e) {

        }
        return false;
    }

    /**
     * 釋放分散式鎖
     *
     * @param lockKey 鎖的key
     */
    private void releaseLock(String lockKey) {
        Query query = new Query(Criteria.where("key").is(lockKey));
        mongoTemplate.remove(query, LockDocument.class);
        log.info("程式執行成功,釋放分散式鎖,lockKey:{}",lockKey);
    }

    /**
     * 分散式鎖入口方法,引數lockName為鎖的名稱,lockKey為需要加鎖的key,執行完成後自動釋放鎖
     *
     * @param lockKey
     * @param task
     * @param <T>
     * @throws Exception
     */
    public <T> void executeWithLock(String lockKey, ITask<T> task) throws Exception {
        boolean locked = acquireLock(lockKey);
        if (locked) {
            log.info("獲取分散式鎖成功,lockKey:{}",lockKey);
            try {
                task.execute();
            } finally {
                releaseLock(lockKey);
            }
        } else {
            log.warn("獲取分散式鎖失敗,lockKey:{}", lockKey);
            throw new AppException("獲取分散式鎖失敗!");
        }
    }

    @Data
    @Document(collection = "lock_collection")
    static class LockDocument {
        @Id
        private String id;
        @Indexed(expireAfterSeconds = DEFAULT_LOCK_TIMEOUT)
        private Instant expireAt;
    }

    @FunctionalInterface
    public interface ITask<T> {
        T execute() throws Exception;
    }
}

呼叫示例

    @Resource
    MongoDBLock mongoDBLock;

    mongoDBLock.executeWithLock("key", () -> {
        // do some thing
        return null;
    });

原理

  • 使用key作為主鍵,利用mongodb的insert原子性保障LockDocument不會重複插入
  • LockDocument中expireAt欄位利用的mongodb索引過期機制,解決死鎖問題,這裡設定超時時間是30秒,並在執行完成之後會主動釋放鎖

相關文章