分布式锁的必要性
在分布式集群系统中,由于分布式系统的多线程、多进程分布在不同的机器上,为了在这种情况下解决并发控制而引入了分布式锁技术。通过分布式锁可以在跨JVM的互斥控制机制来处理共享资源的多线程、多进程访问问题。
分布式锁应该具备的条件
- 在分布式系统环境下,同一个时间点一个方法只能被一个机器的一个线程执行。
2)高性能的获取锁和释放锁。
3)高可用的获取锁和释放锁。
4)具有锁失效机制,防止死锁。 - 具备可重入特性。
6)具有非阻塞锁特性,即没有获取到锁立即返回获取锁失败。
分布式锁的三种实现
目前几乎所有的大型网站或应用都是分布式进行部署的,分布式场景中数据一致性问题一直是一个比较重要的话题。分布式CAP理论告诉我们,任何一个分布式系统都不可能同时满足一致性(Consistency),可用性(Availability),分区容错性(Partition Tolerance),最多同时满足两项。在互联网环境下,一般会牺牲强一致性来换取系统的高可用性,往往系统只需要保证最终一致性,只要最终时间是用户可接受的时间范围内即可。
在很多场景下,为了达到最终一致性,会使用到技术分布式锁,分布式事务等技术。有的时候一个方法在一个时间范围内只能被一个线程执行。
基于数据库实现分布式锁
基于缓存(Redis等)实现分布式锁
基于Zookeeper实现分布式锁
1. 单机版分布式锁的实现
第一步:锁请求实体封装
@Data
public class LockParam {
private String key;
private String type; //0-只执行一次;1-按次数; 2-按时间
private long times; //当type为1或2时,此值为必传参数;为1是代表次数;为2时代表等待时长
private long lockExpireTime = 2 * 60 * 1000;
}
锁的执行方式枚举
@AllArgsConstructor
@Getter
public enum LockUseTypeEnum {
JUST_ONCE("0", "只获取一次锁"), BY_FREQUENCY("1", "按次数获取锁"), BY_TIME("2", "按时间获取锁");
private String code;
private String name;
}
第二步:锁的具体实现
/**
* 分布式锁的实现
*/
public class DistributedLock {
/** 分布式锁前缀 **/
private static final String LOCK_PREFIX = "distribute_lock_";
private static final long DEFAULT_SLEEP_TIME = 100L;
public boolean lock(LockParam lockParam) {
if(LockUseTypeEnum.JUST_ONCE.getCode().equals(lockParam.getType()) || (!LockUseTypeEnum.JUST_ONCE.getCode().equals(lockParam.getType()) && lockParam.getTimes() <=0L)){
return getNewLock(lockParam.getKey(), lockParam.getLockExpireTime());
}else if(LockUseTypeEnum.BY_FREQUENCY.getCode().equals(lockParam.getType())){
do{
if(getNewLock(lockParam.getKey(), lockParam.getLockExpireTime())){
return true;
}
lockParam.setTimes(lockParam.getTimes()-1);
try{
Thread.sleep(DEFAULT_SLEEP_TIME);
}catch (InterruptedException e){
return false;
}
}while(lockParam.getTimes() > 0);
}else {
do{
if(getNewLock(lockParam.getKey(), lockParam.getLockExpireTime())){
return true;
}
long thisSleepTime = 0L;
if(lockParam.getTimes()<100L){
thisSleepTime = lockParam.getTimes();
}else{
thisSleepTime = NumberUtils.getRandom(50, 200);
}
lockParam.setTimes(lockParam.getTimes() - thisSleepTime);
try{
Thread.sleep(thisSleepTime);
}catch (InterruptedException e){
return false;
}
}while(lockParam.getTimes()>0L);
}
return false;
}
private boolean getNewLock(String key, long lockExpireTime){
RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
String lockKey = LOCK_PREFIX + key;
long expires = System.currentTimeMillis() + lockExpireTime + 1;
String expiresStr = String.valueOf(expires); // 锁到期时间
if(redisUtil.setNX(lockKey, expiresStr)){
return true;
}
String currentValueStr = redisUtil.getNX(lockKey); //this.get(lockKey); // redis里的时间
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
String oldValueStr = redisUtil.getSet(lockKey, expiresStr);
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
return true;
}
}
return false;
}
public void unlock(String key){
RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
String lockKey = LOCK_PREFIX + key;
redisUtil.delete(lockKey);
}
}
第三步:redis操作相关方法
public boolean setNX(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
StringRedisSerializer serializer = new StringRedisSerializer();
Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
connection.close();
return success;
});
} catch (Exception e) {
return false;
}
return obj != null ? (Boolean) obj : false;
}
public String getNX(final String key) {
Object obj = null;
try {
obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] data = connection.get(serializer.serialize(key));
connection.close();
if (data == null) {
return null;
}
return serializer.deserialize(data);
});
} catch (Exception e) {
return null;
}
return obj != null ? obj.toString() : null;
}
public String getSet(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
connection.close();
return serializer.deserialize(ret);
});
} catch (Exception e) {
return null;
}
return obj != null ? (String) obj : null;
}
参考:https://blog.csdn.net/weixin_38003389/article/details/89434629
参考:https://blog.csdn.net/wuzhiwei549/article/details/80692278
参考:https://blog.csdn.net/russle/article/details/103106238