RedisLockRegistry源码-redis分布式锁

redis实现分布式锁,实现了Lock接口,和ReentrantLock意向,有可重入,阻塞等功能

使用

依赖

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-redis</artifactId>
        </dependency>

    </dependencies>

配置类

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> 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<String, Object> 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<Boolean> 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<String, RedisLock> 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锁。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值