分布式锁
1 什么是分布式锁?
在讨论分布式锁之前, 我们先假设一个业务场景:
1.1 业务场景
在电商系统中, 用户购买商品需要扣减库存, 一般扣库存有两种方式:
下单减库存
优点: 用户体验好, 下单成功, 库存直接扣减, 用户支付不会出现库存不足.
缺点: 用户一直不付款, 这个商品的库存就会被占用, 其他人无法购买.
支付减库存
优点: 不会导致库存被恶意锁定, 对商家有利.
缺点: 用户体验不好, 用户支付时可能商品库存不足了, 会导致交易失败.
那么, 我们一般为了用户体验, 会采用下单减库存, 为了解决下单减库存的缺陷, 会创建一个定时任务, 定时去清理超时未支付的订单.
这个定时任务主要包含以下步骤:
查询超时未支付的订单, 获取订单中的商品信息.
修改未支付订单的状态, 改为取消.
恢复订单中商品扣减的库存.
如果我们给订单服务搭建一个 100 个节点的超时订单检查服务集群, 那么就会同时有 100 个定时任务触发并执行, 设想一下这样的场景:
订单服务 A 和 B 同时执行了步骤 1.
它们返回了同样的商品和订单信息.
订单服务 A 执行了步骤 2 和 3.
订单服务 B 执行了步骤 2 和 3. 商品库存再次被增加.
因为任务的并发执行, 出现了线程安全问题, 商品库存被增加多次.
为什么需要分布式锁
对于线程安全问题, 传统的方法是给对线程操作的资源代码加锁.
理想状态下, 加了锁以后, 在当前订单服务执行时, 其他订单需要等待当前服务完成业务后才能执行, 这样就避免了线程安全的问题. 实际上这样并不能解决问题.
1.2.1 线程锁
我们通常使用的 synchronized 和 Lock 都是线程锁, 对同一个 JVM 进程内的多个线程有效. 因为锁的本质是在内存中存放一个标记, 记录获取锁的线程是谁, 这个标记对每个线程都可见.
因此, 锁生效的前提是:
互斥: 锁的标记只有一个线程可以获取.
共享: 标记对所有线程可见.
然而我们启动了多个订单服务, 就是多个 JVM, 内存中的锁显然是不共享的. 为了解决这个问题, 能够保证各个订单服务能够共享内存的锁, 分布式锁就派上用场了.
- 127.0.0.1:6379> keys *
- (empty list or set)
- 127.0.0.1:6379> SETNX lock 001
- (integer) 1
- 127.0.0.1:6379> get lock
- "001"
- 127.0.0.1:6379> SETNX lock 002
- (integer) 0
- 127.0.0.1:6379> get lock
- "001"
- 127.0.0.1:6379> set lock 001 NX EX 30
- OK
- 127.0.0.1:6379> set lock 002 NX EX 30
- nil (第二次执行失败)
- 127.0.0.1:6379> ttl lock
- (integer) 12
- 127.0.0.1:6379> get lock
- "001"
- 127.0.0.1:6379>
- HSET key threadId 1
- .
- HEXISTS lock threadId
- .
- EVAL script numkeys key [key ...] arg [arg ...]
- summary: Execute a lua script server side
- since: 2.6.0
- SCRIPT LOAD script
- summary: Load the specified lua script into the script cache.
- since: 2.6.0
- 127.0.0.1:6379>
- 127.0.0.1:6379> SCRIPT LOAD "return'hello world!'""absd9sd9fsdjdkfjs9ds0d0r1klj1209i"
- 127.0.0.1:6379>
- EVALSHA sha1 numkeys key[key ...] arg[arg ...]
- summary: Execute a lua script server side
- since: 2.6.0
- if()
- then
- ....
- else if()
- then
- ....
- else
- .....
- end
- while(ture)
- do
- print('')
- end
- return Redis.call('DEL',KEYS[1])
- end
- if(count> 0) then
- Redis.call('EXPIRE',key,releaseTime);
- return nil;
- else
- return nil;
- end;
来源: https://www.cnblogs.com/paulwang92115/p/12163425.html