欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > Redis实现分布式锁

Redis实现分布式锁

2025/5/3 7:39:57 来源:https://blog.csdn.net/zyxzyx666/article/details/147656649  浏览:    关键词:Redis实现分布式锁

分布式锁是分布式系统中解决资源竞争问题的重要机制。Redis凭借其高性能和原子性操作,成为实现分布式锁的热门选择。本文将详细介绍如何使用Java和Redis实现分布式锁,并重点讲解如何通过Lua脚本保证锁操作的原子性。

一、分布式锁的基本要求

一个可靠的分布式锁应满足以下条件:

  1. 互斥性:同一时刻只有一个客户端能持有锁

  2. 避免死锁:即使客户端崩溃,锁也能自动释放

  3. 容错性:只要大部分Redis节点正常运行,客户端就能获取和释放锁

  4. 释放锁的正确性:只能由锁的持有者释放锁

二、不使用Lua脚本的基础实现

1. 基础分布式锁实现(Java + StringRedisTemplate)

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.UUID;
import java.util.concurrent.TimeUnit;@Component
public class BasicRedisLock {private final StringRedisTemplate stringRedisTemplate;public BasicRedisLock(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 获取锁* @param lockKey 锁的key* @param expireTime 过期时间(秒)* @return 锁的value(用于释放锁时验证)*/public String lock(String lockKey, long expireTime) {String value = UUID.randomUUID().toString();Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);return Boolean.TRUE.equals(success) ? value : null;}/*** 释放锁* @param lockKey 锁的key* @param value 锁的value* @return 是否释放成功*/public boolean unlock(String lockKey, String value) {// 1. 获取当前锁的值String currentValue = stringRedisTemplate.opsForValue().get(lockKey);// 2. 验证是否是自己的锁if (value.equals(currentValue)) {// 3. 删除锁return stringRedisTemplate.delete(lockKey);}return false;}
}

2. 基础实现的使用示例

@RestController
public class OrderController {@Autowiredprivate BasicRedisLock basicRedisLock;@PostMapping("/createOrder")public String createOrder() {String lockKey = "order_lock";String lockValue = null;try {// 尝试获取锁lockValue = basicRedisLock.lock(lockKey, 30);if (lockValue == null) {return "系统繁忙,请稍后再试";}// 执行业务逻辑return "订单创建成功";} finally {// 释放锁if (lockValue != null) {basicRedisLock.unlock(lockKey, lockValue);}}}
}

3. 基础实现的缺点

  1. 非原子性操作问题

    • 释放锁的操作分为"获取值"、"比较值"和"删除键"三步,不是原子操作

    • 在比较值和删除键之间,锁可能已过期并被其他客户端获取,导致误删别人的锁

  2. 网络延迟问题

    • 客户端A获取锁并执行时间过长,锁已自动释放

    • 客户端B获取了锁

    • 客户端A执行完任务后,仍会尝试释放锁,可能释放客户端B的锁

  3. 性能问题

    • 每次释放锁需要至少2次Redis操作(GET+DEL)

三、使用Lua脚本优化实现

1. Lua脚本实现的分布式锁

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@Component
public class LuaRedisLock {private final StringRedisTemplate stringRedisTemplate;public LuaRedisLock(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}// 获取锁(与基础实现相同)public String lock(String lockKey, long expireTime) {String value = UUID.randomUUID().toString();Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);return Boolean.TRUE.equals(success) ? value : null;}// 使用Lua脚本释放锁public boolean unlock(String lockKey, String value) {// Lua脚本String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"    return redis.call('del', KEYS[1]) " +"else " +"    return 0 " +"end";// 创建Redis脚本对象DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(luaScript);redisScript.setResultType(Long.class);// 执行脚本Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);return result != null && result == 1;}
}

2. Lua脚本详解

Lua脚本语法说明
-- KEYS[1] 表示第一个键参数
-- ARGV[1] 表示第一个非键参数
-- redis.call() 用于执行Redis命令-- 脚本逻辑:
if redis.call('get', KEYS[1]) == ARGV[1] then  -- 如果锁的值等于传入的值return redis.call('del', KEYS[1])          -- 则删除这个键
elsereturn 0                                   -- 否则返回0表示失败
end
Lua脚本在Redis中的优势
  1. 原子性:整个脚本作为一个整体执行,执行期间不会被其他命令打断

  2. 减少网络开销:多个操作合并为一个脚本,减少客户端与Redis的交互次数

  3. 灵活性:可以编写复杂的逻辑来处理各种场景

3. Lua脚本实现的使用示例

@RestController
public class PaymentController {@Autowiredprivate LuaRedisLock luaRedisLock;@PostMapping("/pay")public String payOrder(@RequestParam String orderId) {String lockKey = "pay_lock:" + orderId;String lockValue = null;try {// 尝试获取锁,设置30秒过期lockValue = luaRedisLock.lock(lockKey, 30);if (lockValue == null) {return "支付处理中,请勿重复提交";}// 执行业务逻辑processPayment(orderId);return "支付成功";} finally {// 释放锁if (lockValue != null) {luaRedisLock.unlock(lockKey, lockValue);}}}private void processPayment(String orderId) {// 支付处理逻辑}
}

四、两种实现的对比

特性基础实现Lua脚本实现
原子性非原子操作原子操作
安全性可能误删别人的锁不会误删别人的锁
网络开销至少2次Redis操作1次Redis操作
实现复杂度简单需要了解Lua脚本
性能较低较高
适用场景对安全性要求不高的简单场景对安全性和性能有要求的场景

五、Lua脚本的更多用法

1. 带重试的获取锁脚本

public String lockWithRetry(String lockKey, long expireTime, long waitTime) {String value = UUID.randomUUID().toString();String luaScript = "local wait = tonumber(ARGV[3]) * 1000 " +"local endTime = redis.call('time')[1] * 1000 + wait " +"while redis.call('time')[1] * 1000 < endTime do " +"    if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +"        redis.call('expire', KEYS[1], ARGV[2]) " +"        return ARGV[1] " +"    end " +"    -- 短暂休眠 " +"    redis.call('echo', 'waiting...') " +"end " +"return nil";DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(luaScript);redisScript.setResultType(String.class);return stringRedisTemplate.execute(redisScript,Collections.singletonList(lockKey),value, String.valueOf(expireTime), String.valueOf(waitTime));
}

2. 锁续期脚本

public boolean renewLock(String lockKey, String value, long expireTime) {String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"    return redis.call('expire', KEYS[1], ARGV[2]) " +"else " +"    return 0 " +"end";DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(luaScript);redisScript.setResultType(Long.class);Long result = stringRedisTemplate.execute(redisScript,Collections.singletonList(lockKey),value, String.valueOf(expireTime));return result != null && result == 1;
}

六、总结

  1. 基础实现简单但存在安全隐患,适合对一致性要求不高的场景

  2. Lua脚本实现通过原子操作解决了安全问题,是生产环境推荐的做法

  3. Lua脚本在Redis中执行是原子的,适合实现复杂的多步操作

  4. 实际应用中还可以结合Redisson等成熟框架,它们提供了更完善的分布式锁实现

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词