基于数据库唯一索引的分布式锁

该博客介绍了在分布式环境中,如何利用Oracle数据库的唯一约束来实现分布式锁,确保客户在同一时间只能拥有一份待消费红包。在领取红包时,系统会先检查是否有待消费红包,如果存在则提示用户,否则插入新的锁记录并执行业务,最后删除锁记录以释放锁。此解决方案避免了使用synchronized和redis,适用于不支持额外缓存的场景。
摘要由CSDN通过智能技术生成

          最近在开发过程中遇到一个问题,客户可以有多次领取红包,但是只能有一个待消费的红包。在开发过程中有同时提议使用锁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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值