Redis 系列 -- SpringBoot中 基于 Redis 实现分布式锁
分布式锁应用场景
锁的应用常出现在一些多线程、高并发的场景中,如秒杀、抢购、12306抢票,还有一些商品库存管理等。在一般的单体应用中,我们通常使用 synchronized、ReentrantLock 等来进行共享变量的管理,从而实现线程锁功能。但随着互联网发展、用户的剧增,单体应用已无法满足需求,多服务器部署、分布式应用越来越被广泛使用。但问题也出现了,对于之前的基于本地变量锁的形式,在不同服务器间无法共享。所以分布式锁应运而生,如基于中间件 redis 、zookeeper等实现的分布式锁。
为什么使用redis 分布式锁
1、redis 读写快
与mysql等传统数据库进行内存、磁盘I\O 流操作过程相比,redis 基于内存操作和I\O多路复用具有更快的读写效率。
2、redis 单线程 操作简单
基于单线程设计,所以redis在处理一些并发问题上具有天生的优势。
使用命令
SETNX命令
SETNX key value
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
GETSET命令
GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。
GET命令
GET key
返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。
DEL命令
DEL key
删除给定的一个或多个 key ,不存在的 key 会被忽略。
SpringBoot 中 代码实现
redis锁对象
package com.tcwong.redis.util;
import java.util.concurrent.TimeUnit;
/**
* redis锁
*/
public interface RedisLock {
/**
* 获取锁
* [@param] tryMillSecondsTime 尝试时间 秒
* [@return]
*/
boolean tryLock(long tryMillSecondsTime);
/**
* 获取锁
* [@param] tryTime 尝试时间
* [@param] timeUnit 时间类型
* [@return]
*/
boolean tryLock(long tryTime, TimeUnit timeUnit);
/**
* 释放锁
*/
void unLock();
/**
* 重制锁
*/
void reInit();
}
package com.tcwong.redis.util;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;
/**
* redis锁
*/
public class RedisLockImpl implements RedisLock {
private RedisTemplate redisTemplate;
private String key;
private long expireTime;
private long tryTime;
private long maxTryTime;
private ValueOperations valueOperations;
public RedisLockImpl(RedisTemplate redisTemplate, String key, Long expireTime, Long maxTryTime) {
this.redisTemplate = redisTemplate;
this.key = key;
this.expireTime = expireTime;
this.maxTryTime = maxTryTime;
this.valueOperations = redisTemplate.opsForValue();
}
/**
* 获取锁
* @return
*/
private boolean tryLock() {
while (this.tryTime >= 0) {
Long expires = System.currentTimeMillis() + this.expireTime ;
String expiresValue = String.valueOf(expires);
Boolean ifAbsentFlag = this.valueOperations.setIfAbsent(this.key, expiresValue);
if (ifAbsentFlag) {
redisTemplate.expire(this.key, this.expireTime, TimeUnit.MILLISECONDS);
return true;
}
String lastTimeValue = (String)this.valueOperations.get(this.key);
if (lastTimeValue != null && Long.valueOf(lastTimeValue) < System.currentTimeMillis()) {
String oldTimeValue = (String) this.valueOperations.getAndSet(this.key, expiresValue);
this.redisTemplate.expire(this.key, this.expireTime, TimeUnit.MILLISECONDS);
if (oldTimeValue != null && oldTimeValue.equals(lastTimeValue)) {
return true;
}
}
this.tryTime -= 100;
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
}
}
return false;
}
/**
* 获取锁
* @param tryMillSecondsTime 尝试时长
* @return
*/
@Override
public boolean tryLock(long tryMillSecondsTime) {
this.tryTime = tryMillSecondsTime > this.maxTryTime ? maxTryTime : tryMillSecondsTime;
return this.tryLock();
}
/**
* 获取锁
* @param tryTime 尝试时间
* @param timeUnit 时间类型
* @return
*/
@Override
public boolean tryLock(long tryTime, TimeUnit timeUnit) {
return this.tryLock(timeUnit.toMillis(tryTime));
}
/**
* 释放锁
*/
@Override
public void unLock() {
this.redisTemplate.delete(this.key);
}
/**
* 重制锁
*/
@Override
public void reInit() {
this.redisTemplate.expire(this.key, 0 , TimeUnit.MILLISECONDS);
}
}
获取redis 锁
package com.tcwong.redis.util;
import java.util.concurrent.TimeUnit;
/**
* 获取锁对象
*/
public interface RedisLockFactory {
/**
* 获取锁
* @param key redis锁key
* @return
*/
RedisLock getLock(String key);
/**
* 获取锁
* @param key redis锁key
* @param tryTime 尝试时间
* @param timeUnit 时间类型
* @return
*/
RedisLock getLock(String key, int tryTime, TimeUnit timeUnit);
}
package com.tcwong.redis.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 获取锁对象
*/
@Component
public class RedisLockFactoryImpl implements RedisLockFactory {
@Autowired
private RedisTemplate redisTemplate;
private long expireTime = 0 ;
private long maxTryTime = 1000 ;
private static final String REDIS_KEY_PRE = "REDIS_KEY_PRE";
/**
* 获取锁
* @param key redis锁key
* @return
*/
@Override
public RedisLock getLock(String key) {
return new RedisLockImpl(this.redisTemplate,REDIS_KEY_PRE + key,this.expireTime,this.maxTryTime);
}
/**
* 获取锁
* @param key redis锁key
* @param tryTime 尝试时间
* @param timeUnit 时间类型
* @return
*/
@Override
public RedisLock getLock(String key, int tryTime, TimeUnit timeUnit) {
return new RedisLockImpl(this.redisTemplate, REDIS_KEY_PRE + key,timeUnit.toMillis(tryTime),this.maxTryTime);
}
}
《 完 》
谢谢大家持续关注TC谈
你们的每一个交流和【在看】都是TC持续更新的动力