基于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分布式锁机制,适用于各种需要保证数据一致性的场景。
热门推荐
北京大学航天中心医院:特色科室优势明显,高薪岗位前景可期
揭秘《哪吒之魔童闹海》陈塘关大战:饺子导演如何挑战极限?
春节焦虑症:如何让团圆不再成为负担?
春节焦虑症:如何轻松应对,享受团圆时光
秋冬季节,女性如何预防干燥综合征?
苹果什么时间吃比较好?晚上吃苹果真的是“毒”吗?
“秋吃果,药不抓”,明日立秋,提醒多吃5样“秋果”,安稳入秋
如何优雅应对干燥综合征?
干燥综合征:最新研究进展与应对策略
周末山东自驾游去哪好玩
去五台山的最佳时间:探寻佛国胜地的四季之美
中国最值得去的九大自然保护区
房颤个性化治疗方案:如何与医生密切合作制定最佳治疗计划
劳动法新动向:企业如何守护员工心理防线?
成都蓉城发布2024成绩单:组织球迷远征19481人次,消费近1亿元
重庆解放碑:网红打卡地新玩法!
如何应对恶意投诉与诬告
法外狂徒张三经典案例:解析其违法犯罪行为及社会影响
钓鱼城:一座改变世界历史的军事要塞
梅州美食攻略:从早餐到夜宵,吃遍客家特色美味
硬控24+hours!来南沙,看这篇就够了
女神节打卡:周庄古镇最美瞬间
南宁旅游适合玩几天? 南宁玩几天比较好?
赏美景,品文化,一次玩够!跟着非遗游南宁,4条特色旅游线路邀您开启非遗之旅
《哪吒重生》:经典故事的新时代演绎
麻仁丸是治疗慢性便秘的常用中成药,能长期服用吗?有副作用吗?
《黑神话:悟空》如何通过营销策略吸引全球玩家
武隆天生三桥:世界自然遗产的地质奇观
2025年五一东江湖特色活动全攻略:非遗表演、汉服体验应有尽有
东江湖:体验仙境般的休闲之旅