Spring Boot 集成 Redis 实现看门狗 Lua 脚本分布式锁
公司实战项目分布式锁,偷偷给你们看,怎么使用的,好多人都不会也不懂
一、概述
1.1 什么是看门狗锁?
看门狗锁(Watchdog Lock) 是一种基于Redis实现的分布式锁优化方案,它在传统分布式锁的基础上增加了自动续期机制,防止业务执行时间超过锁过期时间而导致的锁提前释放问题。
1.2 基本原理
- 获取锁时:设置一个较短的初始过期时间(如30秒)
- 后台线程(看门狗) :定期检查锁是否仍被持有,如果是则自动续期
- Lua脚本保证原子性:所有关键操作使用Lua脚本,确保在Redis端的原子执行
- 锁释放:业务完成后手动释放,或线程结束时自动清理
1.3 核心优势 vs 传统分布式锁
| 特性 | 传统分布式锁 | 看门狗锁 |
|---|---|---|
| 过期时间 | 固定,需要预估业务时间 | 动态续期,自适应业务耗时 |
| 业务超时风险 | 高(业务超时导致锁失效) | 低(自动续期) |
| 实现复杂度 | 简单 | 中等 |
| 资源占用 | 低 | 需要额外线程维护 |
二、应用场景
2.1 适用场景
- 长时间任务:批处理、数据迁移等耗时不确定的操作
- 事务性操作:分布式事务中的资源锁定
- 定时任务防重:确保分布式环境下只有一个节点执行
- 秒杀/限流:高并发场景下的资源争用控制
2.2 不适用场景
- 极短时间锁(< 100ms):续期开销可能大于收益
- 对延迟极其敏感的场景:看门狗检查可能引入额外延迟
三、实现方案详述
3.1 架构设计
arduino
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 业务线程 │ │ 看门狗线程 │ │ Redis Server │
│ │ │ │ │ │
│ 1.获取锁 │───▶│ │───▶│ 执行Lua脚本 │
│ │ │ │ │ 设置锁+过期时间 │
│ 2.执行业务逻辑 │ │ 3.启动看门狗 │ │ │
│ │ │ 定期检查 │ │ │
│ │◀───│ 自动续期 │◀───│ 执行续期Lua │
│ 4.释放锁 │───▶│ │───▶│ 删除Key │
└─────────────────┘ └─────────────────┘ └─────────────────┘
3.2 关键特性
- 可重入性:同一线程可重复获取锁
- 自动续期:后台线程定期延长锁有效期
- 防死锁:设置最大续期次数,避免异常情况无限续期
- 异常恢复:客户端异常断开时,锁会自动过期
四、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 配置类
ini
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 注解支持
ini
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 控制器
kotlin
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 单元测试
ini
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 性能优化建议
- 合理设置过期时间:根据业务平均耗时调整leaseTime
- 调整检查间隔:根据业务波动性调整checkInterval
- 避免锁粒度太细:减少锁竞争和续期开销
- 使用连接池:确保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集群环境下,需要考虑以下调整:
arduino
// 配置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 优点
- 自动续期:解决业务执行时间不确定性问题
- 高可靠性:Lua脚本保证原子性,避免竞争条件
- 可重入性:支持同一线程多次获取
- 易于集成:提供注解和编程两种使用方式
- 可监控:完善的监控和告警机制
8.2 缺点
- 实现复杂度高:需要维护看门狗线程和锁状态
- 资源消耗:每个锁需要额外线程进行续期
- 网络依赖:完全依赖Redis可用性
- 时钟同步问题:分布式环境下需要保证时钟同步
8.3 最佳实践建议
- 锁命名规范 :使用业务前缀,如
order:lock:{orderId} - 设置合理的超时时间:根据业务99线耗时设置
- 实现降级策略:Redis不可用时使用本地锁
- 定期清理:实现锁的自动清理机制
- 文档化:记录锁的使用场景和注意事项
部署注意事项:
- 生产环境建议使用Redis Sentinel或Cluster保证高可用
- 建议设置合理的JVM参数,避免看门狗线程过多导致资源耗尽
- 定期检查锁的使用情况,优化锁粒度和持有时间
- 在容器化部署时,注意线程池的合理配置