Java主流分布式解决方案多场景设计与实战

150 阅读4分钟

Java主流分布式解决方案多场景设计与实战

核心代码,注释必读

// download:3w ukoou com

分布式锁多种解决方案

分布式锁是在分布式系统中控制同一资源被多个节点访问的一种机制。下面列举了一些常见的分布式锁的解决方案:

  1. 数据库乐观锁/悲观锁:根据数据库的特性,可以使用乐观锁和悲观锁实现分布式锁。其中,乐观锁适用于读操作较多的场景,悲观锁适用于写操作较多的环境。
  2. 基于 Redis 的分布式锁:Redis 可以提供一个 setnx(key, value) 的原子操作,可以设置一个 key,并且在该 key 存在的时候不进行任何操作。Redis 还可以设置 key 的有效期,使得在锁可能被遗忘的情况下可以自动释放锁。
  3. ZooKeeper 分布式锁:ZooKeeper 提供的分布式锁,是通过创建临时 znode 来实现的。所有的客户端都会去创建同一个 znode,最终只有一个客户端能成功,那么创建成功的客户端就获取到了锁。
  4. 基于 etcd 的分布式锁:etcd 是一种分布式键值存储,可以支持分布式锁的实现。客户端可以尝试创建特定的 key,如果成功,那么该客户端就获取到了锁,如果失败,则说明有其他客户端已经获取了锁。
  5. 基于 Chubby 的分布式锁:Chubby 是 Google 开发的一种分布式锁服务,其基本思想是为分布式应用提供一个简单的、大概念上的文件系统,应用可以在这个文件系统中创建锁。
  6. 基于 Consul 的分布式锁:Consul 是一种服务发现框架,也可以实现分布式锁。Consul 提供了基于 session 的分布式锁。

Java主流分布式解决方案多场景设计与实战 - 分布式锁实现高并发引起的超卖问题

在高并发场景下,分布式锁是一种常用的解决方案,用于控制对共享资源的访问。然而,分布式锁的实现可能会引发超卖问题,即多个并发请求同时获取到了锁,导致资源被重复使用或超出预期的数量。

下面是一种实战中常见的分布式锁实现方式(基于Redis):

  1. 客户端请求到达后,尝试获取分布式锁。
  2. 使用Redis的SETNX命令(SET if Not eXists)来尝试将一个唯一标识符作为锁的键名存储到Redis中。如果返回1,则表示获取到了锁;如果返回0,则表示锁已被其他客户端持有,请求需要等待一段时间后重试。
  3. 获取到锁后,执行业务逻辑,处理资源。
  4. 业务逻辑执行完毕后,释放锁,使用Redis的DEL命令删除锁的键。

然而,这种简单的分布式锁实现方式可能会引发超卖问题。考虑以下情况:

  1. 客户端A获取到了锁,并开始执行业务逻辑。
  2. 客户端B也获取到了锁,由于客户端A尚未释放锁,所以客户端B也开始执行业务逻辑。
  3. 客户端A完成业务逻辑后,释放了锁。
  4. 客户端B完成业务逻辑后,再次释放了锁。

在这种情况下,客户端A和客户端B都成功地获取到了锁,并且都执行了业务逻辑,导致资源被重复使用或超出预期的数量。

为了解决这个问题,可以引入一个唯一的标识符(例如请求的唯一ID)来标记锁的持有者,并在释放锁时进行校验。具体步骤如下:

  1. 客户端请求到达后,生成一个唯一的标识符作为锁的持有者标记。
  2. 使用Redis的SETNX命令尝试获取锁,将标识符作为锁的值存储到Redis中。
  3. 如果获取到了锁,执行业务逻辑。
  4. 业务逻辑执行完毕后,使用Lua脚本(原子操作)检查锁的持有者是否与当前客户端标识符匹配,如果匹配则释放锁,否则不进行释放。
  5. 客户端请求完成。

通过引入标识符并在释放锁时进行校验,可以确保只有持有锁的客户端才能释放锁,从而避免了超卖问题的发生。

需要注意的是,分布式锁的实现并非一成不变的,具体的实现方式可能会因系统架构、业务场景等因素而异。在实际应用中,需要综合考虑并发情况、锁的粒度、锁的持有时间等因素,选择合适的分布式锁实现方式来解决超卖问题。