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

  1. deleteLocked 方法:
java 复制代码
public R deleteLocked(String id, String username) {
    String examReportUserKey = "examReportId_" + id + "_" + username;
    stringRedisTemplate.delete(examReportUserKey);
    return R.ok();
}

功能:删除指定用户对某个 ExamReport 的锁。

实现:通过删除 Redis 中对应的键来释放锁。

  1. checkIfLocked 方法:
java 复制代码
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",则表示未被锁定;否则表示已被锁定。
  1. getLockIds 方法:
java 复制代码
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,并去重。
  1. checkUserLocked 方法
java 复制代码
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 小时。
  1. examReportService.lambdaQuery 方法
java 复制代码
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():执行查询并返回一条记录。
  1. queryVerifyList 方法
java 复制代码
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 小时。

  1. 锁的检查

checkIfLocked 方法:

检查某个 ExamReport 是否被其他用户锁定。

通过查询 Redis 中的键来判断是否存在与当前用户相关的锁,并且该锁的值为 "1"。

如果存在且值为 "1",则表示未被锁定;否则表示已被锁定。

  1. 锁的释放

deleteLocked 方法:

删除指定用户对某个 ExamReport 的锁。

通过删除 Redis 中对应的键来释放锁。

  1. 查询记录

examReportService.lambdaQuery 方法:

查询符合条件的 ExamReport 记录,并排除已经获取到的锁 ID。

确保返回的记录是未被锁定的。

  1. 查询验证列表

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 的分布式锁机制,确保在同一时间只有一个用户可以访问某个资源,从而避免并发冲突。虽然存在一些性能和竞态条件的问题,但在大多数场景下,这种实现方式是有效且可靠的。

相关推荐
QQ_115432031几秒前
基于Java+SpringBoot+Mysql在线简单拍卖竞价拍卖竞拍系统功能设计与实现四
java·spring boot·mysql·毕业设计·毕业源码·竞拍系统·竞拍平台
LightOfNight6 分钟前
一文学会Golang里拼接字符串的6种方式(性能对比)
开发语言·golang
fa_lsyk6 分钟前
Spring:AOP面向切面案例讲解AOP核心概念
java·后端·spring
陈奕迅本讯6 分钟前
人力资源项目学习
java·学习
2401_878467329 分钟前
大连环保公益管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·学习·tomcat·maven
Genius Kim12 分钟前
JWT加解密应用方案设计与实现
java·开发语言
Matlab程序猿小助手17 分钟前
【MATLAB源码-第222期】基于matlab的改进蚁群算法三维栅格地图路径规划,加入精英蚁群策略。包括起点终点,障碍物,着火点,楼梯。
开发语言·人工智能·算法·matlab·机器人·无人机
大佬,救命!!!19 分钟前
Python编程整理汇总(基础汇总版)
开发语言·笔记·python·pycharm·学习方法·启发式算法
Python私教20 分钟前
Python 使用 Token 认证方案连接 Kubernetes (k8s) 的详细过程
开发语言·python·kubernetes
攻城狮_Dream21 分钟前
Python 版本的 2024详细代码
开发语言·python·pygame