基于数据库的分布式锁
记一次分布式锁-基于数据库
1:分布锁,我所了解的一共有三种方式
A:传统的数据库的全局锁
B:基于缓存的全局锁,如redis
【基于数据库的分布式锁】C:基于zookeeper的分布式锁
这三种方式的优先机是 C > B > A。因为公司架构问题,最终还是选择了第一种实现方式。所以本文只讲述关于数据库的分布式锁。以下提供相关知识的几个链接,请自行查阅
分布式锁的几种实现方式2:数据库全局锁的优缺点 优点:
Redis分布式锁的正确实现方式
分布式锁与实现(二)——基于ZooKeeper实现
简单易实现
缺点:
A:存在数据库一般是单点的,一旦数据库宕机。服务则不可用。
B:锁没有失效时间,一旦解锁失败则锁一直存在,导致服务不可用、线程阻塞
C:锁只能是非阻塞的,锁不可重入
3:数据库全局锁的解决方案 1:单点?数据库可以多搞个数据库备份。
2:没有失效时间?定时任务,隔一段时间清理一次。或者每次加锁时,插入一个期待的有效时间,下次加锁时则先判断当前时间是否大于有效时间以此判断锁是否失效。
3:非阻塞?开启另个线程循环获取
4:非重入,在加锁时加入机器信息和线程信息,下次获取时,先判断这两个字段
4:代码实例
@Component
public class ArchiveTask {private ThreadPoolTaskScheduler scheduler= null;
private ArchiveTaskDAO dao;
private ScheduledFuture> future;
private String clearDataDay = "90";
//默认迁移90天前的数据
private String wfmProcedure = null;
public static String stype = "TASK_SCHEDULER";
public static String pkey = "WFM_ARCHIVE_TASK";
public static String lock = "lock";
// 上锁状态
public static String unLock = "unLock";
// 无锁状态
public static String errorLock = "errorLock";
// 发生了错误
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(()->{
// 执行任务
runTask();
}, new Trigger(){
// 先执行 Trigger,在执行 Runnable
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
// TODO Auto-generated method stub
Date nextExecDate = null;
try {
//查询定时计划
Map taskMap = dao.getTaskSchedulerCron();
String cron = MapUtils.getString(taskMap, "cron", "");
if(cron.equals("")) {
return null;
}
// 定时任务触发,可修改定时任务的执行周期。数据库修改即可,不用重启应用。
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();
});
}privatevoid runTask(){
try{
Map 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);
CalendartmpCal = 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) 先判断是否已有其他线程、进程在执行数据迁移任务了,数据库锁
synchronized (ArchiveTask.class) {
int flag = dao.taskSchedulerLock(lockValid);
if(flag < 1){ // 无法加锁,已被其他任务锁住了
return;
}
isLock = true;
}//3)查询需要迁移的数据
Calendarcal = 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 params = new HashMap();
params.put("endDate", endDate);
params.put("iomBaseDataName", iomBaseDataName);
//4) (逻辑处理)//更新锁状态
dao.updateTaskSchedulerLock(unLock);
isLock = false;
}catch(Exception ex){
ex.printStackTrace();
//如果出错 则更新数据库锁 为 出错状态
dao.updateTaskSchedulerLock(errorLock);
isLock = false;
}
}public void releaseLock(){
if(isLock==true){
dao.updateTaskSchedulerLock(unLock);
}
}}
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- Docker应用:容器间通信与Mariadb数据库主从复制