最近在开发过程中遇到一个问题,客户可以有多次领取红包,但是只能有一个待消费的红包。在开发过程中有同时提议使用锁synchronized,但是该关键字只对单体应用有效。考虑到当前生产服务器涉及到分布式部署,且甲方不允许引入redis,基于oracle的的唯一约束实现了分布式锁,保证客户在获取红包时先查询是否有待消费的红包,没有领取,有则提示客户不允许领取。
package com.changshin.lock;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.changshin.dao.CustBusinessLockDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Optional;
import java.util.function.Function;
/**
* 基于数据库唯一索引实现分布式锁
*/
@Service
@Slf4j
public class LockService {
@Autowired
private CustBusinessLockDao lockDao;
/**
* 客户业务锁
*
* @param lockVO 锁信息
* @param function 上锁后执行的逻辑
* @param param 逻辑上送的参数
* @param lockSecond 锁失效时间
* @param <T> 入参
* @param <R> 返回参数
* @return 返回值
*/
public <T, R> Object custBusinessLock(BusinessLockVO lockVO, Function<T, R> function, T param, int lockSecond) {
//校验锁数据
if (lockVO == null || lockVO.getBusinessType() == null || lockVO.getIdNo() == null) {
throw new RuntimeException("参数异常");
}
//根据证件号+业务类型查询数据是否存在
CustBusinessLockPO hisLockPO = lockDao.queryLockRecordByIdAndBsnType(lockVO.getIdNo(), lockVO.getBusinessType());
if (Optional.ofNullable(hisLockPO).isPresent()) {
//判断是否在超时时间范围内
if (DateUtil.between(new Date(), hisLockPO.getOpTime(), DateUnit.SECOND) - lockSecond > 0) {
//超过了超时时间删除锁数据
lockDao.deleteById(hisLockPO.getLockSeq());
} else {
throw new RuntimeException("重复操作请稍后再试");
}
}
//插入锁数据
CustBusinessLockPO custBusinessLockPO = new CustBusinessLockPO();
custBusinessLockPO.setLockSeq((new Date()).getTime());
custBusinessLockPO.setIdNo(lockVO.getIdNo());
custBusinessLockPO.setBusinessType(lockVO.getBusinessType());
custBusinessLockPO.setOpTime(new Date());
try {
lockDao.insert(custBusinessLockPO);
} catch (Throwable e) {
log.error("插入数据异常", e);
throw new RuntimeException("重复操作请稍后再试");
}
//执行业务并释放锁
R ret = null;
try {
//执行实际业务
ret = function.apply(param);
} catch (Exception e) {
log.error("执行过程出现异常", e);
throw new RuntimeException("重复操作请稍后再试");
} finally {
//根据主键删除锁数据
lockDao.deleteById(custBusinessLockPO.getLockSeq());
}
return ret;
}
}
package com.changshin.lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 基于数据库唯一索引实现分布式锁
*/
@Service
@Slf4j
public class BsnService {
public Integer getTime(String param) {
try {
log.info(Thread.currentThread().getName() + "执行睡眠start");
Thread.sleep(50000);
log.info(Thread.currentThread().getName() + "执行睡眠end");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}
}
package com.changshin.lock;
import lombok.Getter;
/**
* 业务锁枚举
*/
@Getter
public enum BusinessLockEnum {
RED_PACKAGE("1001","红包锁");
BusinessLockEnum(String code,String desc){
this.code = code;
this.desc = desc;
}
private String code;
private String desc;
}
package com.changshin.lock;
import lombok.Data;
@Data
public class BusinessLockVO {
/**
* 业务类型
*/
private String businessType;
/**
* 证件号
*/
private String idNo;
}
package com.changshin.lock;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
/**
* 对businessType+idNo加唯一索引
*/
@TableName("cust_business_lock")
@Data
public class CustBusinessLockPO {
/**
* 锁顺序号
*/
@TableId("lock_seq")
private Long lockSeq;
/**
* 业务类型
*/
@TableField("business_type")
private String businessType;
/**
* 证件号
*/
@TableField("id_no")
private String idNo;
/**
* 操作时间
*/
@TableField("op_time")
private Date opTime;
}
package com.changshin.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.changshin.lock.CustBusinessLockPO;
import org.apache.ibatis.annotations.Param;
/**
* dao
*/
public interface CustBusinessLockDao extends BaseMapper<CustBusinessLockPO> {
/**
* 根据证件及业务类型获取锁记录
*
* @param idNo 证件类型
* @param businessType 业务类型
* @return 锁记录
*/
CustBusinessLockPO queryLockRecordByIdAndBsnType(@Param("idNo") String idNo, @Param("businessType")String businessType);
}