DistributedLock-Redis


本文的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望各位大佬指出。

1. 背景

在分布式项系统的大背景下,CAP中分区容错性是必不可少的。多节点的协调尤为重要,单体服务中的锁在分布式系统中已无效,需要另辟蹊径。此文介绍比较常见的一种基于Redis实现的分布式锁。ZooKeeper的分布式锁后续Blog更新中介绍。

2. 环境

JDK:1.8

IDE:IDEA

Redis:Redis server v=5.0.5 bits=64 (redis配置此处不做赘述)

3.详解

Redis能实现分布式锁是因为存在一个命令:setnx ,而ex属性保证了一些容错。

3.1 使用LUA脚本

3.1.1 maven依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.1.2实现代码如下:

注:调用时value结果业务设定,过期时间也可以当参数传入。当业务的花费时间波动比较大,需要添加守护线程进行续命,确保业务不会因为锁的过期时间太短出现问题。

@Slf4j
@Component
public class RedisDistributedLock {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 过期时间10min
     */
    @Value("${redis.expireTime:600000}")
    private static long EXPIRE_TIMEOUT = 10 * 60 * 1000;


    private static final Long SUCCESS = 1L;

    /**
     * 加锁
     *
     * @param key   redis key
     * @param value redis value
     * @return 是否上锁成功
     */
    public boolean lock(final String key, final String value) {
        if (Strings.isBlank(key)) {
            return false;
        }
        try {
            String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
            RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(EXPIRE_TIMEOUT));
            if (SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            log.error("redis lock failed", e);
        }
        return false;
    }


    /**
     * 解锁
     *
     * @param key redis key 
     * @param value redis value
     * @return 是否解锁成功
     */
    public boolean unlock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

3.2 现有框架redisson

3.2.1 maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.3</version>
</dependency>

3.2.2 代码实现如下:

3.2.2.1 根据官方javadoc说明,使用redisson实现分布式锁的例子如下:

/**
     * Acquires the lock only if it is free at the time of invocation.
     *
     * <p>Acquires the lock if it is available and returns immediately
     * with the value {@code true}.
     * If the lock is not available then this method will return
     * immediately with the value {@code false}.
     *
     * <p>A typical usage idiom for this method would be:
     *  <pre> {@code
     * Lock lock = ...;
     * if (lock.tryLock()) {
     *   try {
     *     // manipulate protected state
     *   } finally {
     *     lock.unlock();
     *   }
     * } else {
     *   // perform alternative actions
     * }}</pre>
     *
     * This usage ensures that the lock is unlocked if it was acquired, and
     * doesn't try to unlock if the lock was not acquired.
     *
     * @return {@code true} if the lock was acquired and
     *         {@code false} otherwise
     */
boolean tryLock();

3.2.2.2 此处提供基础的使用示例,redisson包含的很多类型的锁,包括公平锁,锁组,读写锁等,此处不逐一介绍。不难发现,redisson的锁和java提供的显式锁用法一致。

@Slf4j
@Component
public class RedisSonDistributedLock {
    @Autowired
    private Redisson redisson;
    /**
     * 过期时间10min
     */
    @Value("${redis.expireTime:600000}")
    private static long EXPIRE_TIMEOUT = 10 * 60 * 1000;

     public void redissonDistributedLockDemo(final String key) {
        RLock lock = redisson.getLock(key);
        try {
            boolean isLock = lock.tryLock(EXPIRE_TIMEOUT, TimeUnit.MILLISECONDS);
            if (isLock) {
                // TODO: 业务操作
            }
        } catch (InterruptedException e) {
            log.error("tryLock failed.", e);
        } finally {
            lock.unlock();
        }
    }
}

此次介绍的分布式锁,只是一个简单的展示,具体的使用,需要根据业务处理。

比如Lua实现的分布式锁,当业务处理所需时间波动比较大时,预设的过期时间不好制定时,需要添加守护线程进行续命;同时也可以添加重试机制,确保加锁时不会因为其他非锁占用因素导致加锁失败。

本人自己也实现了分布式锁的starter,有兴趣可以看一下,starter源码地址



 上一篇
AOP实战-AspectJ AOP实战-AspectJ
本文的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望各位大佬指出。 1. 背景用过Spring的都知道DI和AOP,今天介绍一个实现AOP超级且方便功能强
2020-05-27
下一篇 
ArrayList扩容机制 ArrayList扩容机制
本文的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望各位大佬指出。 1. 背景面试的时候问到过ArrayList的扩容机制以及HashMap的扩容机制,今
2020-05-26
  目录