如何使用 Redis 实现分布式锁
我很笨
好吧,只要我们在本地系统上工作,一切都会顺利进行。这就是为什么我们说“没有比 127.0.0.1 更好的地方了”,但现实是这样的
在生产环境中,事情并不总是按预期进行。尤其是在运行应用程序的多个实例时。
🚀 正如您所看到的,如果我们的应用程序的多个实例正在运行,并且假设我们的客户端发出请求,将用户标记为我们数据库中的付费用户。
- 客户端将请求我们的服务器
- 请求将到达我们的负载均衡器
- 其中一个实例将收到请求并向我们的数据库发出写入查询
看起来还好吧?到现在为止都没问题吧。
嗯,到目前为止没有问题。但是如果我们想写如下业务逻辑呢:
- 从数据库中获取用户
- 检查用户是否是免费用户或已付费用户
- 如果免费,则标记为付费并保存在数据库中
- 如果已付款,请在回复中发送“已付款”。
⚡️ 我们知道(假设我们在这里使用 MySQL),MySQL 数据库符合 ACID 标准,这意味着任何查询都是原子的和隔离的。这意味着 MySQL 查询将以原子方式运行,要么通过,要么失败。但它不会在中间退出。
🤔但这里有一个问题。想想,想想……
- 步骤 1:我们正在获取用户(原子事务)
- 步骤 2:在代码中运行一些业务逻辑
- 步骤 3:如果用户未付款,则更新 MySQL 记录(原子事务)
如果在第 2 步中,又有一个取消付款的请求,然后该查询首先运行并将用户标记为免费,然后第 3 步运行并将用户标记为已付款,会发生什么情况。
🕺🏻 太好了,用户无需付费即可使用我们的产品。
锁定
🔐 Lock 是一种结构,它一次只允许一个线程进入关键部分(不应被多个工作线程访问的代码块,即线程)
因此,我们将在操作之前获取锁并在操作完成后释放:-
- 步骤 0:try-acquire() 锁
- 步骤 1:如果获取到,我们将获取用户(原子事务)
- 步骤 2:在代码中运行一些业务逻辑
- 步骤 3:如果用户未付款,则更新 MySQL 记录(原子事务)
- 步骤 4:释放()锁
😅 问题
现在,问题来了,如果我们使用一些内存锁数据结构或任何基于内存的锁,它将有资格成为我们应用程序的一个实例。那么运行相同代码并在数据库中更新的其他实例呢?
分布式锁定的概念就在这里
🔓 分布式锁定
这里锁充当集中服务,如果我们服务的一个实例获取了锁,那么其他实例就不能使用同一个密钥。
支付服务中的关键是什么?
🔓 对于用户来说,付款密钥可以是 =“PAYMENT_”+user_id+amount 的组合
并且每个用户都是唯一的。无论用户付款还是取消付款,此密钥都将保持不变。因此,当其中一个操作发生时,其他操作将无法进行,因为两个操作都会尝试获取相同的密钥。
💭 到底什么是 Key、获取锁、释放锁?最重要的是,Redis 是如何使用的?
🎈 使用 Redis 实现分布式锁定
使用 Redis 的单个实例:-
但是单个 Redis 实例存在以下几个问题:
- 单个实例可能会失败并且获取的锁可能不会被释放
- 如果使用两个实例(主副本),则一个客户端将获取一个实例上的锁定
- 主服务器必须与副本服务器进行相同的通信才能同步。此通信本身是异步通信
🚀 因此,如果在主服务器上获取了锁,并且在与副本服务器通信时,主服务器在与副本服务器同步之前发生故障,则副本服务器将成为主服务器,并且可获取先前在主服务器上获取的相同键的锁。
即使有两个实例(主副本),我们的服务的两个实例也将能够获取 redis 上的锁。
使用Redlock算法:-
获取锁:- 我们将尝试在具有锁过期时间的多个 redis 实例上获取锁。
锁的验证:- 如果主要 redis 实例已为客户端获取锁,则将被视为已获取锁。
释放锁:- 释放锁时,所有实例都会释放锁。
是的,就是这样。
❤️感谢您的阅读,请订阅我们的时事通讯以获取更多此类文章:- https://www.serversidedigest.com/
更多信息:-
- Java 中的 Jedis:-https: //redis.io/docs/latest/develop/connect/clients/java/jedis/
- Golang 中的 Redis 客户端:https: //github.com/redis/go-redis