php、redis实现分布式锁的正确写法(原子操作 通用类 加讲解)
最终代码(通用类)
1 面试中、实际工作中,经常涉及到 redis 分布式锁,正确写法如下。先奉上代码,再讲解。
<?php
namespace app\common\library;
/*** 通用分布式锁(原子操作)*/
class Lock
{/*** 获取redis实例* @return \Redis* @throws \RedisException*/public static function redis(){$redis = new \Redis();$redis->connect('192.168.4.147',6179);return $redis;}/*** 加锁(原子操作)* @param string $key 要加锁的key* @param string $value 必须是唯一值* @param int $expires 锁的过期时间(秒)* @return bool* @throws \RedisException*/public static function lock(string $key,string $value,int $expires): bool{# redis实例$redis = self::redis();# 原子操作 -- 设置锁且设置过期时间return $redis->set($key,$value,['nx','ex'=>$expires]);}/*** 解锁(原子操作)* @param string $key 要解锁的key* @param string $value 加锁时的值* @return mixed|\Redis* @throws \RedisException*/public static function unlock(string $key,string $value){# redis实例$redis = self::redis();# lua 脚本$lua = "if redis.call('GET',KEYS[1]) == ARGV[1] thenreturn redis.call('DEL',KEYS[1])elsereturn 0end";return $redis->eval($lua,[$key,$value],1);}/*** 生成唯一值* @return string*/public static function generateValue(): string{return microtime(true).mt_rand(10000,99999);}
}
2 使用方式
use app\common\library\Lock;
# 抢到锁
if(Lock::lock($key,$value,300)){try{# 业务逻辑}catch (\Exception $e){}finally {# 释放锁Lock::unlock($key,$value);}
}
讲解
使用场景
抢红包、秒杀下单、扣库存、…
目的
在并发情况下,避免业务逻辑的重复执行,导致性能低下,或数据不一致。重复执行没意义的工作,浪费性能,比如数据清理、数据归档、检测日志等 。重复执行有意义的工作,导致数据出错,比如重复多扣了库存。
一些常见的写法
1 setnx + expire
说到 redis 的分布式锁,很多同学马上会想到 setnx + expire,先用 setnx 抢锁,如果抢到之后,再用expire 给锁设置一个过期时间防止忘记释放。
setnx 是 set if not exists 的缩写,表示如果 key 不存在,则去设置,成功返回1,否则返回0。
//抢锁
if($redis->setnx($key,$value)){//设置过期时间$redis->expire($key,300);try{//业务逻辑}catch (\Exception $e){}finally {//释放锁$redis->del($key);}
}
注:这个方案的问题在于,setnx 和 expire 两个命令分开了,不是原子操作。如果执行完加锁操作(setnx)后正要执行 expire 设置过期时间,进程崩了或者要重启维护,那么这个锁就长生不老了,别的线程永远也获取不到这个锁了。
2 使用Lua脚本(包含setnx + expire两条指令)
抢锁的改进如下,解决了上面写法1抢锁时的非原子操作。利用 lua 脚本把多个指令一起执行,达到原子操作的目的。
# lua脚本$lua = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 thenredis.call('expire',KEYS[1],ARGV[2])elsereturn 0end";# 执行lua脚本,并传入参数return $redis->eval($lua,[$key,$value,$expires],1);
3 SET的扩展命令(SET EX PX NX)
效果等同于写法2,也是原子性的。
//抢锁
if($redis->set($key,$value,['NX','EX'=>300])){try{//业务逻辑}catch (\Exception $e){}finally {//释放锁$redis->del($key);}
}
SET key value[EX seconds][PX milliseconds][NX|XX]
NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
EX seconds :设定key的过期时间,时间单位是秒。
PX milliseconds: 设定key的过期时间,单位为毫秒
XX: 仅当key存在时设置值
注:但是,这个方案还有个问题:「锁被别的线程误删」
比如线程A执行完后,去释放锁,但它不知道当前的锁可能是线程B持有。线程A就把线程B的锁释放了,但线程B临界区业务代码可能还没执行完成。
释放锁的代码改进如下,判断所有者是否是自己,然后再释放。也用原子操作:
# redis实例
$redis = $this->redis();
# lua 脚本
$lua = "
if redis.call('GET',KEYS[1]) == ARGV[1] thenreturn redis.call('DEL',KEYS[1])
elsereturn 0
end
";
return $redis->eval($lua,[$key,$value],1);
4 SET EX PX NX + 校验所有者,再删除
改进后,最终的代码,见文章开头。
后记
注意,请根据实际业务情况合理设置锁的过期时间 expires 。
一、
无论如何,依然存在一个问题:「锁过期释放了,业务还没执行完」
假设线程A获取锁成功,一直在执行业务代码,300秒过后,它还没执行完。这时候锁就过期了,别的线程又请求进来获取到锁了,也开始执行业务代码。问题就来了,业务代码并不是严格串行执行。
一般情况中小项目中,做好日志、容错判断等即可。如果你项目到了一定规模,如果你追求锁的决定安全性,解决方案是:自动续期。
这个话题,值得再另写一篇文章讲解。自行上网搜索学习,此处省略。
JAVA 提供了很好的一个分布式锁框架: Redisson ,它很好的解决了此问题。PHP 暂时我还没找到好的类库。
还有就是,自己实现自动续期。
二、
多个 redis 实例、集群模式时,解决方案请看官方提供的 RedLock。这又值得另写一篇文章讲解。自行上网搜索学习,此处省略。
相关文章:
php、redis实现分布式锁的正确写法(原子操作 通用类 加讲解)
最终代码(通用类) 1 面试中、实际工作中,经常涉及到 redis 分布式锁,正确写法如下。先奉上代码,再讲解。 <?php namespace app\common\library; /*** 通用分布式锁(原子操作)*/ class Lock {/*** 获取redis实例* return \Redis* throws…...
Transformer在时序预测的应⽤第一弹——Autoformer
Transformer在时序预测的应⽤第一弹——Autoformer 原文地址:Autoformer: Decomposition Transformers with Auto-Correlation for Long-Term Series Forecasting(NIPS 2021) 做长时间序列的预测 Decomposition把时间序列做拆分,…...
文章改写神器在线-AI续写文章生成器
AI续写生成器 AI续写生成器是一种利用人工智能技术的创意工具,能够提高写作效率,为营销推广带来全新的可能性。无论你是写手、广告人员还是市场营销人员,这个工具都能够有效地解决你在写作中遇到的难题。 在内容创作行业中,原创…...
一秒钟给硬盘文件做个树状结构目录
一秒钟给硬盘文件做个树状结构目录 一、背景 对于长时间坐在电脑前的打工人来说,若没有养成良好文件分类习惯的话,年终整理电脑文件绝对是件头疼的事情。 给磁盘文件做个目录,一目了然文件都在哪里?想想都是件头疼的事情。 对于…...
电脑重装系统后会怎样?
有小伙伴的电脑系统运行缓慢卡顿,现在想通过重装系统来解决问题。咨询电脑重装系统会怎么样对系统有影响吗,现在小编就带大家看看电脑重装系统后会怎样。 方法/步骤: 一、电脑重装系统会怎么样 1、我们的电脑重装系统后,电脑…...
100种思维模型之反熵增思维模型-47
查理芒格被誉为反熵增思维模型的倡导者。本文将介绍查理芒格的反熵增思维模型,并分析它的实用性。 一、什么是熵增? 在物理学中,熵是衡量系统无序程度的指标。系统的熵越高,其无序程度越高。这个概念也可以应用到其他领域。在金融…...
【网络安全】Xss漏洞
xss漏洞 xss漏洞介绍危害防御方法xss测试语句xss攻击语句1. 反射性xss2.存储型xss3.DOM型xssdvwa靶场各等级渗透方法xss反射型(存储型方法一致)LowMediumHightimpossible Dom型LowMediumHight xss漏洞介绍 定义:XSS 攻击全称跨站脚本攻击&am…...
17.网络爬虫—Scrapy入门与实战
这里写目录标题 Scrapy基础Scrapy运行流程原理Scrapy的工作流程Scrapy的优点 Scrapy基本使用(豆瓣网为例)创建项目创建爬虫配置爬虫运行爬虫如何用python执行cmd命令数据解析打包数据打开管道pipeline使用注意点 后记 前言: 🏘️🏘️个人简介…...
【面试题】JavaScript 中 try...catch 的使用技巧 ?
大厂面试题分享 面试题库 前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库 web前端面试题库 VS java后端面试题库大全 作为一位 Web 前端工程师,JavaScript 中的 try...catch 是我们常用的特性之一。…...
Java 命名格式规范
Java 命名格式规范 概述 简洁清爽的代码风格应该是大多数开发工程师所期待的。在编码过程中笔者常常因为起名字而纠结,夸张点可以说是编程 5 分钟,命名两小时!究竟为什么命名成为了编码中的拦路虎。 每个公司都有不同的标准,目…...
【C++】STL中的容器适配器 stack queue 和 priority_queue 的模拟实现
STL中的容器适配器 一、容器适配器1、什么是容器适配器2、STL标准库中的容器适配器 二、stack的模拟实现1、stack的简单介绍2、栈的模拟实现 三、queue的模拟实现1、queue的简单介绍2、queue的模拟实现 四、priority_queue的模拟实现1、priority_queue的简单介绍2、priority_qu…...
MongoDB 聚合管道中使用算术表达式运算符
算术表达式运算符主要用于实现数字之间的算术运算,主要包含了对加、减、乘、除、余数、截取、舍入等算术操作。 下面我们进行详细介绍: 一、准备数据 初始化商品数据 db.goods.insertMany([{ "_id": 1, name: "薯片", size: &q…...
代码随想录算法训练营第四十三天-动态规划5|1049. 最后一块石头的重量 II , 494. 目标和 , 474.一和零
最后一块石头重量转化为将一个集合分隔成两个集合,两个集合之间的差值最小,就是最后剩下最小的石头重量。这里可以求集合的一个平均值,如果正好等于平均值,说明可以抵消,这时候重量为0,如果不行,…...
《淘宝网店》:计算总收益
目录 一、题目 二、思路 1、当两个年份不一样的时候 (1)from年剩余之后的收益 (2)中间年份的全部收益 (3)to年有的收益 2、同一个年份 三、代码 详细注释版本: 简化注释版本ÿ…...
2023年03月青少年软件编程C语言一级真题答案——持续更新.....
1.字符长方形 给定一个字符,用它构造一个长为4个字符,宽为3个字符的长方形,可以参考样例输出。 时间限制:1000 内存限制:65536 输入 输入只有一行, 包含一个字符。 输出 该字符构成的长方形,长4个字符,宽3个字符。 样例输入 * 样例输出 **** **** ****#include<bi…...
家用洗地机好用吗?好用的洗地机分享
洗地机是一种高效、节能、环保的清洁设备,广泛应用于各种场所的地面清洁工作。它不仅可以快速清洁地面,还可以有效去除污渍、油渍等难以清洁的污染物,让地面恢复光洁如新的状态。同时,洗地机还可以减少清洁人员的劳动强度…...
《分解因数》:质因数分解
目录 一、题目: 二、思路: 三、代码: 一、题目: 分解因数 《分解因数》题目链接 所谓因子分解,就是把给定的正整数a,分解成若干个素数的乘积,即 a a1 a2 a3 ... an,并且 1 < a1…...
(排序10)归并排序的外排序应用(文件排序)
TIPS 在一些文件操作函数当中,fputc与fgetc这两个函数都是针对字符的,如果说你需要往文件里面去放入整形啊等等,不是字符的类型,这时候就用fprintf,fscanf在参数里面数据类型控制一下就可以。但是话说回来,…...
浅谈根号分治与分块
文章目录 1. 根号分治哈希冲突 2. 线性分块引入教主的魔法[CQOI2011] 动态逆序对[国家集训队] 排队[HNOI2010] 弹飞绵羊蒲公英 1. 根号分治 哈希冲突 题目1 n n n 个数, m m m 次操作。操作 1 为修改某一个数的值,操作 2 为查询所有满足下标模 x x x …...
(OpenAI)ChatGPT注册登录常见问题错误代码及其解决方法
在使用 ChatGPT 的时候我们可能会碰到一些错误的代码,本文统一来介绍一下每一种错误以及解决方法。 错误代码1. 不能在当前国家使用 出现场景:一般在注册或登录的时候会出现。 原因:主要是ChatGPT检测到当前访问所在的地区不允许访问导致。 …...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
C# winform教程(二)----checkbox
一、作用 提供一个用户选择或者不选的状态,这是一个可以多选的控件。 二、属性 其实功能大差不差,除了特殊的几个外,与button基本相同,所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...
ArcGIS Pro+ArcGIS给你的地图加上北回归线!
今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等,设置经线、纬线都以10间隔显示。 2、需要插入背会归线…...
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
这个警告表明您在使用Vue的esm-bundler构建版本时,未明确定义编译时特性标志。以下是详细解释和解决方案: 问题原因: 该标志是Vue 3.4引入的编译时特性标志,用于控制生产环境下SSR水合不匹配错误的详细报告1使用esm-bundler…...
构建Docker镜像的Dockerfile文件详解
文章目录 前言Dockerfile 案例docker build1. 基本构建2. 指定 Dockerfile 路径3. 设置构建时变量4. 不使用缓存5. 删除中间容器6. 拉取最新基础镜像7. 静默输出完整示例 docker runDockerFile 入门syntax指定构造器FROM基础镜像RUN命令注释COPY复制ENV设置环境变量EXPOSE暴露端…...
