- deleteLocked 方法:
java
public R deleteLocked(String id, String username) {
String examReportUserKey = "examReportId_" + id + "_" + username;
stringRedisTemplate.delete(examReportUserKey);
return R.ok();
}
功能:删除指定用户对某个 ExamReport 的锁。
实现:通过删除 Redis 中对应的键来释放锁。
- 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",则表示未被锁定;否则表示已被锁定。
- 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,并去重。
- 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 小时。
- 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():执行查询并返回一条记录。
- 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,直接返回请求结果。
分布式锁的实现原理
- 锁的获取
checkUserLocked 方法:
为指定用户对某个 ExamReport 设置锁。
键的格式为 examReportId_{id}_{username},值为 "1",过期时间为 1 小时。
- 锁的检查
checkIfLocked 方法:
检查某个 ExamReport 是否被其他用户锁定。
通过查询 Redis 中的键来判断是否存在与当前用户相关的锁,并且该锁的值为 "1"。
如果存在且值为 "1",则表示未被锁定;否则表示已被锁定。
- 锁的释放
deleteLocked 方法:
删除指定用户对某个 ExamReport 的锁。
通过删除 Redis 中对应的键来释放锁。
- 查询记录
examReportService.lambdaQuery 方法:
查询符合条件的 ExamReport 记录,并排除已经获取到的锁 ID。
确保返回的记录是未被锁定的。
- 查询验证列表
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 的分布式锁机制,确保在同一时间只有一个用户可以访问某个资源,从而避免并发冲突。虽然存在一些性能和竞态条件的问题,但在大多数场景下,这种实现方式是有效且可靠的。