发布于 2026-01-05 0 阅读
0

如何使用 Redis 实现分布式锁?DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

如何使用 Redis 实现分布式锁

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

我是个笨蛋

嗯,只要我们在本地系统里工作,一切都顺畅无比。所以我们才说“没有比127.0.0.1更好的地方了”,但醒醒吧,面对现实吧!

宇智波斑

然而,在生产环境中,事情并非总能如预期般顺利进行,尤其是在运行应用程序的多个实例时。

微服务

🚀 正如你所看到的,如果我们的应用程序有多个实例正在运行,假设我们的客户发出请求,将数据库中的某个用户标记为付费用户。

  • 客户端将向我们的服务器发出请求
  • 请求将到达我们的负载均衡器。
  • 其中一个实例会收到请求,并向我们的数据库执行写入查询。

看起来好像没问题吧?目前为止都没出过问题。

嗯,目前为止没有问题。但如果我们想编写类似这样的业务逻辑呢?

  • 从数据库中获取用户
  • 检查用户是免费用户还是已付费用户。
  • 如果免费,则将其标记为付费并保存到数据库中。
  • 如果已付款,请在回复中发送“已付款”。

⚡️ 我们知道(假设这里使用的是 MySQL),MySQL 数据库符合 ACID 原则,这意味着任何查询都是原子性的且相互隔离的。也就是说,MySQL 查询会以原子方式执行,要么成功,要么失败,不会中途终止。

🤔 但这里有个问题。想想,想想……

  • 步骤 1:我们正在获取用户(原子事务)
  • 步骤二:在代码中运行一些业务逻辑
  • 步骤 3:如果用户未付款,则更新 MySQL 记录(原子事务)

如果在步骤 2 中,又有一个取消付款的请求,那么该查询会先运行并将用户标记为免费,然后步骤 3 运行并将用户标记为已付费,会发生什么情况?

🕺🏻 太棒了!用户无需付费即可使用我们的产品。

锁定

✅ 救世主来了,洛克斯
操作系统锁定梗

🔐 锁是一种结构,它一次只允许一个线程进入临界区(不应该被多个工作线程访问的代码块)。

因此,我们将在操作完成前获取锁,并在操作完成后释放锁:

  • 步骤 0:尝试获取锁
  • 步骤 1:如果已获取,我们将获取用户(原子事务)
  • 步骤二:在代码中运行一些业务逻辑
  • 步骤 3:如果用户未付款,则更新 MySQL 记录(原子事务)
  • 步骤 4:释放锁

😅 问题

现在问题来了,如果我们使用内存锁数据结构或任何基于内存的锁,它只能用于我们应用程序的一个实例。那么运行相同代码并更新数据库的其他实例怎么办?

这就引出了分布式锁的概念。

🔓 分布式锁定

分布式锁定

在这里,锁充当集中式服务,如果我们的服务的一个实例获得了锁,那么其他实例就不能使用同一个密钥获得锁。

支付服务中可能存在什么关键因素?

🔓 对于进行支付的用户,支付密钥可能是“PAYMENT_” + user_id + amount 的组合。

每个用户都将拥有唯一的密钥。无论用户进行支付还是取消支付,此密钥都保持不变。因此,当用户进行支付或取消支付时,其他操作将无法进行,因为这两个操作都会尝试使用同一个密钥。

💭 Key、Acquire lock、release lock 到底是什么?最重要的是,Redis 是如何被使用的?


🎈 使用 Redis 实现分布式锁

使用单个 Redis 实例:

Redis 单实例锁定

但使用单个 Redis 实例存在以下几个问题:

  • 单个实例可能发生故障,且获取的锁可能不会被释放。
  • 如果使用两个实例(主从模式),则一个客户端将获取其中一个实例的锁。
  • 主节点必须与副本节点进行通信才能实现同步。这种通信本身就是一种异步通信。

🚀 因此,如果主节点获取了锁,但在与副本节点通信期间,如果主节点在与副本节点同步之前宕机,则副本节点将成为新的主节点,并可以获取之前在主节点上获取的同一键的锁。

即使有两个实例(主副本),我们的服务的两个实例也能够获取 Redis 上的锁。

使用红锁算法:

获取锁:我们将尝试在多个 Redis 实例上获取锁,并设置锁的过期时间。
锁验证:如果主要 Redis 实例都为客户端获取了锁,则认为该锁已成功获取。
释放锁:释放锁时,所有实例都会释放该锁。

红锁算法

是的,就是这样。

❤️感谢阅读,欢迎订阅我们的新闻简报,获取更多类似文章:https ://www.serversidedigest.com/

更多信息:-

文章来源:https://dev.to/singhdevhub/how-to-implement-a-distributed-lock-using-redis-he