当前位置: 首页 > news >正文

分布式锁(防止同时操作同一条数据)实现分析

1. deleteLocked 方法:

public R deleteLocked(String id, String username) {String examReportUserKey = "examReportId_" + id + "_" + username;stringRedisTemplate.delete(examReportUserKey);return R.ok();
}

功能:删除指定用户对某个 ExamReport 的锁。
实现:通过删除 Redis 中对应的键来释放锁。

2. checkIfLocked 方法:

public boolean checkIfLocked(Long id, String username) {String examReportKey = "examReportId_" + id + "_";String examReportUserKey = "examReportId_" + id + "_" + username;Set keys = stringRedisTemplate.keys(examReportKey + "*");boolean iflocked = true;if (keys == null || keys.size() == 0) {iflocked = false;}for (Object key : keys) {if (examReportUserKey.equals(key.toString())) {String lockflag = stringRedisTemplate.opsForValue().get(examReportUserKey).toString();if (lockflag.equals("1")) {iflocked = false;break;}}}return iflocked;
}
  • 功能:检查某个 ExamReport 是否被其他用户锁定。
  • 实现:
    获取所有以 examReportKey 开头的键。
    检查是否存在与当前用户相关的键,并且该键的值为 "1"。
    如果存在且值为 "1",则表示未被锁定;否则表示已被锁定。

3. getLockIds 方法:

public String getLockIds(String username) {String examReportKey = "examReportId_";Set keys = stringRedisTemplate.keys(examReportKey + "*");String lockIds = "";List<String> lockIdsList = new ArrayList<>();for (Object key : keys) {String ids = key.toString();String[] parts = ids.split("_");if (!parts[2].equals(username)) {if (!lockIdsList.contains(parts[1])) {lockIdsList.add(parts[1]);}}}lockIds = String.join(",", lockIdsList);return lockIds;
}
  • 功能:获取与特定用户名不匹配的锁 ID 列表。
  • 实现:
    获取所有以 examReportKey 开头的键。
    提取与特定用户名不匹配的锁 ID,并去重。

4. checkUserLocked 方法

public void checkUserLocked(Long id, String username) {String examReportUserKey = "examReportId_" + id + "_" + username;stringRedisTemplate.opsForValue().set(examReportUserKey, "1", 1, TimeUnit.HOURS);
}
  • 功能:为指定用户对某个 ExamReport 设置锁。
  • 实现:
    在 Redis 中设置一个键,键名为 examReportUserKey,值为 "1",过期时间为 1 小时。

5. examReportService.lambdaQuery 方法

ExamReport examReport = examReportService.lambdaQuery().eq(ExamReport::getDelFlag, "0").eq(ExamReport::getStatus, 1).eq(ExamReport::getId, id).notIn(StringUtils.hasLength(ids), ExamReport::getId, ids).last("limit 1").one();
  • 功能:查询符合条件的 ExamReport 记录,并排除已经获取到的锁 ID。
  • 实现:
    eq(ExamReport::getDelFlag, "0"):查询 del_flag 为 "0" 的记录。
    eq(ExamReport::getStatus, 1):查询 status 为 1 的记录。
    eq(ExamReport::getId, id):查询 id 为指定 id 的记录。
    notIn(StringUtils.hasLength(ids), ExamReport::getId, ids):如果 ids 不为空,则排除 id 在 ids 列表中的记录。
    last("limit 1"):在查询的最后添加 LIMIT 1,确保只返回一条记录。
    one():执行查询并返回一条记录。

6. queryVerifyList 方法

public R queryVerifyList(@Param("prevId") String prevId) {String username = SecurityUtils.getUser().getUsername();String ids = getLockIds(username);RestTemplate restTemplate = new RestTemplate();Map<String, Object> map = new HashMap<>();map.put("ids", ids);map.put("prevId", prevId);String url = basePath + "/appapi/queryVList?ids={ids}&prevId={prevId}";R result = restTemplate.getForObject(url, R.class, map);if (result == null) {return R.failed("请求失败");}if (result.getCode() == 200) {ExamReportCheckVO examReportCheckVO = JSON.parseObject(JSON.toJSONString(result.getData()), ExamReportCheckVO.class);checkUserLocked(examReportCheckVO.getId(), username);deleteLocked(prevId, username);return R.ok(examReportCheckVO);}return result;
}
  • 功能:查询验证列表,并处理锁的获取和释放。
  • 实现:
    获取当前用户的用户名。
    调用 getLockIds 方法获取当前用户未锁定的 ExamReport ID 列表。
    使用 RestTemplate 发送 HTTP GET 请求到指定 URL,传递 ids 和 prevId 参数。
    处理请求结果:
    如果请求失败,返回失败响应。
    如果请求成功且状态码为 200,解析返回的数据为 ExamReportCheckVO 对象。
    调用 checkUserLocked 方法为新的 ExamReport 设置锁。
    调用 deleteLocked 方法释放旧的 ExamReport 锁。
    返回成功响应,包含 ExamReportCheckVO 对象。
    如果请求成功但状态码不是 200,直接返回请求结果。

分布式锁的实现原理
1. 锁的获取
checkUserLocked 方法:
为指定用户对某个 ExamReport 设置锁。
键的格式为 examReportId_{id}_{username},值为 "1",过期时间为 1 小时。
2. 锁的检查
checkIfLocked 方法:
检查某个 ExamReport 是否被其他用户锁定。
通过查询 Redis 中的键来判断是否存在与当前用户相关的锁,并且该锁的值为 "1"。
如果存在且值为 "1",则表示未被锁定;否则表示已被锁定。
3. 锁的释放
deleteLocked 方法:
删除指定用户对某个 ExamReport 的锁。
通过删除 Redis 中对应的键来释放锁。
4. 查询记录
examReportService.lambdaQuery 方法:
查询符合条件的 ExamReport 记录,并排除已经获取到的锁 ID。
确保返回的记录是未被锁定的。
5. 查询验证列表
queryVerifyList 方法:
获取当前用户的用户名。
获取当前用户未锁定的 ExamReport ID 列表。
发送 HTTP 请求查询验证列表。
处理请求结果,设置新锁并释放旧锁。
优缺点
优点
简单易懂:实现逻辑简单,容易理解和维护。
高可用性:利用 Redis 的高可用性和持久化特性,确保锁的可靠性和一致性。
自动过期:设置锁的过期时间,避免死锁问题。
缺点
性能问题:频繁的 keys 操作可能会导致性能瓶颈,特别是在数据量较大的情况下。
竞态条件:在高并发环境下,可能存在竞态条件,导致锁的不一致。
依赖 Redis:整个锁机制依赖于 Redis,如果 Redis 出现故障,锁机制将失效。
示例
假设 Redis 中有以下键:
examReportId_123_user1
examReportId_456_user1
examReportId_789_user2
examReportId_101_user3
操作流程
获取锁 ID 列表:
调用 getLockIds("user1") 会返回 789,101。
查询记录:
使用 examReportService.lambdaQuery 方法查询符合条件的 ExamReport 记录,并排除 789 和 101 这些已经被锁定的 ID。
检查锁状态:
调用 checkIfLocked(123, "user1") 会返回 false,表示 123 已经被 user1 锁定。
释放锁:
调用 deleteLocked("123", "user1") 会删除 examReportId_123_user1 键,释放锁。
查询验证列表:
调用 queryVerifyList("123") 会发送 HTTP 请求查询验证列表,并处理锁的获取和释放。
总结
通过上述方法,实现了一个基于 Redis 的分布式锁机制,确保在同一时间只有一个用户可以访问某个资源,从而避免并发冲突。虽然存在一些性能和竞态条件的问题,但在大多数场景下,这种实现方式是有效且可靠的。

http://www.lryc.cn/news/478734.html

相关文章:

  • 【已解决,含泪总结】Ubuntu18.04下非root用户Anaconda3卸载重装,conda install终于不再报错
  • 大语言模型(LLM)量化基础知识(一)
  • hadoop面试题
  • mysql 安装 windows
  • 24下软考中级网络工程师考前必背22页
  • Java类和对象(下篇)
  • k8s图形化显示(KRM)
  • apache poi 实现下拉框联动校验
  • 【canal 中间件】canal 实时监听 binlog
  • JVM垃圾收集算法、对应收集器和选择建议
  • 如何在算家云搭建Aatrox-Bert-VITS2(音频生成)
  • ceph灾备之cephfs snapshot mirror和rsync对比
  • 【工具分享】Plutocrypt勒索病毒解密工具
  • IDEA启动提示Downloading pre-built shared indexes
  • [HCTF 2018]WarmUp 1--详细解析
  • 软考教材重点内容 信息安全工程师 第1章 网络信息安全概述
  • TOSHIBA 74VHC00FT COMS汽车、工业企业的选择
  • 【Android】使用productFlavors构建多个变体
  • ubuntu 22.04 防火墙 ufw
  • MySQL压缩版安装详细图解
  • elementui中的新增弹窗在新增数据成功后再新增 发现数据无法清除解决方法
  • 软件开发项目管理:实现目标的实用指南
  • Jenkins面试整理-如何在 Jenkins 中进行并行构建?
  • DPDK(F-Stack) 实现UDP通信
  • 基于ExtendSim的库存与订购实验
  • 操作系统个人八股文总结
  • scala set训练
  • 【d63】【Java】【力扣】141.训练计划III
  • 【Linux】- 权限(2)
  • 如何设置内网IP的端口映射到公网