記一次分布式鎖-基于數(shù)據(jù)庫
1:分布鎖碱妆,我所了解的一共有三種方式
A:傳統(tǒng)的數(shù)據(jù)庫的全局鎖
B:基于緩存的全局鎖拓轻,如redis
C:基于zookeeper的分布式鎖
這三種方式的優(yōu)先機是 C > B > A洛心。因為公司架構問題乳讥,最終還是選擇了第一種實現(xiàn)方式考蕾。所以本文只講述關于數(shù)據(jù)庫的分布式鎖掖肋。以下提供相關知識的幾個鏈接甩苛,請自行查閱
2:數(shù)據(jù)庫全局鎖的優(yōu)缺點
優(yōu)點:
簡單易實現(xiàn)
缺點:
A:存在數(shù)據(jù)庫一般是單點的蹂楣,一旦數(shù)據(jù)庫宕機。服務則不可用浪藻。
B:鎖沒有失效時間捐迫,一旦解鎖失敗則鎖一直存在,導致服務不可用爱葵、線程阻塞
C:鎖只能是非阻塞的施戴,鎖不可重入
3:數(shù)據(jù)庫全局鎖的解決方案
1:單點反浓?數(shù)據(jù)庫可以多搞個數(shù)據(jù)庫備份。
2:沒有失效時間赞哗?定時任務雷则,隔一段時間清理一次》舅瘢或者每次加鎖時月劈,插入一個期待的有效時間,下次加鎖時則先判斷當前時間是否大于有效時間以此判斷鎖是否失效藤乙。
3:非阻塞猜揪?開啟另個線程循環(huán)獲取
4:非重入,在加鎖時加入機器信息和線程信息坛梁,下次獲取時而姐,先判斷這兩個字段
4:代碼實例
@Component
public class ArchiveTask {
private ThreadPoolTaskScheduler scheduler = null;
private ArchiveTaskDAO dao;
private ScheduledFuture<?> future;
private String clearDataDay = "90"; //默認遷移90天前的數(shù)據(jù)
private String wfmProcedure = null;
public static String stype = "TASK_SCHEDULER";
public static String pkey = "WFM_ARCHIVE_TASK";
public static String lock = "lock"; // 上鎖狀態(tài)
public static String unLock = "unLock"; // 無鎖狀態(tài)
public static String errorLock = "errorLock"; // 發(fā)生了錯誤
public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private boolean isLock = false;
//private Logger logger = LoggerFactory.getLogger(ArchiveTask.class);
public void init(){
scheduler = new ThreadPoolTaskScheduler();
dao = (ArchiveTaskDAO) BaseDAOFactory.getDAO(ArchiveTaskDAO.class.getName());
wfmProcedure = Optional.ofNullable(dao.getConfigByType("ORDER", "HIS_SAVE_ORDERHIS_BY_ORDERID"))
.orElse("HIS_SAVE_ORDERHIS_BY_ORDERID");
}
public void schedule(){
scheduler.initialize();
future = scheduler.schedule(()->{
// 執(zhí)行任務
runTask();
}, new Trigger(){
// 先執(zhí)行 Trigger,在執(zhí)行 Runnable
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
// TODO Auto-generated method stub
Date nextExecDate = null;
try {
//查詢定時計劃
Map<String,Object> taskMap = dao.getTaskSchedulerCron();
String cron = MapUtils.getString(taskMap, "cron", "");
if(cron.equals("")) {
return null;
}
// 定時任務觸發(fā)划咐,可修改定時任務的執(zhí)行周期拴念。數(shù)據(jù)庫修改即可,不用重啟應用褐缠。
CronTrigger trigger = new CronTrigger(cron);
nextExecDate = trigger.nextExecutionTime(triggerContext);
//計算時間差
Long timeDiffSecond = (nextExecDate.getTime() - new Date().getTime())/1000;
dao.updateTaskNextTimeDiff(timeDiffSecond.toString());
}catch (Exception e) {
e.printStackTrace();
}
return nextExecDate;
}
});
}
public void stop(){
future.cancel(true);
}
public void reStart(){
future.cancel(true);
schedule();
}
public void test(){ // 不通過定時任務,測試
scheduler.initialize();
scheduler.execute(()->{
runTask();
});
}
private void runTask(){
try{
Map<String,Object> taskMap = dao.getTaskSchedulerCron();
//0) 功能開關政鼠,如果關閉則直接跳過
String openStatus = MapUtils.getString(taskMap, "openStatus","");
if(!openStatus.equals("open")){
return;
}
//1)查詢鎖的有效時間
boolean lockValid = true; // 判斷之前的加鎖時間是否有效
String timeDiffSecondStr = MapUtils.getString(taskMap, "timeDiffSecond","");
String modifyDay = MapUtils.getString(taskMap, "modifyDay", ""); // 最近修改鎖時間
if(modifyDay.equals("")||timeDiffSecondStr.equals("")){
lockValid = false;
}else{
Long timeDiffSecond = Long.parseLong(timeDiffSecondStr);
Date modifyDate = sdf.parse(modifyDay);
Calendar tmpCal = Calendar.getInstance();
tmpCal.setTime(modifyDate);
tmpCal.add(Calendar.SECOND, timeDiffSecond.intValue());
Date curDateForValid = tmpCal.getTime(); //鎖的有效時間
Date curDate = new Date();
if(curDate.after(curDateForValid)){ //鎖失效
lockValid = false;
}
}
//2) 先判斷是否已有其他線程、進程在執(zhí)行數(shù)據(jù)遷移任務了,數(shù)據(jù)庫鎖
synchronized (ArchiveTask.class) {
int flag = dao.taskSchedulerLock(lockValid);
if(flag < 1){ // 無法加鎖队魏,已被其他任務鎖住了
return;
}
isLock = true;
}
//3)查詢需要遷移的數(shù)據(jù)
Calendar cal = Calendar.getInstance();
String dayStr = MapUtils.getString(taskMap, "day", clearDataDay);
Integer dayI = Integer.parseInt(dayStr);
cal.add(Calendar.DAY_OF_YEAR, - dayI.intValue());
Date endDate = cal.getTime(); // 截止日期
Map<String,Object> params = new HashMap<String, Object>();
params.put("endDate", endDate);
params.put("iomBaseDataName", iomBaseDataName);
//4) (邏輯處理)
//更新鎖狀態(tài)
dao.updateTaskSchedulerLock(unLock);
isLock = false;
}catch(Exception ex){
ex.printStackTrace();
//如果出錯 則更新數(shù)據(jù)庫鎖 為 出錯狀態(tài)
dao.updateTaskSchedulerLock(errorLock);
isLock = false;
}
}
public void releaseLock(){
if(isLock==true){
dao.updateTaskSchedulerLock(unLock);
}
}
}