背景:秒杀服务中要写一个定时任务:活动到期时给order微服务发送关闭订单的通知。这需要改变数据库表中的数据,而集群中服务是多节点的方式进行部署,会出现并发执行的情况,所以采用的redis的分布式锁的实现方式。
Redis 锁(setNx)
特点: 如果没有获取到锁,请求会被丢弃。 只适合 消息队列 和定时任务场景
点击查看代码
public function sendEndNotice()
{
$lock = new QueueLockLogic(LimitCFG::CLOSE_LOCK_KEY);
$lock_res = $lock->lock(LimitCFG::CLOSE_LOCK_KEY);
if (!$lock_res) return false;
//...
$res = $lock->unlock(LimitCFG::CLOSE_LOCK_KEY);
return $res;
}
//再附上一个应用于砍价服务中消息队列的
加锁:
$uniqueKey = "bargain:cmq:upOrderStatus:$storeId_$branchId_$orderId_$status";
$lock = new QueueLockLogic($uniqueKey);
$lockRes = $lock->lock($uniqueKey);
if (!$lockRes){
throw new ErrException(ExceCode::E13161);
}
.....
解锁:
$res = $lock->unlock($uniqueKey);
===================方法原型============================
namespace App\Models\Logic;
use Swoft\Redis\Redis;
use Swoft\App;
/**
* Class QueueLockLogic
* @package App\Models\Logic
*/
class QueueLockLogic
{
private $redis;
private $prefix;
/**
*
* @param
*/
public function __construct(string $redisPrefix)
{
$this->redis = App::getBean(Redis::class);
$this->prefix = $redisPrefix;
}
/**
* 获取redis键
*
* @param string $unique
* @return string
*/
public function getKey(string $unique): string
{
return $this->prefix . $unique;
}
/**
* 获取锁
* 返回:
* true: 说明获得锁,
* false: 说明获得锁失败
*
* @param string $key
* @param integer $expireTime
* @return boolean
*/
public function lock(string $key, int $ttl = 60): bool
{
//加锁
$res = $this->redis->setNx($key, time() + $ttl);
if ($res === 0) { //加锁失败
//检测锁是否过期
$expireTime = $this->redis->get($key);
if ($expireTime < time()) { //锁已过期,获取锁
$oldExpireTime = $this->redis->getSet($key, time() + $ttl); //在查询期间可能有别的进程加锁,也许已经加锁成功。
if ($oldExpireTime < time()) {//加锁成功
return true;
}//即便加锁失败,重置了key的值也相差无几可忽略
}
return false;
}
return true;
}
/**
* 解锁
* @param string $key
* @return bool
*/
public function unlock(string $key): bool
{
return $this->redis->delete($key) > 0;
}
}