在 Redis 分布式锁场景中,锁超时但业务逻辑未完成解决方案

在 Redis 分布式锁场景中,锁超时但业务逻辑未完成是一个典型问题,通常称为锁过期与业务执行时间不匹配
以下是几种解决方案:

方案一:自续期(看门狗机制)

在获取锁的同时启动一个后台线程,定期检查业务是否仍在执行,若未执行完则自动延长锁的过期时间。
示例代码(使用 Redisson 框架):

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedisLockDemo {
    public static void main(String[] args) {
        // 配置Redisson客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        RedissonClient redisson = Redisson.create(config);

        // 获取锁(默认带看门狗机制,自动续期)
        RLock lock = redisson.getLock("myLock");
        try {
            // 尝试获取锁,等待100秒,自动释放时间30秒
            boolean isLocked = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);
            if (isLocked) {
                try {
                    // 执行业务逻辑(可能耗时较长)
                    Thread.sleep(20000); // 模拟20秒业务操作
                } finally {
                    lock.unlock(); // 释放锁
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            redisson.shutdown(); // 关闭客户端
        }
    }
}

关键说明

  • Redisson 的看门狗默认每 10 秒检查一次(默认锁超时时间 30 秒),若业务未完成则自动续期至 30 秒。
  • 无需手动管理续期,框架自动处理。
方案二:预估业务时间,设置合理的超时时间

根据业务历史数据预估最大执行时间,设置足够长的锁超时时间,但需避免过长导致资源浪费。
示例代码:

// 使用Jedis客户端手动实现
import redis.clients.jedis.Jedis;

public class RedisLockManual {
    private static final String LOCK_KEY = "myLock";
    private static final String RELEASE_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "   return redis.call('del', KEYS[1]) " +
        "else " +
        "   return 0 " +
        "end";

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String requestId = java.util.UUID.randomUUID().toString();

        try {
            // 设置锁,超时时间设为业务预估最大执行时间(如120秒)
            String result = jedis.set(LOCK_KEY, requestId, "NX", "EX", 120);
            if ("OK".equals(result)) {
                try {
                    // 执行业务逻辑(确保在120秒内完成)
                    Thread.sleep(100000); // 模拟100秒业务操作
                } finally {
                    // 使用Lua脚本原子性释放锁,避免误删
                    jedis.eval(RELEASE_SCRIPT, 1, LOCK_KEY, requestId);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }
    }
}
方案三:分段处理业务逻辑

将长耗时业务拆分为多个短任务,每个任务单独获取锁并设置合理超时,任务间通过状态标识或消息队列协调。
示例流程:
1、任务 A:获取锁 → 处理数据 → 释放锁 → 记录进度。
2、任务 B:获取锁 → 读取进度 → 继续处理 → 释放锁。

方案四:结合数据库事务补偿

使用 Redis 锁保证并发控制,同时通过数据库事务确保数据一致性,若锁超时,在业务回滚时通过事务补偿机制处理。
示例代码(伪代码):

public void complexBusiness() {
    String lockKey = "order:" + orderId;
    String requestId = UUID.randomUUID().toString();
    
    try (Connection conn = dataSource.getConnection()) {
        conn.setAutoCommit(false);
        
        // 获取Redis锁
        boolean locked = redisService.tryLock(lockKey, requestId, 60);
        if (!locked) {
            throw new RuntimeException("获取锁失败");
        }
        
        try {
            // 执行业务操作(数据库更新)
            orderDao.updateStatus(orderId, "PROCESSING");
            inventoryDao.decreaseStock(productId, quantity);
            
            // 提交事务
            conn.commit();
        } catch (Exception e) {
            // 回滚事务
            conn.rollback();
            throw e;
        } finally {
            // 释放锁
            redisService.releaseLock(lockKey, requestId);
        }
    }
}
总结:

推荐方案:优先使用自动续期(看门狗),如Redisson框架,简单可靠。

  • 适用场景
    • 短业务:预估超时时间+ Lua脚本释放锁
    • 长业务:分段处理 + 状态记录
    • 强一致性:结合数据库事务补偿
      根据业务特点选择合适的方案,避免锁超时导致的并发问题。

你可能感兴趣的:(redis,分布式,数据库)