有一次线上问题排查,发现
有一个这个问题,后面排查之后发现是因为我们的锁是在数据库中实现的,并且解锁之后是不删除锁的,只是去把锁的状态改成解锁。
而在去查询锁的时候一般是根据object_id 和lock_object去查询的时候如果有两个锁就会不知道解锁哪个,所以需要改造业务
改造锁业务改造成,插入的时候先比较确保一下不在再插入,并附带重试机制
if (lock == null) { //有相同object_id的时候重试5次,超过5次就业务繁忙不允许操作了
int retryTimes = 5;
while (retryTimes-- > 0) {
int lockId = 0;
try {
BusinessLock newLock = new BusinessLock();
/*String timeStr = LocalTime.now().format(DateTimeFormatter.ofPattern("HHmmssSSS"));*/
int newLockId = businessLockDao.getNewLockId();
newLock.setLockId(newLockId);
newLock.setLockObject(lockObject);
newLock.setObjectId(objectId);
newLock.setLockStatus(LockStatusEnum.LOCKED.getStatus());
newLock.setLockTime(new Date());
newLock.setOwnerId(requestEmpId);
newLock.setLockCount(1);
int result = businessLockDao.insertIfNotExists(
newLock.getLockId(),
newLock.getLockObject(),
newLock.getObjectId(),
newLock.getLockStatus(),
newLock.getLockTime(),
newLock.getOwnerId());
businessLockDao.flush();;
if (result == 1) {
lock = newLock;
break;
} else {
Thread.sleep(RandomUtils.nextInt(500, 1500)); // 随机短暂等待
}
} catch (Exception e) {
logger.warn("lockByobjectId general fail lockId={},异常:{}", lockId, e.getMessage(), e);
try {
Thread.sleep(RandomUtils.nextInt(500, 1500));
} catch (InterruptedException ex) {
logger.info(ex.getMessage());
Thread.currentThread().interrupt();
}
}
并且解锁的时候直接去根据lockId把锁直接删掉,避免数据库冗余。
又因为原来的数据库设计lock_id只有9位,如果去改位数的话改动太大了要改entity之类的全部都要改,要去避免重复生成lock_id 比较困难,一开始打算用时间戳加线程id加随机数去拼9位数,但是写测试脚本之后发现在高并发的时候还是会有主键生成重复的问题,
于是我就想到用Redis,后面发现我们没有部署redis,我就去ai找了下结果发现orcale是有提供这种类似redis递增序列这种线程安全的生生成id 通过Sequences可以线程安全的生成不重复的id
CREATE SEQUENCE SEQ_NEW_BUSINESS_LOCK_ID START WITH 100000000 INCREMENT BY 1 MAXVALUE 999999999 NOCYCLE NOCACHE;
利用这个就可以实现业务功能啦!