【JAVA高级】如何使用Redis加锁和解锁(二)做分布式锁案例(防误删锁)

10 篇文章 1 订阅
6 篇文章 0 订阅

使用Redis中加锁和解锁的开发中,解锁需要验证当前客户端是否是锁的持有者,然后再解锁,具体怎么验证,下面是一个示例(使用redisTemplate来实现)

在使用Redis进行加锁和解锁的开发中,确实需要验证当前客户端是否是锁的持有者,然后再进行解锁操作。这是为了确保锁的安全性,避免其他客户端非法解锁导致数据不一致的问题。

在Java中,如果你使用Spring Boot并集成了Spring Data Redis,那么redisTemplate是一个常用的操作Redis的模板类。下面是一个使用redisTemplate来实现加锁和解锁(包括验证锁持有者)的示例。

首先,确保你的Spring Boot项目中已经添加了Spring Data Redis的依赖。

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-redis</artifactId>  
</dependency>

你可以创建一个服务类来封装加锁和解锁的逻辑:
第一种实现思路
redisTemplate.execute(); 接收的内容直接是UNLOCK_SCRIPT Lua脚本

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.data.redis.core.script.DefaultRedisScript;  
import org.springframework.data.redis.core.script.RedisScript;  
import org.springframework.stereotype.Service;  
  
import java.util.Arrays;  
import java.util.Collections;  
import java.util.UUID;  
import java.util.concurrent.TimeUnit;  
  
@Service  
public class RedisLockService {  
  
    private static final String LOCK_PREFIX = "lock:";  
    private static final RedisScript<Long> UNLOCK_SCRIPT = new DefaultRedisScript<>(  
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +  
        "  return redis.call('del', KEYS[1]) " +  
        "else " +  
        "  return 0 " +  
        "end",  
        Long.class  
    );  
  
    @Autowired  
    private RedisTemplate<String, String> redisTemplate;  
  
    /**  
     * 尝试加锁  
     *  
     * @param lockKey   锁的key  
     * @param requestId 锁的持有者标识符(如UUID)  
     * @param expireTime 锁的过期时间(秒)  
     * @return 加锁成功返回true,否则返回false  
     */  
    public boolean tryLock(String lockKey, String requestId, long expireTime) {  
        String lockKeyWithPrefix = LOCK_PREFIX + lockKey;  
        // 使用SET命令的NX和PX选项来加锁  
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKeyWithPrefix, requestId, expireTime, TimeUnit.SECONDS);  
        return result != null && result;  
    }  
  
    /**  
     * 解锁  
     *  
     * @param lockKey   锁的key  
     * @param requestId 锁的持有者标识符(如UUID)  
     * @return 解锁成功返回true,否则返回false  
     */  
    public boolean unlock(String lockKey, String requestId) {  
        String lockKeyWithPrefix = LOCK_PREFIX + lockKey;  
        // 使用Lua脚本来验证锁的持有者并解锁  
        Long result = (Long) redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKeyWithPrefix), requestId);  
        return result != null && result == 1L;  
    }  
  
    // 示例用法  
    public void demoLock() {  
        String lockKey = "myLock";  
        String requestId = UUID.randomUUID().toString();  
        long expireTime = 10L; // 锁的过期时间为10秒  
  
        // 尝试加锁  
        if (tryLock(lockKey, requestId, expireTime)) {  
            try {  
                // 执行需要同步的代码块  
                // ...  
  
                // 模拟处理时间  
                try {  
                    Thread.sleep(5000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
  
            } finally {  
                // 释放锁  
                unlock(lockKey, requestId);  
            }  
        } else {  
            // 处理加锁失败的情况  
            System.out.println("Lock acquisition failed");  
        }  
    }  
}

代码解读:
tryLock方法使用redisTemplate.opsForValue().setIfAbsent来尝试加锁,该方法结合了SET命令的NX(Not
Exists,不存在则设置)和PX(设置键的过期时间,单位为毫秒)选项。unlock方法则使用了一个Lua脚本来执行解锁操作,该脚本首先检查Redis中存储的锁的值是否与客户端提供的值相匹配,如果匹配则删除锁。

请注意,由于Redis的集群模式和网络延迟等因素,上述实现可能不是完全无懈可击的。在生产环境中,你可能需要考虑使用更健壮的分布式锁解决方案,如Redisson提供的锁服务。此外,还应该注意处理Redis服务器宕机或网络分区等异常情况。

第二种实现思路:
redisTemplate.execute()中的接的参数是new RedisCallback()对象

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.data.redis.connection.RedisConnection;  
import org.springframework.data.redis.core.RedisCallback;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.stereotype.Service;  
  
import java.util.UUID;  
import java.util.concurrent.TimeUnit;  
  
@Service  
public class RedisLockService {  
  
    @Autowired  
    private RedisTemplate<String, String> redisTemplate;  
  
    private static final String LOCK_PREFIX = "lock:";  
  
    /**  
     * 尝试获取锁  
     *  
     * @param key     锁的key  
     * @param expire  锁的超时时间,单位秒  
     * @return 锁的唯一标识,如果获取锁失败则返回null  
     */  
    public String tryLock(String key, long expire) {  
        String requestId = UUID.randomUUID().toString();  
        Boolean result = redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + key, requestId, expire, TimeUnit.SECONDS);  
        return result ? requestId : null;  
    }  
  
    /**  
     * 释放锁  
     *  
     * @param key     锁的key  
     * @param requestId 锁的唯一标识  
     * @return 是否成功释放锁  
     */  
    public boolean unlock(String key, String requestId) {  
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";  
        Object result = redisTemplate.execute(new RedisCallback<Object>() {  
            @Override  
            public Object doInRedis(RedisConnection connection) throws Exception {  
                Object nativeConnection = connection.getNativeConnection();  
                if (nativeConnection instanceof jedis.Jedis) {  
                    return ((jedis.Jedis) nativeConnection).eval(script, Collections.singletonList(LOCK_PREFIX + key),  
                            Collections.singletonList(requestId));  
                }  
                // 注意:这里应该添加对其他Redis客户端库的支持,比如Lettuce  
                throw new UnsupportedOperationException("Unsupported Redis client library");  
            }  
        });  
        return "1".equals(result.toString());  
    }  
  
    /**  
     * 调用需要加锁的方法  
     *  
     * @param key 锁的key  
     */  
    public void criticalSection(String key) {  
        String requestId = tryLock(key, 10); // 尝试获取锁,超时时间为10秒  
        if (requestId != null) {  
            try {  
                // 执行需要同步的代码块  
                System.out.println("Executing critical section...");  
                // 模拟耗时操作  
                Thread.sleep(2000);  
            } catch (InterruptedException e) {  
                Thread.currentThread().interrupt();  
            } finally {  
                // 释放锁  
                unlock(key, requestId);  
            }  
        } else {  
            // 获取锁失败,可以选择重试、等待或返回错误  
            System.out.println("Failed to acquire lock for key: " + key);  
        }  
    }  
}

代码解读:
加锁(lock):使用setIfAbsent方法尝试设置锁的key和value(这里value是UUID),并设置过期时间。如果key已经存在,则不会进行设置,表示锁被其他客户端持有。
解锁(unlock):使用Lua脚本来确保操作的原子性。Lua脚本首先检查锁的value是否与预期的UUID相匹配,如果匹配则删除该key,表示成功解锁;如果不匹配,则返回0,表示解锁失败(非锁的持有者)。

上述是可以通用的加锁解锁代码实现思路。OK

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执键行天涯

码你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值