Redis锁的基本应用

处理高并发问题时,咱们常常用 Redis 进行加锁操做,目的是为了解决并发可能带来的问题。作一个简单的总结redis

常见的方案之一:setnx,其余线程必须拿到这个值,才能继续往下执行,不然等待。该命令是原子操做,因此能够防止并发状况的发生。session

while(!$redis->setnx('lock', '1')) {    // 设置锁
    usleep(100000);
}

// 执行业务代码

$redis->del('lock');    // 释放锁

可是该方案有个弊端,若是设置锁后进程崩溃,那么该锁永远不会释放。通常解决方法是在 setnx 的时候设置过时时间,则能够解决线程奔溃锁没法释放的问题。但若是设置锁和设置锁的过时时间不是原子操做,仍然不能防止并发的状况发生。好在 Redis 中的 set 命令提供了这些参数使用,NX 就和 setnx 同样, EX 能够在 set 的时候加上过时时间。并发

$expire = 5;    // 锁的过时时间
while(!$redis->set('lock', '1', ['NX', 'EX'=>$expire])) {
    usleep(100000);
}

// 执行业务代码

$redis->del('lock');

可是在这种状况下,当一个进程执行出现问题或执行时间超过 setnx 设置的过时时间,那么这个锁就自动消失了,仍会有其余进程并发执行业务代码,且不一样进程的锁相互覆盖。因此这个方案也不能有效防止并发。dom

解决方法: watch,在 set 的时候设置 NX 与 EX,而且设置值为随机数(惟一),当 A 进程设置锁后,后续进程都没法设置锁。A 进程业务逻辑完成后,对比随机数是否一致,若是一致再删除,若是在删除过程当中,发现 key 的值被修改,则删除失败。防止 A 进程超时后,锁被后续进程获取,这个时候若是 A 进程删除锁,就会把后面的锁给删了。高并发

$expire = 5;
$random = session_create_id();  
while(!$redis->set('lock', $random, ['NX', 'EX'=>$expire])) {
    usleep(100000);
}
$redis->watch("lock");  // 监听 lock 的值

// 执行业务代码

$ret = $redis->multi(Redis::MULTI)
    ->del("lock")
    ->exec();   // multi 的做用在于若被 watch 的键被修改、删除、覆盖,那么 exec 就会执行失败
if($ret) {
    $redis->del("lock"); // 释放锁
} else {
    // 表明 lock 的值已被其余进程修改
}
相关文章
相关标签/搜索