分布式锁

方案1:

本地使用锁,单机锁

问题:不支持集群


方案2:

一个SQL, 表锁(product_code 没有索引)(其他用户不能更新和插入)

条件字段需要加索引,查询或者更新条件是具体值(不能是like、!=、>或者<,可以是=、in)

刚给product_code 加个索引

问题1:锁表问题

问题2:同一个商品有多条库存记录

问题3:无法记录库存变化前后的状态


方案3:

MySQL悲观锁中使用行级锁select … for update

问题1:有性能问题

问题2:有死锁问题

问题3:操作步骤需要统一


方案4:

乐观锁,需要时间戳、版本号或者cas(compare and swap),比较并交换

比如修改密码,旧密码需要匹配才行

问题1:高并发性能极低,重试浪费大量资源

问题2:ABA问题,有人偷偷往回改版本号

问题3:读写分离下导‘; 致乐观锁不可靠(从库延时导致)


Redis乐观锁:

watch命令监听值

multi exec 等实现事务

root@0fdf2d78af27:/data# redis-cli 
127.0.0.1:6379> WATCH ff
OK
127.0.0.1:6379> set ff 1
OK
127.0.0.1:6379> 
127.0.0.1:6379> get ff
"1"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set ff 11
QUEUED
127.0.0.1:6379(TX)> EXEC
(nil)
127.0.0.1:6379> get ff
"1"
127.0.0.1:6379>
root@0fdf2d78af27:/data# redis-cli 
127.0.0.1:6379> set ff 11
OK
127.0.0.1:6379> WATCH ff
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set ff 88
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
127.0.0.1:6379> get ff
"88"
127.0.0.1:6379>

Redis分布式锁:

特点:跨进程 跨服务 跨服务器

场景:超卖现象 缓存击穿

缓存击穿:一个热点key 不存在 解决办法:不存在的key也缓存

分布式锁实现方式:

1.基于redis实现

2.基于zookeeper/etcd实现

3.基于MySQL实现

特征:

1.独占排他使用

2.防止死锁的发生

3.原子性,获取锁和设置过期时间要保证原子性

4.防止误删除:解铃还须系铃人

先判断释放是自己的锁

5.自动续期

6.可重入性

redis: 使用setnx加锁,只有一个请求能设置成功、使用del释放锁

setnx 返回1成功,返回0失败

操作:

1.枷锁 setnx

2.解锁 del

3. 重试:递归 循环,不要使用递归!

解决死锁问题:

给锁添加过期实现 set lock 11 ex 20 nx

lua脚本:通过lua脚本保证删除的原子性,防止误删除

可以一次性发送多个指令给redis,redis是单线程,执行指令遵守one-by-one规则

127.0.0.1:6379> set lock 111
OK
127.0.0.1:6379> eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock 11
(integer) 0
127.0.0.1:6379> eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock 111
(integer) 1
127.0.0.1:6379>

if redis.call(‘get’,KEYS[1]) == ARGV[1]
then
return redis.call(‘del’, KEYS[1])
else
return 0
end

使用redis实现分布式可重入锁

思路:hash + lua脚本
判断锁释放存在 判断是否是自己的锁 从入锁并重置过期时间