方案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脚本
判断锁释放存在 判断是否是自己的锁 从入锁并重置过期时间