锁!
# 建立锁
$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 生成的伪随机数序列总是相同的。