RedisTemplate 实现分布式锁

RedisTemplate 实现分布式锁

基础分布式锁

使用redisTemplate和RedisCallback 实现分布式锁

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import redis.clients.jedis.JedisCommands;
import java.util.Arrays;

@Component
public class RedisDistributedLock {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);
    
    /**
     * 尝试获取分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识(用于标识锁的持有者)
     * @param expireTime 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) {
                byte[] value = redisTemplate.getStringSerializer().serialize(requestId);
                String lockValue = new String(value);
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                String result = commands.set(lockKey, lockValue, "NX", "EX", expireTime);
                
                return "OK".equals(result);
            }
        });
    }
    
    /**
     * 释放分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) {
                byte[] value = redisTemplate.getStringSerializer().serialize(requestId);
                String lockValue = new String(value);
                
                // 使用Lua脚本保证原子性:只有锁的持有者才能释放锁
                String luaScript = 
                    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) " +
                    "else " +
                    "return 0 " +
                    "end";
                
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(luaScript);
                redisScript.setResultType(Long.class);
                
                try {
                    Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockValue);
                    if (result != null && result == 1L) {
                        log.info("Redis lock released successfully: {}", lockKey);
                    } else {
                        log.warn("Failed to release Redis lock (possibly held by another client): {}", lockKey);
                    }
                    return result != null && result == 1;
                } catch (Exception e) {
                    log.error("Failed to release Redis lock: {}", lockKey, e);
                }
                return false;
            }
        });
    }
}

RedisDistributedLock是一个基于 Spring Data Redis 实现的分布式锁组件,用于在分布式系统中协调多个服务实例对共享资源的访问控制。

使用场景

适用于需要分布式协调的场景:

  • 订单处理防重复提交

  • 库存扣减防超卖

  • 定时任务分布式调度

  • 分布式系统关键资源互斥访问

优势特点

  1. 简单易用 - 提供简洁的 API 接口

  2. 安全可靠 - 完善的锁机制防止常见并发问题

  3. 可追溯性 - 通过请求标识追踪锁的持有者

  4. 容错处理 - 完善的异常处理和日志记录

该组件为分布式系统提供了轻量级且可靠的分布式锁解决方案,能够有效解决分布式环境下的资源竞争问题。

增强版分布式锁

支持重试和自动续约的分布式锁

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import redis.clients.jedis.JedisCommands;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.connection.RedisConnection;
import javax.annotation.PreDestroy;

@Component
public class RedisDistributedLock {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);
    
    // 用于锁续约的调度器
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    
    /**
     * 尝试获取分布式锁(带重试机制)
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @param expireTime 过期时间(秒)
     * @param waitTime 最大等待时间(毫秒)
     * @param retryInterval 重试间隔(毫秒)
     * @return 是否获取成功
     */
    public boolean tryLockWithRetry(String lockKey, String requestId, long expireTime, 
                                   long waitTime, long retryInterval) {
        long startTime = System.currentTimeMillis();
        
        while (true) {
            // 尝试获取锁
            if (tryLock(lockKey, requestId, expireTime)) {
                log.info("Successfully acquired lock: {}", lockKey);
                return true;
            }
            
            // 检查是否超时
            if (System.currentTimeMillis() - startTime > waitTime) {
                log.warn("Failed to acquire lock within {} ms: {}", waitTime, lockKey);
                return false;
            }
            
            // 等待一段时间后重试
            try {
                log.debug("Waiting to retry acquiring lock: {}", lockKey);
                Thread.sleep(retryInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("Thread interrupted while waiting for lock: {}", lockKey);
                return false;
            }
        }
    }
    
    /**
     * 尝试获取分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识(用于标识锁的持有者)
     * @param expireTime 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        // 基础分布式锁实现
    }
    
    /**
     * 启动锁续约机制
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @param expireTime 续约后的过期时间(秒)
     * @param renewInterval 续约间隔(秒)
     * @return 续约任务Future,可用于取消续约
     */
    public ScheduledFuture<?> startLockRenewal(String lockKey, String requestId, 
                                              long expireTime, long renewInterval) {
        log.info("Starting lock renewal for: {} with interval {}s", lockKey, renewInterval);
        
        return scheduler.scheduleAtFixedRate(() -> {
            try {
                boolean renewed = renewLock(lockKey, requestId, expireTime);
                if (renewed) {
                    log.debug("Lock renewed successfully: {}", lockKey);
                } else {
                    log.warn("Failed to renew lock, lock may have been released: {}", lockKey);
                    // 在实际应用中,这里可以抛出异常或执行其他恢复逻辑
                }
            } catch (Exception e) {
                log.error("Error occurred while renewing lock: {}", lockKey, e);
            }
        }, renewInterval, renewInterval, TimeUnit.SECONDS);
    }
    
    /**
     * 续约分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @param expireTime 续约后的过期时间(秒)
     * @return 是否续约成功
     */
    public boolean renewLock(String lockKey, String requestId, long expireTime) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) {
                byte[] value = redisTemplate.getStringSerializer().serialize(requestId);
                String lockValue = new String(value);
                
                // 使用Lua脚本保证原子性:只有锁的持有者才能续约
                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);
                
                try {
                    Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), 
                                                       lockValue, String.valueOf(expireTime));
                    boolean success = result != null && result == 1;
                    if (!success) {
                        log.warn("Lock renewal failed for: {}", lockKey);
                    }
                    return success;
                } catch (Exception e) {
                    log.error("Error executing lock renewal for: {}", lockKey, e);
                    return false;
                }
            }
        });
    }
    
    /**
     * 释放分布式锁(带续约任务取消)
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @param renewalFuture 续约任务Future(可为null)
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId, ScheduledFuture<?> renewalFuture) {
        // 先取消续约任务
        if (renewalFuture != null && !renewalFuture.isCancelled()) {
            renewalFuture.cancel(false);
            log.debug("Lock renewal task cancelled for: {}", lockKey);
        }
        
        // 然后释放锁
        return releaseLock(lockKey, requestId);
    }
    
    public boolean releaseLock(String lockKey, String requestId) {
        // 基础分布式锁实现
    }
    
    /**
     * 优雅关闭,释放资源
     */
    @PreDestroy
    public void destroy() {
        log.info("Shutting down lock renewal scheduler");
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

使用示例

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private RedisDistributedLock redisDistributedLock;
    
    public void processOrder(String orderId) {
        String lockKey = "order_lock:" + orderId;
        String requestId = UUID.randomUUID().toString();
        ScheduledFuture<?> renewalFuture = null;
        
        try {
            // 尝试获取锁,最多等待3秒,每次重试间隔100ms
            boolean lockAcquired = redisDistributedLock.tryLockWithRetry(
                lockKey, requestId, 30, 3000, 100);
            
            if (!lockAcquired) {
                throw new RuntimeException("Unable to acquire lock for order: " + orderId);
            }
            
            // 启动锁续约,每10秒续约一次,续约后过期时间为30秒
            renewalFuture = redisDistributedLock.startLockRenewal(
                lockKey, requestId, 30, 10);
            
            // 执行业务逻辑
            doProcessOrder(orderId);
            
        } finally {
            // 释放锁并取消续约
            redisDistributedLock.releaseLock(lockKey, requestId, renewalFuture);
        }
    }
    
    private void doProcessOrder(String orderId) {
        // 业务逻辑实现
    }
}

新增功能说明

🔄 重试机制

  • tryLockWithRetry(): 支持在指定时间内自动重试获取锁

  • 可配置最大等待时间和重试间隔

  • 支持线程中断处理

⏰ 续约机制

  • startLockRenewal(): 启动定时续约任务

  • renewLock(): 执行锁续约操作

  • 使用 Lua 脚本保证续约的原子性

  • 自动管理续约任务的启动和取消

相关推荐
Uluoyu4 小时前
支持Word (doc/docx) 和 PDF 转成一张垂直拼接的长PNG图片工具类
java·pdf·word
闭着眼睛学算法4 小时前
【双机位A卷】华为OD笔试之【模拟】双机位A-新学校选址【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
java·c语言·javascript·c++·python·算法·华为od
源码_V_saaskw4 小时前
JAVA校园跑腿校园外卖源码校园外卖小程序校园代买帮忙外卖源码社区外卖源码小程序+公众号+h5
java·开发语言·微信小程序·小程序
源码哥_博纳软云4 小时前
JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统源码小程序+公众号+h5
java·开发语言·微信小程序·小程序
Cc00108524 小时前
【AI学习笔记】用AI生成spring boot + redis
spring boot·笔记·学习·ai编程
红尘客栈24 小时前
Kubernetes 集群调度
java·linux·网络·容器·kubernetes
编程岁月5 小时前
java面试-0203-java集合并发修改异常、快速/安全失败原理、解决方法?
java·开发语言·面试
whltaoin5 小时前
AI 超级智能体全栈项目阶段五:RAG 四大流程详解、最佳实践与调优(基于 Spring AI 实现)
java·人工智能·spring·rag·springai
junnhwan5 小时前
【苍穹外卖笔记】Day05--Redis入门与店铺营业状态设置
java·数据库·redis·笔记·后端·苍穹外卖