1、适用于单节点的分布式锁
2、多节点的分布式锁可使用redlock等框架实现
分布式锁需要解决如下几个问题
问题1:获取锁的唯一性(多个线程不能同时获取一个锁)
问题2:锁要有过期时间(一个线程获取锁后宕机,导致其它线程都获取不到锁)
问题3:释放锁时,只能释放自己(本线程)创建的锁
key: 锁名,业务唯一
value: 使用线程id + 时间戳
1、redis的SETNX是key不存在时,才能set成功【问题1】
2、锁需要添加过期时间,并且获取锁和设置过期时间要保证原子性,这里使用Lua脚本(Lua脚本在redis执行是原子性的)执行【问题2】
/**
* 获取锁保证原子性
*/
private static final String SET_KEY_LUA = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
" return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
/**
* 获取锁
* @param expire 锁过期时间,单位秒
* @return
*/
public Boolean lock(Integer expire) {
if (expire == null || expire <= 0) {
expire = DEFAULT_EXPIRE;
}
RedisScript<Long> redisScript = new DefaultRedisScript(SET_KEY_LUA, Long.class);
Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), value, String.valueOf(expire));
return SUCCESS.equals(result);
}
解锁时检查value值是否和当前线程的value一致,一致才能解锁【问题3】
/**
* 释放锁保证原子性
*/
private static final String DELETE_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del', KEYS[1])" +
" else" +
" return 0" +
" end";
/**
* 解锁
*/
public void unlock() {
RedisScript<Long> redisScript = new DefaultRedisScript(DELETE_LUA, Long.class);
redisTemplate.execute(redisScript, Arrays.asList(key), value);
}
Lock.java
import lombok.Data;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import java.util.Arrays;
/**
* 锁
*/
@Data
public class Lock {
private String key;
private String value;
private RedisTemplate redisTemplate;
/**
* 获取锁保证原子性
*/
private static final String SET_KEY_LUA = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
" return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
/**
* 释放锁保证原子性
*/
private static final String DELETE_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del', KEYS[1])" +
" else" +
" return 0" +
" end";
private static final Long SUCCESS = 1L;
// 默认过期时间5分钟
private static final Integer DEFAULT_EXPIRE = 5 * 60;
public Lock(String key, RedisTemplate redisTemplate) {
this.key = key;
this.value = Thread.currentThread().getName() + "-" + System.currentTimeMillis();
this.redisTemplate = redisTemplate;
}
/**
* 获取锁,默认60秒过期
* @return
*/
public Boolean lock() {
return this.lock(DEFAULT_EXPIRE);
}
/**
* 获取锁
* @param expire 锁过期时间,单位秒
* @return
*/
public Boolean lock(Integer expire) {
if (expire == null || expire <= 0) {
expire = DEFAULT_EXPIRE;
}
RedisScript<Long> redisScript = new DefaultRedisScript(SET_KEY_LUA, Long.class);
Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), value, String.valueOf(expire));
return SUCCESS.equals(result);
}
/**
* 解锁
*/
public void unlock() {
RedisScript<Long> redisScript = new DefaultRedisScript(DELETE_LUA, Long.class);
redisTemplate.execute(redisScript, Arrays.asList(key), value);
}
}
LockService.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class LockServiceImpl implements LockService {
@Autowired
private RedisTemplate redisTemplate;
@Override
public Lock getLock(String key) {
return new Lock(key, redisTemplate);
}
}
使用
String key = "xxx";
Integer expire = 5;
Lock lock = lockService.getLock(key);
if (!lock.lock(expire)) {
log.warn("未获取到分布式锁, key: {}", key);
return null;
}
log.info("获取到分布式锁, key: {}", key);
try {
... ...
} finally {
lock.unlock();
log.info("分布式锁已释放, key: {}", key);
}