一文搞懂Redis的锁

本文详细介绍了Redis的锁机制,包括incr的线程安全性,利用链表避免锁,并发锁的概念和要求,重点讲解了分布式锁的实现,并探讨了使用Lua脚本保证操作原子性以及在Redis中的安全执行,强调了Lua脚本在解决主从节点一致性问题上的作用。
摘要由CSDN通过智能技术生成

锁!

# 建立锁
$lock = $redis_obj->setnx('lock1',1);

if($lock){
	//todo ... 
}

#释放锁
$redis_obj->del('lock1');

incr 线程安全

这个方法会在不存在key的时候,先初始化0,然后加1,返回1;如果key存在,则在执行就会大于1;


$lock = $redisObj->incr(‘lock’);
if($lock === 0 ){
     // todo ...      
 }

用链表, 完全不需要锁!

LPUSH : 将值插入到列表的头部
LPOP : 移除并返回列表 key 的头元素
LLEN : 返回列表 key 的长度
LRANGE:返回列表 key 中指定区间内的元素
EXPIRE:为key设置生存时间
EXPIREAT:为key设置到期时间

//将商品放入链表
$redis_obj->del('goods_store');
for ($i=0; $i < $res[2]['num_left']; $i++) {
	$redis_obj->lpush('goods_store', 1);
}

 //移除链表的头元素 减一次库存
$res = $redisObj->lPop('goods_store');

if($res){
	//执行入库操作  写订单数据
	$redisObj->incr($key,$num);
	$redisObj->set('u_trade_' . $uid . '_' . $active_id, 1, 86400);
	$redisObj->set('order_' . $uid . '_' . $active_id . '_' . $goods_id, 1, 86400);
}else{
 return false;
}
  

并发锁

一,互斥:既然是锁,不能每个客户端都有吧,那还锁啥?
二,不能死锁:让一个客户端抱住锁,永远不释放,别的就获取不到了,那还要分布式系统干啥?
三,每个客户端的锁自己加,自己解;

分布式锁

分布式锁和本地锁的区别是什么?
就像上面说的,单机,并发的单位是线程,分布式,并发的单位是多进程。
并发单位的等级上去了,锁的等级自然也得上去。

以前锁是进程自己的,进程下的线程都看这个锁的眼色行事,谁拿到锁,谁才可以放行。

进程外面还有别的进程,你要跟别人合作,就不能光看着自己了,得有一个大家都看得到的,光明正大的地方,来放这把锁。

有不少适合放这把锁的地方,redis、zookeeper、etcd等等,今天我们先聊聊如何用redis实现分布式锁

lua 脚本

要注意的:
LUA 里面的变量要先赋值,不然可能会出错,
local num = 0
num = redis.call(“GET”, “info_g_”…goods_id)

有些函数有返回值,但是不能获取, 如下面语句 ,如果写
res = redis.call(“DECR”,‘info_g_1’) 就会报错

 if(redis.call("DECR",'info_g_1')) 
127.0.0.1:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
"a42059b356c875f0717db19a51f6aaca9ae659ea"

127.0.0.1:6379>  EVALSHA "a42059b356c875f0717db19a51f6aaca9ae659ea" 2 leon leon2 leon3 leon4
1) "leon"
2) "leon2"
3) "leon3"
4) "leon4"

127.0.0.1:6379> script flush
OK

#秒杀逻辑
        $script = <<<LUA
            local uid = KEYS[1];
            local goods_id = KEYS[2];
            local active_id = KEYS[2];
            local num = 0
            num = redis.call("GET", "info_g_"..goods_id)
            
            if(tonumber(num)<=0)
            then
              return 1     
            end
            
            if(redis.call("DECR",'info_g_1')) 
            then 
                redis.call("SET",'u_trade_'..uid..active_id,1)
                redis.call("SET", 'order_'..uid..'_'..active_id..'_'..goods_id,1)
                return 2
            end
            return 3
LUA;

使用redis执行lua脚本示例 multi与pipeline

脚本的安全性
如生成随机数这一命令,如果在master上执行完后,再在slave上执行会不一样,这就破坏了主从节点的一致性

为了解决这个问题, Redis 对 Lua 环境所能执行的脚本做了一个严格的限制 —— 所有脚本都必须是无副作用的纯函数(pure function)。所有刚才说的那种情况压根不存在。Redis 对 Lua 环境做了一些列相应的措施:

不提供访问系统状态状态的库(比如系统时间库)
禁止使用 loadfile 函数
如果脚本在执行带有随机性质的命令(比如 RANDOMKEY ),或者带有副作用的命令(比如 TIME )之后,试图执行一个写入命令(比如 SET ),那么 Redis 将阻止这个脚本继续运行,并返回一个错误。
如果脚本执行了带有随机性质的读命令(比如 SMEMBERS ),那么在脚本的输出返回给 Redis 之前,会先被执行一个自动的字典序排序,从而确保输出结果是有序的。
用 Redis 自己定义的随机生成函数,替换 Lua 环境中 math 表原有的 math.random 函数和 math.randomseed 函数,新的函数具有这样的性质:每次执行 Lua 脚本时,除非显式地调用 math.randomseed ,否则 math.random 生成的伪随机数序列总是相同的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值