京东6.18大促主会场领京享红包更优惠

 找回密码
 立即注册

QQ登录

只需一步,快速开始

基于PHP+Redis实现分布式锁

2024-11-4 05:53| 发布者: ae2942d9| 查看: 80| 评论: 0

摘要: 目录一、Redis作为分布式锁的优势二、PHP中使用Redis实现分布式锁的步骤与原理三、待优化的地方一、Redis作为分布式锁的优势 Redis是一个开源的、基于内存的键值存储体系,它支持多种数据结构并具备持久化选项。由于
目录

一、Redis作为分布式锁的优势

Redis是一个开源的、基于内存的键值存储体系,它支持多种数据结构并具备持久化选项。由于其提供了原子操作(如[code]SETNX[/code]、[code]EXPIRE[/code]等)和高性能特性,使得Redis成为实现分布式锁的理想选择:

  1. 性能优异:Redis是内存数据库,相应速度极快,适合于高频读写的场景。
  2. 原子性:Redis对某些命令(如[code]SETNX[/code])提供了原子操作,还可以实行lua脚本,所以确保了业务的稳定性。
  3. 超时开释:可以设置锁的有效期,即使持有锁的历程崩溃,也能通过逾期机制自动开释锁,克制死锁问题。

二、PHP中使用Redis实现分布式锁的步骤与原理

前期准备

[code][/code]

在使用分布式锁时候我们起告急考虑以下几点:

  • 如何确保锁的唯一性?
    使用phpredis扩展的 setNx('key','value') 或者使用 set('key', 'value', ['nx', 'ex'=>10]) # Will set the key, if it doesn't exist, with a ttl of 10 second 方法,这些方法保证这个key不存在于redis数据库时才会写入,就算有N个并发同时在写这个key,redis也能确保只会有一个能写乐成。
  • 如何克制死锁?
    死锁一般发生在我们的业务代码抛出非常或者实行超时,最终没有开释锁从而导致产生了死锁。这种情况我们可以通过增加一个锁的有效期就能克制产存亡锁。比方:
    • 使用redis的expire方法给对应的key设置一个有效期 expire(string $key, int $seconds, ?string $mode = NULL): Redis|bool
    • 使用lua脚本 redis.call("expire", KEYS[1], ARGV[2])
  • 如何确保redis命令实行的原子性?

要保证原子性必须要求一系列操作要么全部乐成实行,要么全部不实行。举例:

[code]$redis = new \Redis(); $redis->connect('127.0.0.1',6379); $result = $redis->setNx('key','val'); if ($result) { $redis->expire('key',30); } [/code]

上面的代码看起来没有太大的问题,但是 $redis->expire() 一旦实行失败就创建了一个不外期的值,最终就可能导致产存亡锁,这就是为什么要保证命令实行的原子性。

我们可以通过 $redis->eval() 方法实行 lua脚本 来解决这个问题(我们不消关心实现细节,这是底层的实现,只需要知道要保证 redis 命令实行的原子性用lua脚本就行)。示例:

[code]$redis = new \Redis(); $redis->connect('127.0.0.1',6379); $luaScript = <<<LUA if redis.call("setnx", KEYS[1], ARGV[1]) == 1 then redis.call("expire", KEYS[1], ARGV[2]) return true end return false LUA; $result = $redis>eval($luaScript,[ $this->lockKey, $this->requestId, $this->expireTime ],1); [/code]

eval 方法使用详解,官方的文档和示例写得有点打脑壳,完全没写脚本字符串中的 KEYS 和 ARGV 和传递参数的对应关系。下面写了一个对应关系的例子方便大家明确:

语法:$redis>eval(string $script, ?array $args, ?int num_keys): mixed

参数阐明:

  • string $script 实行的lua脚本字符串
  • ?array $args lua脚本字符串中 [code]KEYS[/code] 和 [code]ARGV[/code] 的对应值,按顺序对应(可选值)
  • ?int num_keys lua脚本字符串中 [code]KEYS[/code] 的数目,写了几个 [code]KEYS[/code] 就传几个(可选值)

官方文档eval方法阐明:

[code]//index.php $redis = new \Redis(); $redis->connect('127.0.0.1',6379); $luaScript = <<<LUA return {KEYS[1],KEYS[2],KEYS[3],ARGV[1],ARGV[2]}; LUA; var_dump($redis->eval($luaScript,[1,2,3,4,5],3)); [/code]

输出效果

以下是完整的实当代码:

  • RedisDistributedLock.php
[code]<?php class RedisDistributedLock { private $redis; private $lockKey; private $requestId; private $expireTime; /** * @param string $lockKey 加锁的key * @param int $expireTime 锁的有效期(单位:秒) */ public function __construct(string $lockKey, $expireTime = 30) { $redis = new \Redis(); $redis->connect('127.0.0.1',6379); $this->redis = $redis; $this->lockKey = $lockKey; $this->expireTime = $expireTime; $this->requestId = uniqid(); // 天生唯一请求ID } /** * 实行获取锁,并在指定次数内举行重试 * * @param int $maxRetries 最大重试次数,默认为3次 * @param int $retryDelay 两次重试之间的耽误时间(单位:毫秒) * @return bool 是否乐成获取锁 */ public function acquireLock(int $maxRetries = 3, int $retryDelay = 50): bool { for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { if ($this->acquireLockOnce()) { return true; } usleep($retryDelay * 1000); } return false; } /** * 举行加锁 * @return bool 加锁是否乐成 */ private function acquireLockOnce(): bool { $luaScript = <<<LUA if redis.call("setnx", KEYS[1], ARGV[1]) == 1 then redis.call("expire", KEYS[1], ARGV[2]) return true end return false LUA; $result = $this->redis->eval( $luaScript, [ $this->lockKey, $this->requestId, $this->expireTime ], 1 ); return (bool)$result; } /** * 开释锁 * @return bool */ public function releaseLock(): bool { $luaScript = <<<LUA if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end LUA; $result = $this->redis->eval( $luaScript, [ $this->lockKey, $this->requestId ], 1 ); return (bool)$result; } } ?> [/code]
  • index.php
[code]<?php include 'RedisDistributedLock.php'; function task() { $lockKey = 'task_1'; $handler = new RedisDistributedLock($lockKey); $startTime = time(); if ($handler->acquireLock(4)) { //@TODO 加锁乐成后实行具体的业务逻辑 echo '加锁乐成 开始实行加锁逻辑的时间:'.date('Y-m-d H:i:s',$startTime); echo "\r\n"; echo '锁定到:'.date('Y-m-d H:i:s',time() + 15); sleep(15); $handler->releaseLock(); echo "\r\n"; echo '---15s后已开释锁---'; } else { echo '加锁失败:'.date('Y-m-d H:i:s',$startTime); return false; } } task(); ?> [/code]

实行效果如下:

三、待优化的地方

  • 集群情况下假如主节点挂掉,如何保证设置的 [code]key[/code] 在子节点上不会丢失?
  • 如那里理[code] key[/code] 的自动续期

以上就是基于PHP+Redis实现分布式锁的详细内容,更多关于PHP Redis分布式锁的资料请关注脚本之家其它相关文章!


来源:https://www.jb51.net/program/317065j2t.htm
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
关闭

站长推荐上一条 /6 下一条

QQ|手机版|小黑屋|梦想之都-俊月星空 ( 粤ICP备18056059号 )|网站地图

GMT+8, 2025-7-1 21:54 , Processed in 0.042256 second(s), 18 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

返回顶部