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


猜你喜欢
- python默认使用的是国外镜像,有时候下载非常慢,最快的办法就是在下载命令中增加国内源:常用的国内源如下:清华大学:https://pyp
- 本文实例讲述了python实现string和dict的相互转换方法。分享给大家供大家参考,具体如下:字典(dict)转为字符串(string
- 最近JETBRAINS发布了目前最受欢迎的python-web开发框架,可以看到最受欢迎的还是Django和Flask,那么本文就对上榜的1
- 编写tasks.pyfrom celery import Celeryfrom tornado.httpclient import HTTP
- 实际上,在web开发中,cookie仅仅是一个文本文件,当用户访问站点时,它就被存储在用户使用的计算机上,其中,保存了一些信息,当用户日后再
- 最近由于公司有一个向谷歌网站上传文件的需求,需要进行web的自动化测试,选择了selenium这个自动化测试框架,以前没有接触过这门技术,所
- 问题:开发中常使用Navicat查询数据库,并修改数据库中的值。今天发现查询结果为只读,不能修改。一般连表查不能修改我是知道的,但是单表查居
- 前言在开始本文之前,先来介绍一下相关内容,大家都知道一些防护SSRF漏洞的代码一般使用正则来判断访问IP是否为内部IP,比如下面这段网上比较
- 1.查询高于平均价格的商品名称: SELECT item_name FROM ebsp.product_market_price WHERE
- 这篇文章主要介绍了基于Python获取照片的GPS位置信息,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 1、准备数据以下操作将在该表中进行create table student ( id int unsigned primary key au
- 一、介绍如果在Python中需要对用户输入的密码或者其他内容进行加密,首选的方法是生成hash值。在Python中可以利用二个模块来进行:&
- 1.之前的写法(不报错):data = cursor.fetchall()data_name = data[0]['task_typ
- 最近做的一个B/S项目,在打印时采用了在IE中嵌入.net winform控件和XML结合的方式(参见http://www.yesky.co
- 1 为什么找不见外星人为什么我们见不到外星人? 曾经在物理学上有一个著名人物叫费米,大家知道费米是在物理学上发现中子轰击的人,有一个著名的费
- 前言Vue.js 是开源的一个前端开发库,通过简洁的 API 提供高效的数据绑定和灵活的组件系统。在前端纷繁复杂的生态中,Vue.js在近年
- 本文实例讲述了Python HTML解析模块HTMLParser用法。分享给大家供大家参考,具体如下:简介先简略介绍一下。实际上,HTMLP
- 本文实例讲述了Python使用try except处理程序异常的三种常用方法。分享给大家供大家参考,具体如下:如果你在写python程序时遇
- 本文实例为大家分享了JavaScript实现涂鸦笔的具体代码,供大家参考,具体内容如下1、html部分、css部分1.1 实现一个画框<
- 在JavaScript中单选框的用法和复选框相似。不同之处在于HTML中的应用。复选框是一种开关。如果