引言:
springBoot集成redis之后,再使用之前的jedis,会加上诸多配置和注解,很烦恼,因此,自己研究了两天,使用redisTemplate实现分布式锁。
1.锁工具类
/**
* 分布式事务锁工具类
*
* @author leimin
* @description
* @time: 2019/09/20
**/
@Component
@Slf4j
public class DistributeLockUtil {
/**
* 引入redisTemplate
*/
@Autowired
private RedisTemplate redisTemplate;
/**
* 超时时间,8s
*/
private static final long HANDLE_MSECS = 8000L;
/**
* 重复获取锁的时间间隔 50毫秒
*/
private static final long TRY_INTERVAL = 50L;
/**
* 重复获取锁的超时时间 10s
*/
private static final long TIME_OUT_MSECS = 10000L;
/**
* 获取锁
*
* @param lockName 锁的名称
* @return 释放锁的时间的时间戳
* @throws InterruptedException 中断异常
*/
public Long getLock(String lockName) {
long waitTime = System.currentTimeMillis() + TIME_OUT_MSECS;
long lockTimeout;
Object obj = redisTemplate.opsForValue().get(lockName);
long expire = obj == null ? 0L : (Long) obj;
// 时间段内,循环获取锁
while (System.currentTimeMillis() < waitTime) {
lockTimeout = System.currentTimeMillis() + HANDLE_MSECS + 1L;
// 获取到锁后,设置过期时间
if (redisTemplate.opsForValue().setIfAbsent(lockName, lockTimeout)) {
redisTemplate.expire(lockName, HANDLE_MSECS, TimeUnit.MILLISECONDS);
return lockTimeout;
// 发现死锁,重置锁的过期时间
} else if (expire < System.currentTimeMillis()) {
redisTemplate.opsForValue().set(lockName, lockTimeout, HANDLE_MSECS, TimeUnit.MILLISECONDS);
// 重置后,操作时间小于获取时间,允许等待操作时间
// 操作时间大于获取时间,肯定获取不到,直接结束;
if (HANDLE_MSECS > TIME_OUT_MSECS) {
return 0L;
}
}
try {
Thread.sleep(TRY_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return 0L;
}
/**
* 释放锁的情况:
*
* 1.业务执行过程中,发生异常必须释放锁;(调用释放方法)
* 2.业务执行完成,必须释放锁;(调用释放方法)
* 3.发生死锁,必须释放锁;(超时设置)
* 4.异步处理导致主程序过早释放锁;(取消finally,所有代码跑完才释放锁)
*/
/**
* 同步释放锁
*
* @param lockName 锁的名称
* @param lockTimeout 超时时间
*/
public void releaseLock(String lockName, long lockTimeout) {
// 获取锁的释放时间, 如果锁的释放时间和定义的释放时间一致,那么释放锁
Object lockTime = redisTemplate.opsForValue().get(lockName);
long currentLockTime = lockTime == null ? 0L : (long) lockTime;
if (lockTimeout != 0L && currentLockTime == lockTimeout) {
redisTemplate.opsForValue().getOperations().delete(lockName);
}
}
/**
* 异步释放锁
*
* @param lockName 锁的名称
*/
public void releaseLock(String lockName) {
redisTemplate.opsForValue().getOperations().delete(lockName);
}
}
2.使用锁
我的业务流程是异步的,因此,释放锁,必须在异步操作的最后
2.1 主业务
/**
* 表单数据,新增审批
*
* @param formId 表单id
* @param grantId 操作员id
* @param formDataId 表单中数据id
* @param tenantId 企业id
* @return 分装结果对象
* @author leimin
*/
@Override
@Transactional(rollbackFor = Exception.class)
public BaseVO formCreateApproval(Long formId, Long grantId, Long formDataId, Long tenantId) {
long lockTimeOut = 0L;
try {
lockTimeOut = distributeLockUtil.getLock(FormConstants.CREATE_APPROVAL_LOCK + tenantId + formDataId);
} catch (Exception e) {
return new BaseVO(BaseVO.CONFLICT_CODE, "对不起,目前系统繁忙,请稍后重试");
}
if (lockTimeOut == 0) {
return new BaseVO(BaseVO.CONFLICT_CODE, "对不起,目前系统繁忙,请稍后重试");
}
try {
FormData formData = formDao.getByFormDataId(formDataId, tenantId);
if (!auth.get(FormDataState.APPROVAL_CREATE.toString(), grantId.equals(formData.getCreator()), formData.getStatus())
|| !formData.getEditor().equals(grantId)) {
return new BaseVO(BaseVO.UNAUTHORIZED_CODE, "无权限", null);
}
//通过权限审批,修改fromData中的数据,approval_create;插入formDataApproval对象,approving;
setFormDataStatusAndIndex(formData, FormDataState.APPROVAL_CREATE);
FormDataApproval formDataApproval = createFormDataApproval(formData, FormDataApprovalState.APPROVING);
formDao.updateFormDataStatus(formData);
formApprovalDao.upsertFormDataApproval(formDataApproval);
//日志记录准备数据 jsonObject.toJSONString()
loggerUtil.saveLog(tenantId, grantId, formData, FormConstants.CREATE);
//调用工作流中提交审批接口
return audit.audit(formData, formId, grantId, formDataId, tenantId, FormConstants.CREATE, formDataApproval.getId());
} catch (Exception e) {
distributeLockUtil.releaseLock(FormConstants.CREATE_APPROVAL_LOCK + tenantId + formDataId, lockTimeOut);
return new BaseVO(BaseVO.OTHER_CODE, "操作失败!");
}
}
2.2异步业务
/**
* 审批完成后,回调接口,修改源数据
*
* @param tenantId 企业id
* @param formId 表单id
* @param dataId 数据id
* @param result 审核结果
* @param type 审核类型
* @return 返回的结果
* @author niulibing
*/
@Override
public BaseVO callback(Long tenantId, Long formId, Long dataId, String result, String type) {
try {
//根据dataId查询formDataApproval表的数据,查询状态为审核中的状态的数据
FormDataApproval formDataApproval = formApprovalDao.findOneByDataId(tenantId, dataId);
if (formDataApproval == null) {
return new BaseVO(BaseVO.OTHER_CODE, "不存在该条记录");
}
//审核类型:create
switch (type) {
//审核类型:create
case FormConstants.CREATE:
return createApprovalCallBack(tenantId, formId, dataId, result, formDataApproval);
//审核类型:edit
case FormConstants.EDIT:
return editApprovalCallBack(tenantId, formId, dataId, result, formDataApproval);
//审核类型:delete
case FormConstants.DELETE:
return deleteApprovalCallBack(tenantId, formId, dataId, result, formDataApproval);
default:
return new BaseVO(BaseVO.OTHER_CODE, "审核方式不存在,仅支持create、edit、delete,请确认后提交。");
}
} finally {
switch (type) {
case FormConstants.CREATE:
distributeLockUtil.releaseLock(FormConstants.CREATE_APPROVAL_LOCK + tenantId + dataId);
break;
case FormConstants.EDIT:
distributeLockUtil.releaseLock(FormConstants.EDIT_APPROVAL_LOCK + tenantId + dataId);
break;
default:
}
}
}
redisTemplate相关方法参考:https://www.cnblogs.com/yanan7890/p/6617305.html