PHP使用redis实现分布式锁的示例详解
作者:程序员零壹 时间:2023-06-01 16:32:19
最近在做一个领券功能的时候,发现在一定并发下会出现重复领券的问题。使用度娘一顿搜索操作之后,发现可以使用分布式锁来解决这个问题。
什么是分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。
实现原理
实现分布式锁的原理很简单,就是需要有一把锁,多个服务同时去获取锁,但是只有一个服务能获取到锁。获取到锁的服务就可以执行自己的业务,没有获取到锁的其他服务需要等待获取到锁的服务业务执行完成后释放锁,然后再次尝试获取锁。
实现分布式的方案有很多种。如下
基于数据库实现分布式锁,比如mysql
基于缓存实现分布式锁,比如redis
基于Zookeeper实现分布式锁
这里我们使用redis来实现分布式锁,在执行业务之前先获取一个key,如果key存在就说明已经有其他服务获得锁,这个时候需要等待或者返回系统繁忙。如果key不存在,说明没有其他服务获取锁,把这个key保存到redis,然后执行业务,等待业务执行完就从redis中删除这个key。
php实现代码
<?php
class RedisLock
{
protected $redis;
public function __construct(){
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$this->redis = $redis;
}
public function getLock($key){
$value = $this->redis->get($key);
return $value;
}
public function setLock($key,$value){
$this->redis->set($key,$value);
}
public function delLock($key){
$lineNumber = $thid->redis->del($key);
return $lineNumber;
}
}
$key = 'your_lock_key';
$value = time();
$redisLock = new RedisLock();
$isLock = $redisLock->get($key);
if($isLock) {
//已有锁,直接返回,不往下执行了
return false;
}
//没有锁,加锁
$redisLock->setLock($key,$value);
//todo 执行业务逻辑
sleep(5);
// 解锁
$redisLock->delLock($key);
使用ab进行测试
加锁
加锁
加锁
加锁
加锁
加锁
加锁
加锁
执行业务
解锁
解锁
执行业务
解锁
执行业务
解锁
执行业务
解锁
解锁
执行业务
解锁
加锁
执行业务
解锁
加锁
执行业务
解锁
从测试结果来看,发现有多个执行业务,并没有完全锁住。这个是因为我们用的是redis的set命令。set 命令用于设置给定 key 的值。如果 key 已经存储其他值, SET 就覆写旧值,且无视类型。这样会导致很多服务都能加锁成功,而我们想要的是只有一个服务能加锁成功。
要解决这个问题,需要了解redis的另一个命令setnx。setnx 命令在指定的 key 不存在时,为 key 设置指定的值。
<?php
class RedisLock
{
protected $redis;
public function __construct(){
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$this->redis = $redis;
}
public function getLock($key){
$value = $this->redis->get($key);
return $value;
}
public function setLock($key,$value){
return $this->redis->setnx($key,$value);
}
public function delLock($key){
$lineNumber = $thid->redis->del($key);
return $lineNumber;
}
}
$key = 'your_lock_key';
$value = time();
$redisLock = new RedisLock();
$isLock = $redisLock->get($key);
if($isLock) {
//已有锁,直接返回,不往下执行了
return false;
}
//没有锁,加锁
$setLock = $redisLock->setLock($key,$value);
if(!$setLock) {
//加锁失败
return false;
}
//todo 执行业务逻辑
sleep(5);
// 解锁
$redisLock->delLock($key);
再次使用ab进行测试
加锁
加锁
加锁
加锁
加锁
加锁
加锁
加锁失败
加锁失败
加锁失败
加锁失败
加锁失败
已锁
已锁
已锁
执行业务
解锁
从测试结果来看,在未加锁的状态下,有多个服务同时获取加锁,但是只有一个加锁成功, 其他的都是返回加锁失败,再后面的服务更是直接返回已锁。由此可见,加锁成功。
那么到此就结束了吗?其实并不是的。假如在已加锁的情况执行业务,在业务过程中因为一些原因出现异常导致退出而没有进行解锁,那么将造成死锁,后面的所有服务都无法再次获取锁。为了解决这个问题,我们需要对锁设置一个过期的时间,防止死锁的发生。
<?php
class RedisLock
{
protected $redis;
public function __construct(){
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$this->redis = $redis;
}
public function getLock($key){
$value = $this->redis->get($key);
return $value;
}
public function setLock($key,$value,$second){
$setnx = $this->redis->setnx($key,$value);
if(!$setnx) {
return $setnx;
}
$expire = $this->redis->expire($key,$second);
if(!$expire) {
$this->redis->del($key);
}
return $expire;
}
public function delLock($key){
$lineNumber = $thid->redis->del($key);
return $lineNumber;
}
}
$key = 'your_lock_key';
$value = time();
$redisLock = new RedisLock();
$isLock = $redisLock->get($key);
if($isLock) {
//已有锁,直接返回,不往下执行了
return false;
}
//没有锁,加锁
$second = 5;
$setLock = $redisLock->setLock($key,$value,$second);
if(!$setLock) {
//加锁失败
return false;
}
//todo 执行业务逻辑
sleep(5);
// 解锁
$redisLock->delLock($key);
来源:https://blog.csdn.net/u010594957/article/details/125189169