前言 我們日常開發過程,會有一些定時任務的代碼來統計一些系統運行數據,但是我們應用有需要部署多個實例,傳統的通過配置文件來控制定時任務是否啟動又太過繁瑣,而且還經常出錯,導致一些異常數據的產生 網上有很多分散式鎖的實現方案,基於redis、zk、等有很多,但是我的就是一個用了mysql和mongo的 ...
前言
我們日常開發過程,會有一些定時任務的代碼來統計一些系統運行數據,但是我們應用有需要部署多個實例,傳統的通過配置文件來控制定時任務是否啟動又太過繁瑣,而且還經常出錯,導致一些異常數據的產生
網上有很多分散式鎖的實現方案,基於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秒,併在執行完成之後會主動釋放鎖