需要写一个抽奖活动,并发量很大,抽奖的同时需要操作多个数据表,决定采用redis锁.
网上找了一下,找到大牛的博客
Q:很好奇解锁的函数里为什么要用redis执行lua脚本,为什么不用php直接来操作呢?
A:群里问了下,大概明白了,redis是原子性,操作都是串行,但php是并发操作的,所以在高并发的时候可能存在脏读的问题,所以使用eval函数在redis里串行的执行这段代码,因为是串行的,不存在并发问题.
Q:如何实现阻塞锁
A:我没有在网上找到好的解决方案,都是自己写循环执行
SET key value [EX seconds] [PX milliseconds] [NX|XX]
将字符串值 value 关联到 key 。
如果 key 已经持有其他值, 就覆写旧值,无视类型。
对于某个原本带有生存时间(TTL)的键来说, 当 命令成功在这个键上执行时, 这个键原有的 TTL 将被清除。
可选参数
从 Redis 2.6.12 版本开始, 命令的行为可以通过一系列参数来修改:
- EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
- PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX keymillisecond value 。
- NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
- XX :只在键已经存在时,才对键进行设置操作。
因为 命令可以通过参数来实现和 、 和 三个命令的效果,所以将来的 Redis 版本可能会废弃并最终移除 、 和 这三个命令。
1 key = $key;23 $this->timeout = $timeout;24 $this->token = TextHelper::generateOrderNo();25 }26 27 /**28 * 阻塞加锁29 * @return bool30 */31 public function lock()32 {33 $timeStart = microtime(true) * 1000;34 while (true) {35 $res = \Yii::$app->redis->set($this->key, $this->token, "nx", "ex", $this->timeout);36 if ($res) {37 break;38 }39 $timeEnd = microtime(true) * 1000;40 if ($timeEnd - $timeStart > 10000) {41 //add log error42 return false;43 }44 }45 return true;46 }47 48 /**49 * 解锁50 * @return mixed51 */52 public function unlock()53 {54 $script = '55 if redis.call("get",KEYS[1]) == ARGV[1]56 then57 return redis.call("del",KEYS[1])58 else59 return 060 end';61 return \Yii::$app->redis->eval($script, $this->key, $this->token);62 }63 }
随便写的,没经过测试,大牛不要笑我...