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检测到当前访问所在的地区不允许访问导致。 …...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
