Spring Boot4.0 集成 Redis 实现看门狗 Lua 脚本分布式锁完整使用

Spring Boot 集成 Redis 实现看门狗 Lua 脚本分布式锁

公司实战项目 Lua 脚本分布式锁,偷偷给你们看,怎么使用的,好多人都不会也不懂

一、概述

1.1 什么是看门狗锁?

看门狗锁(Watchdog Lock)是一种基于Redis实现的分布式锁优化方案,它在传统分布式锁的基础上增加了自动续期机制,防止业务执行时间超过锁过期时间而导致的锁提前释放问题。

1.2 基本原理

  1. 获取锁时:设置一个较短的初始过期时间(如30秒)
  2. 后台线程(看门狗):定期检查锁是否仍被持有,如果是则自动续期
  3. Lua脚本保证原子性:所有关键操作使用Lua脚本,确保在Redis端的原子执行
  4. 锁释放:业务完成后手动释放,或线程结束时自动清理

1.3 核心优势 vs 传统分布式锁

特性 传统分布式锁 看门狗锁
过期时间 固定,需要预估业务时间 动态续期,自适应业务耗时
业务超时风险 高(业务超时导致锁失效) 低(自动续期)
实现复杂度 简单 中等
资源占用 需要额外线程维护

二、应用场景

2.1 适用场景

  1. 长时间任务:批处理、数据迁移等耗时不确定的操作
  2. 事务性操作:分布式事务中的资源锁定
  3. 定时任务防重:确保分布式环境下只有一个节点执行
  4. 秒杀/限流:高并发场景下的资源争用控制

2.2 不适用场景

  1. 极短时间锁(< 100ms):续期开销可能大于收益
  2. 对延迟极其敏感的场景:看门狗检查可能引入额外延迟

三、实现方案详述

3.1 架构设计

复制代码
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  业务线程       │    │  看门狗线程     │    │  Redis Server   │
│                 │    │                 │    │                 │
│ 1.获取锁        │───▶│                 │───▶│ 执行Lua脚本     │
│                 │    │                 │    │ 设置锁+过期时间 │
│ 2.执行业务逻辑  │    │ 3.启动看门狗    │    │                 │
│                 │    │   定期检查      │    │                 │
│                 │◀───│   自动续期      │◀───│ 执行续期Lua    │
│ 4.释放锁        │───▶│                 │───▶│ 删除Key        │
└─────────────────┘    └─────────────────┘    └─────────────────┘

3.2 关键特性

  1. 可重入性:同一线程可重复获取锁
  2. 自动续期:后台线程定期延长锁有效期
  3. 防死锁:设置最大续期次数,避免异常情况无限续期
  4. 异常恢复:客户端异常断开时,锁会自动过期

四、Spring Boot 集成实现

4.1 环境准备

Maven 依赖
xml 复制代码
<!-- Spring Boot Starter Data Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Redis客户端(使用Lettuce) -->
<dependency>
    <groupId>io.lettuce.core</groupId>
    <artifactId>lettuce-core</artifactId>
</dependency>

<!-- JSON处理 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

<!-- AOP -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
application.yml 配置
yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms
    timeout: 5000ms

# 分布式锁配置
distributed:
  lock:
    watch-dog:
      enable: true                    # 是否启用看门狗
      check-interval: 10000          # 检查间隔(ms)
      lease-time: 30000              # 租约时间(ms)
      max-renew-times: 10            # 最大续期次数

4.2 核心实现类

4.2.1 Redis 配置类
java 复制代码
package com.example.lock.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@ConfigurationProperties(prefix = "distributed.lock.watch-dog")
public class RedisLockConfig {
    
    private boolean enable = true;
    private long checkInterval = 10000;
    private long leaseTime = 30000;
    private int maxRenewTimes = 10;
    
    // getters and setters
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public DefaultRedisScript<Long> lockScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(
            "if (redis.call('exists', KEYS[1]) == 0) then " +
            "    redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "    redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "    return 1; " +
            "end; " +
            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
            "    redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "    redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "    return 1; " +
            "end; " +
            "return 0;"
        );
        script.setResultType(Long.class);
        return script;
    }
    
    @Bean
    public DefaultRedisScript<Long> unlockScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(
            "if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then " +
            "    return 0; " +
            "end; " +
            "local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1); " +
            "if (counter > 0) then " +
            "    redis.call('pexpire', KEYS[1], ARGV[2]); " +
            "    return 1; " +
            "else " +
            "    redis.call('del', KEYS[1]); " +
            "    return 1; " +
            "end; " +
            "return 0;"
        );
        script.setResultType(Long.class);
        return script;
    }
    
    @Bean
    public DefaultRedisScript<Long> renewScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(
            "if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then " +
            "    redis.call('pexpire', KEYS[1], ARGV[2]); " +
            "    return 1; " +
            "end; " +
            "return 0;"
        );
        script.setResultType(Long.class);
        return script;
    }
}
4.2.2 分布式锁实现类
java 复制代码
package com.example.lock.service;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Component
public class RedisWatchdogLock {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private DefaultRedisScript<Long> lockScript;
    
    @Autowired
    private DefaultRedisScript<Long> unlockScript;
    
    @Autowired
    private DefaultRedisScript<Long> renewScript;
    
    @Autowired
    private RedisLockConfig config;
    
    // 线程本地存储:存储当前线程持有的锁信息
    private final ThreadLocal<LockInfo> currentLock = new ThreadLocal<>();
    
    // 全局锁映射:lockKey -> 锁信息
    private final ConcurrentHashMap<String, LockInfo> lockMap = new ConcurrentHashMap<>();
    
    // 看门狗线程池
    private final ScheduledExecutorService watchdogExecutor = 
        new ScheduledThreadPoolExecutor(1, r -> {
            Thread t = new Thread(r, "redis-lock-watchdog");
            t.setDaemon(true);
            return t;
        });
    
    @Data
    private static class LockInfo {
        private String lockKey;
        private String lockValue;
        private long leaseTime;
        private int holdCount;
        private long lastRenewTime;
        private int renewCount;
        private ReentrantLock localLock = new ReentrantLock();
    }
    
    /**
     * 尝试获取锁(阻塞版本)
     */
    public boolean tryLock(String lockKey, long waitTime, TimeUnit unit) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        long timeout = unit.toMillis(waitTime);
        String lockValue = UUID.randomUUID().toString();
        
        // 检查是否已经持有锁(可重入)
        LockInfo existingLock = currentLock.get();
        if (existingLock != null && existingLock.getLockKey().equals(lockKey)) {
            existingLock.setHoldCount(existingLock.getHoldCount() + 1);
            return true;
        }
        
        // 本地锁,防止同一JVM内多个线程同时竞争
        ReentrantLock localLock = new ReentrantLock();
        localLock.lock();
        try {
            while (true) {
                // 尝试获取Redis锁
                Long result = redisTemplate.execute(
                    lockScript,
                    Collections.singletonList(lockKey),
                    String.valueOf(config.getLeaseTime()),
                    lockValue
                );
                
                if (result != null && result == 1) {
                    // 获取成功,创建锁信息
                    LockInfo lockInfo = new LockInfo();
                    lockInfo.setLockKey(lockKey);
                    lockInfo.setLockValue(lockValue);
                    lockInfo.setLeaseTime(config.getLeaseTime());
                    lockInfo.setHoldCount(1);
                    lockInfo.setLastRenewTime(System.currentTimeMillis());
                    
                    lockMap.put(lockKey, lockInfo);
                    currentLock.set(lockInfo);
                    
                    // 启动看门狗
                    if (config.isEnable()) {
                        startWatchdog(lockInfo);
                    }
                    
                    log.debug("Lock acquired: {} by {}", lockKey, lockValue);
                    return true;
                }
                
                // 检查是否超时
                if (System.currentTimeMillis() - startTime > timeout) {
                    log.debug("Lock acquisition timeout: {}", lockKey);
                    return false;
                }
                
                // 短暂休眠后重试
                Thread.sleep(100);
            }
        } finally {
            localLock.unlock();
        }
    }
    
    /**
     * 启动看门狗线程
     */
    private void startWatchdog(LockInfo lockInfo) {
        watchdogExecutor.scheduleWithFixedDelay(() -> {
            try {
                renewLock(lockInfo);
            } catch (Exception e) {
                log.error("Watchdog error for lock: {}", lockInfo.getLockKey(), e);
            }
        }, config.getCheckInterval() / 2, config.getCheckInterval(), TimeUnit.MILLISECONDS);
    }
    
    /**
     * 续期锁
     */
    private void renewLock(LockInfo lockInfo) {
        if (lockInfo.getRenewCount() >= config.getMaxRenewTimes()) {
            log.warn("Lock {} reached max renew times", lockInfo.getLockKey());
            return;
        }
        
        try {
            Long result = redisTemplate.execute(
                renewScript,
                Collections.singletonList(lockInfo.getLockKey()),
                lockInfo.getLockValue(),
                String.valueOf(config.getLeaseTime())
            );
            
            if (result != null && result == 1) {
                lockInfo.setLastRenewTime(System.currentTimeMillis());
                lockInfo.setRenewCount(lockInfo.getRenewCount() + 1);
                log.debug("Lock renewed: {}", lockInfo.getLockKey());
            } else {
                log.warn("Lock renewal failed: {}", lockInfo.getLockKey());
            }
        } catch (Exception e) {
            log.error("Lock renewal error: {}", lockInfo.getLockKey(), e);
        }
    }
    
    /**
     * 释放锁
     */
    public boolean unlock(String lockKey) {
        LockInfo lockInfo = currentLock.get();
        if (lockInfo == null || !lockInfo.getLockKey().equals(lockKey)) {
            log.warn("Current thread does not hold lock: {}", lockKey);
            return false;
        }
        
        lockInfo.getLocalLock().lock();
        try {
            lockInfo.setHoldCount(lockInfo.getHoldCount() - 1);
            
            if (lockInfo.getHoldCount() > 0) {
                // 还有重入,只减少计数
                return true;
            }
            
            // 完全释放Redis锁
            Long result = redisTemplate.execute(
                unlockScript,
                Collections.singletonList(lockKey),
                lockInfo.getLockValue(),
                String.valueOf(config.getLeaseTime())
            );
            
            if (result != null && result == 1) {
                // 清理资源
                lockMap.remove(lockKey);
                currentLock.remove();
                log.debug("Lock released: {}", lockKey);
                return true;
            }
            
            return false;
        } finally {
            lockInfo.getLocalLock().unlock();
        }
    }
    
    /**
     * 尝试获取锁(非阻塞版本)
     */
    public boolean tryLock(String lockKey) {
        try {
            return tryLock(lockKey, 0, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * 强制释放锁(用于异常情况)
     */
    public void forceUnlock(String lockKey) {
        LockInfo lockInfo = lockMap.get(lockKey);
        if (lockInfo != null) {
            redisTemplate.delete(lockKey);
            lockMap.remove(lockKey);
            currentLock.remove();
            log.warn("Force unlocked: {}", lockKey);
        }
    }
    
    /**
     * 清理线程本地存储
     */
    public void cleanup() {
        LockInfo lockInfo = currentLock.get();
        if (lockInfo != null) {
            unlock(lockInfo.getLockKey());
        }
    }
}
4.2.3 AOP 注解支持
java 复制代码
package com.example.lock.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
    
    /**
     * 锁的key,支持SpEL表达式
     */
    String key();
    
    /**
     * 锁的key前缀
     */
    String prefix() default "lock:";
    
    /**
     * 等待锁的最长时间
     */
    long waitTime() default 5;
    
    /**
     * 等待时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    
    /**
     * 获取失败时的错误信息
     */
    String message() default "系统繁忙,请稍后再试";
}

package com.example.lock.aop;

import com.example.lock.annotation.RedisLock;
import com.example.lock.service.RedisWatchdogLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class RedisLockAspect {
    
    @Autowired
    private RedisWatchdogLock redisWatchdogLock;
    
    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        String lockKey = parseKey(redisLock.key(), joinPoint);
        String fullKey = redisLock.prefix() + lockKey;
        
        boolean locked = false;
        try {
            locked = redisWatchdogLock.tryLock(fullKey, redisLock.waitTime(), redisLock.timeUnit());
            if (!locked) {
                throw new RuntimeException(redisLock.message());
            }
            
            return joinPoint.proceed();
        } finally {
            if (locked) {
                redisWatchdogLock.unlock(fullKey);
            }
        }
    }
    
    private String parseKey(String key, ProceedingJoinPoint joinPoint) {
        if (!key.contains("#")) {
            return key;
        }
        
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String[] parameterNames = signature.getParameterNames();
            Object[] args = joinPoint.getArgs();
            
            ExpressionParser parser = new SpelExpressionParser();
            StandardEvaluationContext context = new StandardEvaluationContext();
            
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            
            Expression expression = parser.parseExpression(key);
            return expression.getValue(context, String.class);
        } catch (Exception e) {
            log.error("Parse lock key error: {}", key, e);
            return key;
        }
    }
}

4.3 使用示例

4.3.1 业务服务类
java 复制代码
package com.example.lock.service;

import com.example.lock.annotation.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class OrderService {
    
    /**
     * 注解方式使用分布式锁
     */
    @RedisLock(key = "'order:' + #orderId", waitTime = 10, timeUnit = TimeUnit.SECONDS)
    public void processOrder(Long orderId) {
        try {
            log.info("开始处理订单: {}", orderId);
            // 模拟耗时操作
            Thread.sleep(15000);
            log.info("订单处理完成: {}", orderId);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 编程方式使用分布式锁
     */
    public void processOrderManual(Long orderId) {
        String lockKey = "order:" + orderId;
        
        try {
            boolean locked = redisWatchdogLock.tryLock(lockKey, 10, TimeUnit.SECONDS);
            if (!locked) {
                throw new RuntimeException("获取订单锁失败");
            }
            
            try {
                log.info("开始处理订单: {}", orderId);
                // 模拟耗时操作
                Thread.sleep(15000);
                log.info("订单处理完成: {}", orderId);
            } finally {
                redisWatchdogLock.unlock(lockKey);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }
}
4.3.2 控制器
java 复制代码
package com.example.lock.controller;

import com.example.lock.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/order")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @PostMapping("/process/{orderId}")
    public String processOrder(@PathVariable Long orderId) {
        orderService.processOrder(orderId);
        return "success";
    }
    
    @PostMapping("/process/manual/{orderId}")
    public String processOrderManual(@PathVariable Long orderId) {
        orderService.processOrderManual(orderId);
        return "success";
    }
}

五、测试与验证

5.1 单元测试

java 复制代码
package com.example.lock;

import com.example.lock.service.RedisWatchdogLock;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@SpringBootTest
class RedisWatchdogLockTest {
    
    @Autowired
    private RedisWatchdogLock redisWatchdogLock;
    
    private int counter = 0;
    
    @Test
    void testReentrantLock() throws InterruptedException {
        String lockKey = "test:reentrant";
        
        // 第一次获取锁
        boolean firstLock = redisWatchdogLock.tryLock(lockKey, 5, TimeUnit.SECONDS);
        assert firstLock;
        
        // 第二次获取(重入)
        boolean secondLock = redisWatchdogLock.tryLock(lockKey, 5, TimeUnit.SECONDS);
        assert secondLock;
        
        // 释放一次
        redisWatchdogLock.unlock(lockKey);
        
        // 再释放一次
        boolean result = redisWatchdogLock.unlock(lockKey);
        assert result;
    }
    
    @Test
    void testConcurrentAccess() throws InterruptedException {
        String lockKey = "test:concurrent";
        int threadCount = 5;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            executor.submit(() -> {
                try {
                    if (redisWatchdogLock.tryLock(lockKey, 2, TimeUnit.SECONDS)) {
                        try {
                            // 临界区操作
                            int current = counter;
                            Thread.sleep(100);
                            counter = current + 1;
                        } finally {
                            redisWatchdogLock.unlock(lockKey);
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await();
        executor.shutdown();
        
        // 验证最终结果
        assert counter == 1 : "并发测试失败,counter=" + counter;
    }
    
    @Test
    void testWatchdogRenewal() throws InterruptedException {
        String lockKey = "test:watchdog";
        
        boolean locked = redisWatchdogLock.tryLock(lockKey, 5, TimeUnit.SECONDS);
        assert locked;
        
        // 持有锁超过初始租期,验证看门狗是否续期
        Thread.sleep(40000); // 40秒
        
        // 尝试在另一个线程获取锁(应该失败)
        Thread otherThread = new Thread(() -> {
            boolean otherLock = redisWatchdogLock.tryLock(lockKey);
            assert !otherLock : "锁应该还在被持有";
        });
        
        otherThread.start();
        otherThread.join();
        
        // 释放锁
        redisWatchdogLock.unlock(lockKey);
    }
}

5.2 集成测试

bash 复制代码
# 启动Redis服务
docker run -d --name redis-test -p 6379:6379 redis:alpine

# 启动Spring Boot应用
./mvnw spring-boot:run

# 测试并发请求
# 使用ab或wrk进行压力测试
wrk -t4 -c100 -d30s http://localhost:8080/order/process/123

六、性能优化与监控

6.1 性能优化建议

  1. 合理设置过期时间:根据业务平均耗时调整leaseTime
  2. 调整检查间隔:根据业务波动性调整checkInterval
  3. 避免锁粒度太细:减少锁竞争和续期开销
  4. 使用连接池:确保Redis连接高效复用

6.2 监控指标

java 复制代码
package com.example.lock.monitor;

import com.example.lock.service.RedisWatchdogLock;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicInteger;

@Configuration
public class LockMonitor {
    
    @Autowired
    private RedisWatchdogLock redisWatchdogLock;
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    private final AtomicInteger activeLocks = new AtomicInteger(0);
    
    @PostConstruct
    public void init() {
        Gauge.builder("redis.lock.active.count", activeLocks, AtomicInteger::get)
            .description("当前活跃的分布式锁数量")
            .register(meterRegistry);
    }
}

6.3 告警配置

yaml 复制代码
# Prometheus告警规则
groups:
  - name: redis_lock_alerts
    rules:
      - alert: RedisLockRenewFailure
        expr: increase(redis_lock_renew_failure_total[5m]) > 10
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Redis锁续期失败"
          description: "锁 {{ $labels.lock_key }} 续期失败次数超过阈值"
      
      - alert: RedisLockHoldTooLong
        expr: redis_lock_hold_duration_seconds > 300
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Redis锁持有时间过长"
          description: "锁 {{ $labels.lock_key }} 已持有超过5分钟"

七、常见问题与解决方案

7.1 问题排查表

问题现象 可能原因 解决方案
获取锁超时 锁竞争激烈或业务持有时间过长 1. 增加waitTime 2. 优化业务逻辑 3. 减小锁粒度
看门狗未续期 看门狗线程异常终止 1. 检查线程池配置 2. 增加异常监控 3. 设置线程未捕获异常处理器
锁释放失败 网络异常或Redis故障 1. 实现重试机制 2. 设置锁的最大持有时间 3. 添加强制释放接口
内存泄漏 ThreadLocal未清理 1. 确保finally块中清理 2. 使用拦截器统一清理

7.2 Redis集群环境

在Redis集群环境下,需要考虑以下调整:

java 复制代码
// 配置Redisson客户端支持集群
@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useClusterServers()
        .addNodeAddress("redis://127.0.0.1:7001", "redis://127.0.0.1:7002")
        .setPassword("password")
        .setScanInterval(2000);
    return Redisson.create(config);
}

八、个人总结

8.1 优点

  1. 自动续期:解决业务执行时间不确定性问题
  2. 高可靠性:Lua脚本保证原子性,避免竞争条件
  3. 可重入性:支持同一线程多次获取
  4. 易于集成:提供注解和编程两种使用方式
  5. 可监控:完善的监控和告警机制

8.2 缺点

  1. 实现复杂度高:需要维护看门狗线程和锁状态
  2. 资源消耗:每个锁需要额外线程进行续期
  3. 网络依赖:完全依赖Redis可用性
  4. 时钟同步问题:分布式环境下需要保证时钟同步

通过以上我公司的实现,您可以获得一个生产级别的分布式锁解决方案,能够满足大多数业务场景的需求。

相关推荐
一念一花一世界8 小时前
Arbess从基础到实践(20) - 集成GitHub+SonarQube实现Java项目自动化部署
java·github·cicd·arbess
为什么要做囚徒8 小时前
并发系列(一):深入理解信号量(含 Redis 分布式信号量)
redis·分布式·多线程·并发编程·信号量
艾莉丝努力练剑8 小时前
【Python库和代码案例:第一课】Python 标准库与第三方库实战指南:从日期处理到 Excel 操作
java·服务器·开发语言·人工智能·python·pycharm·pip
YDS8298 小时前
SpringCloud —— 配置管理
java·spring·spring cloud
乂爻yiyao8 小时前
Java 的云原生困局与破局
java·开发语言·云原生
C182981825758 小时前
traceId 传递-MQ
java
小鸡脚来咯8 小时前
java web后端开发流程
java·开发语言·git
BullSmall8 小时前
Kafka 安全加固实践指南(可直接落地)
分布式·安全·kafka
北友舰长8 小时前
基于Springboot+thymeleaf快递管理系统的设计与实现【Java毕业设计·安装调试·代码讲解】
java·spring boot·mysql·校园管理·快递·快递系统