redis实现分布式锁,实现了Lock接口,和ReentrantLock意向,有可重入,阻塞等功能
依赖
org.springframework.boot
spring-boot-starter-data-redis
redis.clients
jedis
io.lettuce
lettuce-core
redis.clients
jedis
org.springframework.integration
spring-integration-redis
配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory ) {
//设置序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory);
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer); // key序列化
redisTemplate.setValueSerializer(stringSerializer); // value序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
redisTemplate.setHashValueSerializer(stringSerializer); // Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public RedisLockRegistry redisLockRegistry(JedisConnectionFactory jedisConnectionFactory) {
return new RedisLockRegistry(jedisConnectionFactory, "REDIS_LOCK");
}
}
举例
@RestController
public class RedisController {
@Autowired
private RedisLockRegistry redisLockRegistry;
@Autowired
private UserService userService;
//http://localhost:9000/redisLock?id=1
@RequestMapping("/redisLock")
public String redisLock(Integer id){
//redis的key冒号:连接
//registryKey和lockKey自动冒号连接,最终key为REDIS_LOCK:USER_ID:1,值为uuid
Lock lock = redisLockRegistry.obtain("USER_ID:" + id);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin " + new Date());
userService.update();
System.out.println(Thread.currentThread().getName() + " end " + new Date());
lock.unlock();
}).start();
}
return "ok";
}
}
Thread-14 begin Fri Jul 19 17:04:30 CST 2019
Thread-14 end Fri Jul 19 17:04:31 CST 2019
Thread-15 begin Fri Jul 19 17:04:31 CST 2019
Thread-15 end Fri Jul 19 17:04:32 CST 2019
Thread-16 begin Fri Jul 19 17:04:32 CST 2019
Thread-16 end Fri Jul 19 17:04:33 CST 2019
ExpirableLockRegistry接口,添加一个过期释放锁的方法
public interface ExpirableLockRegistry extends LockRegistry {
/**
* Remove locks last acquired more than 'age' ago that are not currently locked.
* @param age the time since the lock was last obtained.
* @throws IllegalStateException if the registry configuration does not support this feature.
*/
void expireUnusedOlderThan(long age);
}
LockRegistry接口,只有一个获取锁的方法
@FunctionalInterface
public interface LockRegistry {
/**
* Obtains the lock associated with the parameter object.
* @param lockKey The object with which the lock is associated.
* @return The associated lock.
*/
Lock obtain(Object lockKey);
}
RedisLockRegistry构造器
private static final long DEFAULT_EXPIRE_AFTER = 60000L;
private final String registryKey;
private final StringRedisTemplate redisTemplate;
private final RedisScript obtainLockScript;
private final long expireAfter;
private static final String OBTAIN_LOCK_SCRIPT =
"local lockClientId = redis.call('GET', KEYS[1])\n" +
"if lockClientId == ARGV[1] then\n" +
" redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
" return true\n" +
"elseif not lockClientId then\n" +
" redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
" return true\n" +
"end\n" +
"return false";
/**
* Constructs a lock registry with the default (60 second) lock expiration.
* @param connectionFactory The connection factory.
* @param registryKey The key prefix for locks.
*/
public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey) {
this(connectionFactory, registryKey, DEFAULT_EXPIRE_AFTER);
}
/**
* Constructs a lock registry with the supplied lock expiration.
* @param connectionFactory The connection factory.
* @param registryKey The key prefix for locks.
* @param expireAfter The expiration in milliseconds.
*/
public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey, long expireAfter) {
Assert.notNull(connectionFactory, "'connectionFactory' cannot be null");
Assert.notNull(registryKey, "'registryKey' cannot be null");
this.redisTemplate = new StringRedisTemplate(connectionFactory);
this.obtainLockScript = new DefaultRedisScript<>(OBTAIN_LOCK_SCRIPT, Boolean.class);
this.registryKey = registryKey;
this.expireAfter = expireAfter;
}
private final Map locks = new ConcurrentHashMap<>();
@Override
public Lock obtain(Object lockKey) {
Assert.isInstanceOf(String.class, lockKey);
String path = (String) lockKey;
return this.locks.computeIfAbsent(path, RedisLock::new);
}
Map
default V computeIfAbsent(K key,
Function super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
每个lockKey创建一个锁,缓存起来
computeIfAbsent和putIfAbsent的区别是,前者是一个函数式接口,创建对象,作为缓存的值,后者是直接传进来值
@Override
public void lock() {
this.localLock.lock();
while (true) {
try {
while (!obtainLock()) {
Thread.sleep(100); //NOSONAR
}
break;
}
catch (InterruptedException e) {
/*
* This method must be uninterruptible so catch and ignore
* interrupts and only break out of the while loop when
* we get the lock.
*/
}
catch (Exception e) {
this.localLock.unlock();
rethrowAsLockException(e);
}
}
}
private final String clientId = UUID.randomUUID().toString();
private boolean obtainLock() {
boolean success = RedisLockRegistry.this.redisTemplate.execute(RedisLockRegistry.this.obtainLockScript,
Collections.singletonList(this.lockKey), RedisLockRegistry.this.clientId,
String.valueOf(RedisLockRegistry.this.expireAfter));
if (success) {
this.lockedAt = System.currentTimeMillis();
}
return success;
}
先用ReentrantLock加锁,再用redis调用lua脚本,
private static final String OBTAIN_LOCK_SCRIPT =
"local lockClientId = redis.call('GET', KEYS[1])\n" +
"if lockClientId == ARGV[1] then\n" +
" redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
" return true\n" +
"elseif not lockClientId then\n" +
" redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
" return true\n" +
"end\n" +
"return false";
如果lockKey没有值,设置值,过期时间60秒。否则是线程重入锁,刷新过期时间60秒
redis加锁成功后,每个线程保存加锁时间
如果加锁失败,100毫秒重试,一直循环到获取锁,所以锁是可重入的。
RedisLockRegistry.RedisLock
@Override
public void unlock() {
if (!this.localLock.isHeldByCurrentThread()) {
throw new IllegalStateException("You do not own lock at " + this.lockKey);
}
if (this.localLock.getHoldCount() > 1) {
this.localLock.unlock();
return;
}
try {
if (Thread.currentThread().isInterrupted()) {
RedisLockRegistry.this.executor.execute(this::removeLockKey);
}
else {
removeLockKey();
}
if (logger.isDebugEnabled()) {
logger.debug("Released lock; " + this);
}
}
catch (Exception e) {
ReflectionUtils.rethrowRuntimeException(e);
}
finally {
this.localLock.unlock();
}
}
private void removeLockKey() {
if (RedisUtils.isUnlinkAvailable(RedisLockRegistry.this.redisTemplate)) {
RedisLockRegistry.this.redisTemplate.unlink(this.lockKey);
}
else {
RedisLockRegistry.this.redisTemplate.delete(this.lockKey);
}
}
ReentrantLock保存了上锁的线程,和线程的重入次数
如果是重入锁,计数器减一,即aqs的state减一
否则redis删除key,然后释放ReentrantLock锁。