❤ 作者主页:李奕赫揍小邰的博客
❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~*
🍊 记得点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习!!!🎉🎉
今天再做接口系统中用户调用接口功能的时候,因为接口调用次数是有限制的,考虑到用户可能会瞬间调用大量接口次数,为了避免统计出错,需要涉及到事务和锁的知识。在这种情况下,如果我们是在分布式环境中运行的,那么可能需要使用分布式锁来保证数据的一致性。
我们平常可能背八股文,锁,事务等怎么使用,理论知识很了解,但当真正运用的时候可能会感觉无从下手。今天我就简单编写一下在正常环境和分布式环境应该怎么使用锁来确保数据的一致性和安全性。
初始代码
初始代码就是根据用户id和接口id进行判断是否为空,之后对总调用次数+1,剩余调用次数-1更新。
public boolean invokeCount(long interfaceInfoId, long userId) {
if (interfaceInfoId <= 0 || userId <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 使用 UpdateWrapper 对象来构建更新条件
UpdateWrapper<UserInterfaceInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("interfaceInfoId", interfaceInfoId);
updateWrapper.eq("userId", userId);
// leftNum=leftNum-1和totalNum=totalNum+1。意思是将leftNum字段减一,totalNum字段加一。
updateWrapper.setSql("leftNum = leftNum - 1, totalNum = totalNum + 1");
// 最后,调用update方法执行更新操作,并返回更新是否成功的结果
return this.update(updateWrapper);
}
正常环境下加锁
要确保在更新用户接口信息时的原子性、数据一致性和安全性,可以使用数据库事务和行级锁来实现。
注意:我们需要在类中提前加入注解将事务管理器注入到需要使用它的类中
@Autowired
private PlatformTransactionManager transactionManager;
@Override
@Transactional // 添加事务注解
public boolean invokeCount(long interfaceInfoId, long userId) {
if (interfaceInfoId <= 0 || userId <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 开启数据库事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 使用悲观锁来锁定要更新的记录,确保其他事务无法同时修改
UserInterfaceInfo userInterfaceInfo = this.selectForUpdate(interfaceInfoId, userId);
// 判断记录是否存在
if (userInterfaceInfo == null) {
throw new BusinessException(ErrorCode.RECORD_NOT_FOUND);
}
// 更新操作
userInterfaceInfo.setLeftNum(userInterfaceInfo.getLeftNum() - 1);
userInterfaceInfo.setTotalNum(userInterfaceInfo.getTotalNum() + 1);
boolean updateResult = this.updateById(userInterfaceInfo);
// 提交事务
transactionManager.commit(status);
return updateResult;
} catch (Exception e) {
// 发生异常时回滚事务
transactionManager.rollback(status);
throw e;
}
}
private UserInterfaceInfo selectForUpdate(long interfaceInfoId, long userId) {
return getBaseMapper().selectOne(new LambdaQueryWrapper<UserInterfaceInfo>()
.eq(UserInterfaceInfo::getInterfaceInfoId, interfaceInfoId)
.eq(UserInterfaceInfo::getUserId, userId)
.last("FOR UPDATE"));
}
上述修改的关键点在于:
1.添加 @Transactional 注解:在方法上添加 @Transactional 注解,以确保方法的执行在一个数据库事务中。
2.使用事务管理器:根据具体的实际情况,注入适当的事务管理器,并使用它来开启、提交或回滚事务。在示例中,使用了 transactionManager。
3.添加行级锁:通过在查询时使用 forUpdate 方法,可以在查询过程中锁定要更新的记录,确保其他事务无法同时修改。
4.异常处理和事务回滚:在发生异常时,通过捕获异常并调用 transactionManager.rollback(status) 来回滚事务,以确保数据的一致性。
请注意,上述修改仅提供了一种实现思路,具体的实现方式可能因您的业务需求和数据库配置而有所不同。建议在实际应用中进行充分的测试和验证,以确保事务和锁的正确使用,以及数据的一致性和安全性。
分布式环境下加锁
在分布式项目中,为了保证数据的一致性和安全性,可以使用分布式锁来控制对用户接口信息的更新操作。
@Override
public boolean invokeCount(long interfaceInfoId, long userId) {
if (interfaceInfoId <= 0 || userId <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 获取分布式锁
boolean lockAcquired = acquireDistributedLock(interfaceInfoId, userId);
if (!lockAcquired) {
throw new BusinessException(ErrorCode.LOCK_ACQUISITION_FAILED);
}
try {
// 使用悲观锁来锁定要更新的记录,确保其他事务无法同时修改
UserInterfaceInfo userInterfaceInfo = this.selectForUpdate(interfaceInfoId, userId);
// 判断记录是否存在
if (userInterfaceInfo == null) {
throw new BusinessException(ErrorCode.RECORD_NOT_FOUND);
}
// 更新操作
userInterfaceInfo.setLeftNum(userInterfaceInfo.getLeftNum() - 1);
userInterfaceInfo.setTotalNum(userInterfaceInfo.getTotalNum() + 1);
boolean updateResult = this.updateById(userInterfaceInfo);
return updateResult;
} finally {
// 释放分布式锁
releaseDistributedLock(interfaceInfoId, userId);
}
}
private boolean acquireDistributedLock(long interfaceInfoId, long userId) {
// 在这里实现获取分布式锁的逻辑,可以使用分布式锁框架(例如Redisson、ZooKeeper等)来实现。
// 获取成功返回true,获取失败返回false。
// 根据实际情况进行实现。
// 示例:
// return distributedLock.acquireLock(interfaceInfoId + "_" + userId);
// 其中,distributedLock是分布式锁框架的实例。
}
private void releaseDistributedLock(long interfaceInfoId, long userId) {
// 在这里实现释放分布式锁的逻辑,与acquireDistributedLock方法对应。
// 根据实际情况进行实现。
// 示例:
// distributedLock.releaseLock(interfaceInfoId + "_" + userId);
// 其中,distributedLock是分布式锁框架的实例。
}
private UserInterfaceInfo selectForUpdate(long interfaceInfoId, long userId) {
return getBaseMapper().selectOne(new LambdaQueryWrapper<UserInterfaceInfo>()
.eq(UserInterfaceInfo::getInterfaceInfoId, interfaceInfoId)
.eq(UserInterfaceInfo::getUserId, userId)
.last("FOR UPDATE"));
}
上述修改的关键点在于:
1.获取分布式锁:在 invokeCount 方法中,通过调用 acquireDistributedLock 方法获取分布式锁,确保只有一个线程能够执行更新操作。
2.释放分布式锁:在 finally 块中,通过调用 releaseDistributedLock 方法释放分布式锁,以便其他线程可以获取锁并执行更新操作。
2.acquireDistributedLock 和 releaseDistributedLock 方法的实现:根据您使用的分布式锁框架(例如Redisson、ZooKeeper等),实现获取和释放分布式锁的具体逻辑。
3.上述修改同样仅是提供了一种实现思路,具体的实现方式可能因使用的分布式锁框架和环境配置而有所不同。建议根据实际情况进行适当的调整和测试,以确保分布式锁的正确使用,以及数据的一致性和安全性。
总结:
我们不仅要熟悉锁,事务的理论知识,更要能够把这些理论知识实现到实际开发当中,这样我们才能更加熟练的掌握这个知识。