基于RedisTemplate和线程池实现Redis分布式锁
创作时间:
作者:
@小白创作中心
基于RedisTemplate和线程池实现Redis分布式锁
引用
1
来源
1.
https://www.cnblogs.com/1399z3blog/p/18290473
在分布式系统中,多个服务实例可能同时访问共享资源,因此需要分布式锁来保证数据的一致性和完整性。本文将详细介绍如何使用Redis和线程池实现一个可靠的分布式锁方案。
分布式锁需求
随着系统规模的扩大,后台服务往往不再只是单机部署,而是通过集群的方式运行在多个服务器上。在这种架构下,用户请求会被负载均衡器分发到不同的服务器。如果需要对集群中的某个代码片段进行加锁,传统的synchronized
关键字就无法满足需求了,因为synchronized
只能控制同一JVM进程内的锁资源。
分布式锁的实现方式有多种,如Redis分布式锁、Zookeeper分布式锁等。本文将重点介绍基于Redis的分布式锁实现方案。
Redis为什么能实现分布式锁?
Redis是一个基于键值对存储的NoSQL数据库,非常适合用来实现分布式锁。作为“锁”,需要保证在某一时刻只有一个线程能够执行特定代码片段。而Redis的主线程模型是单线程的,这意味着在同一时刻只有一个线程在执行Redis数据相关操作。
这种特性使得在Redis中存入锁数据后,其他服务器的线程能够立即获取到锁的状态,从而实现对集群中指定代码片段的加锁。
如何实现Redis分布式锁?
前置知识
- Redis的
set
和get
命令 setnx
命令:如果键不存在,则设置键值对- RedisTemplate的
setIfPresent
方法对应setnx
命令
实现步骤
- 在第一个线程访问时,在Redis中添加一项缓存数据作为锁资源
- 每个线程在执行该片段开始时,执行
setnx
命令进行缓存锁资源更新 - 如果更新失败(返回false),说明已有线程正在执行该片段,可以选择阻塞线程或给用户反馈提示
- 在线程结束时,需要主动删除该锁资源
try{
// 获取分布式锁
Boolean lock = redisTemplate.opsForValue().setIfPresent("lock", "resource");
// 如果锁资源未正常更新,则返回提示
if(!lock){
return "系统繁忙";
}
// 如果正常更新,则进行业务逻辑代码
// todo 业务逻辑
}finally {
// 执行完成后,删除锁
redisTemplate.delete("lock");
}
处理服务挂掉的情况
如果在执行业务逻辑时服务挂掉,锁资源将无法被删除,导致所有后续请求都被阻塞。解决方案是在设置锁时添加过期时间:
try{
// 获取分布式锁
Boolean lock = redisTemplate.opsForValue().setIfPresent("lock", "resource", 10, TimeUnit.SECONDS);
// 如果锁资源未正常更新,则返回提示
if(!lock){
return "系统繁忙";
}
// 如果正常更新,则进行业务逻辑代码
// todo 业务逻辑
}finally {
// 执行完成后,删除锁
redisTemplate.delete("lock");
}
处理运行时间超过过期时间的情况
如果业务逻辑执行时间超过锁的过期时间,会导致并发执行和锁资源意外释放的问题。解决方案包括:
- 创建子线程对过期时间进行续命
- 为每个线程创建唯一标识,在删除锁时进行校验
String clientID = UUID.randomUUID().toString();
try{
// 获取分布式锁
Boolean lock = redisTemplate.opsForValue().setIfPresent("lock", clientID, 10, TimeUnit.SECONDS);
// 如果锁资源未正常更新,则返回提示
if(!lock){
return "系统繁忙";
}
// 创建线程续命
new Thread(new Runnable() {
@Override
public void run() {
// 对 redis的锁过期时间进行续命
}
}).start();
// 如果正常更新,则进行业务逻辑代码
// todo 业务逻辑
}finally {
// 执行完成后,判断为自己创建的锁,则删除锁
if(clientID.equals(redisTemplate.opsForValue().get("lock"))){
redisTemplate.delete("lock");
}
}
优化方案
在实际开发中,建议使用ScheduledThreadPoolExecutor
来管理线程:
@Resource
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
ScheduledFuture<?> addLockLifeThread = null;
try{
String clientId = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfPresent(LOCK_KEY, clientId, LOCK_TTL, TimeUnit.SECONDS);
if (lock == null || !lock) {
return false;
}
addLockLifeThread = scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
lengthenLockLife(clientId);
}, ADD_LOCK_TTL, ADD_LOCK_TTL, TimeUnit.SECONDS);
// todo 完成需要进行加锁的业务逻辑
} catch (Exception e){
log.info("执行出错:{}", e.getMessage());
}finally{
if(addLockLifeThread != null){
addLockLifeThread.cancel(true);
}
redisTemplate.delete(LOCK_KEY);
}
public void lengthenLockLife(String clientId) {
String redisLock = redisTemplate.opsForValue().get(LOCK_KEY);
if (clientId.equals(redisLock)) {
redisTemplate.expire(LOCK_KEY, LOCK_TTL, TimeUnit.SECONDS);
log.info("线程id {},进行续命", clientId);
}
}
Redis分布式锁工具类
为了方便使用,可以封装一个Redis分布式锁工具类:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
return redisTemplate;
}
}
@Component
public class RedisUtil {
public static final String LOCK_PREFIX = "redis_lock_";
private static final Long SUCCESS = 1L;
public static final int LOCK_EXPIRE = 60 * 10;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean lock(String key) {
String lock = LOCK_PREFIX + key;
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = connection.get(lock.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
if (expireTime < System.currentTimeMillis()) {
byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}
public boolean getLock(String lockKey, String value, Integer expireTime){
if(StringUtils.isTrimBlank(expireTime)){
expireTime = LOCK_EXPIRE;
}
try{
String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
Object result = redisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX+lockKey),value,String.valueOf(expireTime));
if(SUCCESS.equals(result)){
return true;
}
}catch(Exception e){
return false;
}
return false;
}
public boolean releaseLock(String lockKey, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
Object result = redisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX+lockKey), value);
if (SUCCESS.equals(result)) {
return true;
}
return false;
}
public void delete(String key) {
redisTemplate.delete(LOCK_PREFIX+key);
}
}
通过以上方案,可以实现一个健壮且灵活的Redis分布式锁机制,适用于各种需要保证数据一致性的场景。
热门推荐
天龙八部慕容技能介绍
法币信用危机与哈密尔顿的财政改革
中东的语言和语言多样性
两部门联合发布反家庭暴力犯罪典型案例——用法律武器对家庭暴力说“不”
简历要写求职意向吗?
什么是昆仑玉?如何区分昆仑玉和和田碧玉?
国企会辞退正式员工吗,答案超出你的想象!
阻抗、电抗、容抗与感抗详解
在人工智能浪潮中,教育应如何应对和变革?
清单待办:提升效率的最佳工具和方法
【以案释法】驾考作弊获真“刑”
毕业生薪酬曝光,这才是90%的应届生工资真相……
维生素E含量比一比!高含量食物大公开
网友接力二创重构“哪吒”IP宇宙 专家:多元化创造丰盈原作血肉
孩子教育中的跨学科学习:如何培养他们的综合素质与创新能力
中科大夏令营录取条件全解析
房地产市场走势怎么看——当前中国经济问答之六
超豪华的金球奖历史最佳十一人
怎么确保安全风险管控制度符合最新的法规要求?
脑部磁共振检查结果多久出来
建筑电气难题:TN-C-S 系统 PEN 线分离,先接中性线还是 PE 母排?
喝电解质水有好处吗
头部伤挂什么科室
赵长鹏因币安合规失误及非法交易被美国司法机构判刑
北京春节活动持续至正月十五:游园会、非遗演出、博物馆展览精彩纷呈
英国“王曼爱华”指的是哪几所高校?中英双语介绍
年终绩效奖金和年终奖的区别
我国钢结构行业现状及前景分析:加工量稳升 政策规划明确下发展潜力十足
左旋肉碱真的能减肥吗?真相令人震惊!
如何辨别消防设施操作员证书真伪?四种实用方法全解析