目录
一、分布式锁的定义与核心作用
二、分布式锁与普通锁的核心区别
三、分布式锁的底层原理与实现方式
1. 核心实现原理
2. 主流实现方案对比
3. 关键技术细节
四、典型问题与解决方案
五、总结
六、具体代码实现
一、分布式锁的定义与核心作用
分布式锁是一种在分布式系统中协调多进程/节点对共享资源进行互斥访问的机制。其核心作用是确保同一时间只有一个进程能够操作共享资源,解决分布式环境下的并发冲突问题(如超卖、数据覆盖等)。
二、分布式锁与普通锁的核心区别
对比维度 | 普通锁(线程/进程锁) | 分布式锁 |
作用范围 | 单机环境(同一JVM或进程内) | 跨机器、跨JVM的分布式环境 |
数据存储 | 基于内存(如synchronized、Lock) | 基于外部存储(如Redis、ZooKeeper、数据库) |
锁失效风险 | 无网络延迟或节点故障风险 | 需处理网络分区、节点宕机、时钟同步等问题 |
典型应用场景 | 单机多线程资源竞争 | 分布式服务、微服务集群、数据库流量控制等 |
三、分布式锁的底层原理与实现方式
1. 核心实现原理
- 互斥性:通过唯一标识(如Redis的Key、ZooKeeper节点路径)确保同一时间仅有一个客户端持有锁。
- 超时机制:设置锁的过期时间,避免死锁(如Redis的
PX
参数)。 - 原子性操作:加锁、解锁需通过原子命令(如Redis的
SETNX
+EXPIRE
组合或Lua脚本)实现。
2. 主流实现方案对比
实现方式 | 原理 | 优点 | 缺点 |
Redis | 基于 命令,结合唯一值(UUID)和Lua脚本保证原子性 。 | 高性能、易扩展 | 主从切换可能导致锁失效(需RedLock或Redisson优化) |
ZooKeeper | 基于临时有序节点 ,最小序号节点获得锁,通过Watcher监听节点变化 。 | 强一致性、自动释放锁(节点断开则删除临时节点) | 性能较低、实现复杂 |
数据库 | 通过唯一约束(如MySQL行锁、乐观锁)或专用锁表 。 | 简单易用 | 性能差、高并发场景易成瓶颈 |
3. 关键技术细节
- 锁续期(看门狗机制):Redisson通过后台线程定期检查并延长锁有效期,避免业务未完成时锁过期。
- 可重入性:通过记录线程标识和重入次数(如Redis的Hash结构)支持同一线程多次加锁。
- 容错设计:
-
- Redis的RedLock算法需半数以上节点加锁成功,避免主从切换问题。
- ZooKeeper通过临时节点自动清理解决进程宕机导致的死锁。
四、典型问题与解决方案
- 锁过期但业务未完成
-
- 方案:使用守护线程续期(如Redisson的看门狗)或超时回滚+告警。
- 锁误删(非持有者释放锁)
-
- 方案:解锁时校验唯一标识(如UUID),并通过Lua脚本保证原子性。
五、总结
分布式锁通过外部存储系统实现跨进程资源互斥,需权衡性能、一致性和复杂度。Redis适合高频低一致性要求的场景,ZooKeeper适用于强一致性但低并发场景,而数据库锁仅作为简单场景的备选。实际选型需结合业务需求和容错能力(如Redisson整合Redis的方案较优)。
六、具体代码实现
我们这边是查询数据
首先如果缓存命中 就直接返回数据
否则是要去数据库查询数据
使用分布式锁 让同一时间只能允许一个线程更新缓存
防止碰巧有写入缓存的线程结束
我们可以进行一个二次检查 防止那个碰巧情况
因为缓存一旦存在 再次写入 数据会进行叠加
确认了在分布式锁内 缓存依旧为空
之后我们就可以去数据库查询数据
@Override
// 这边我们使用redis来辅助mysql查询 因为数据库压力实在是太大了(服务器带宽太低)
public List<GetAllContentResp> getAll() {
// 异常处理
try {
// 1. 构建带业务标识的复合Key
String cacheKey = "balloonSentences:all" + DATA_VERSION;
// 2. 带熔断的缓存读取 如果缓存击中 直接返回即可 返回的是所有数据
List<GetAllContentResp> cachedData = redisService.getList(cacheKey, 0, -1);
if (cachedData != null) {
if (cachedData.isEmpty()) { // 空值缓存处理
return Collections.emptyList();
}
elasticsearchService.saveProduct(cachedData); // 写到elasticsearch里面去
return cachedData;
} else {
// 3. 分布式锁防穿透 同一时间只允许一个线程更新缓存
RLock lock = redissonClient.getLock("lock:" + cacheKey);
try {
lock.lock(5, TimeUnit.SECONDS);
// 二次检查
cachedData = redisService.getList(cacheKey, 0, -1);
if (cachedData != null) return cachedData;
// 4. 数据库查询
List<GetAllContentResp> dbData = tSentencesMapper.getAll();
// 5. 异步写缓存和elasticsearch(保证数据库操作成功)
CompletableFuture.runAsync(() -> {
// 随机化TTL防雪崩
redisService.setList(cacheKey, dbData, RandomUtil.randomInt(30, 60), TimeUnit.MINUTES);
elasticsearchService.saveProduct(dbData); // 写到elasticsearch里面去
});
return dbData;
} finally {
lock.unlock();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}