Redis分布式锁

33次阅读

共计 2230 个字符,预计需要花费 6 分钟才能阅读完成。

Redis 实现的分布式锁,jedis-2.9.0.jar 以上版本。commons-pool2-2.4.2.jar 以上版本

import java.util.Collections;
import javax.annotation.PostConstruct;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisDistributedLock {

    private static volatile JedisPool jedisPool;
    private static final String PREFIX = "redis_lock_";
    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call(\'get\', KEYS[1]) == ARGV[1] then return redis.call(\'del\', KEYS[1]) else return 0 end";

    @PostConstruct
    public void initJedis() {loadJedis();
    }
    
    private synchronized void loadJedis() {if (jedisPool == null) {
            int timeout = 1000 * 30;
            GenericObjectPoolConfig config = new GenericObjectPoolConfig();
            config.setMaxTotal(120);
            config.setMaxIdle(50);
            config.setMaxWaitMillis(10000);
            config.setTestOnBorrow(true);
            jedisPool = new JedisPool(config, "127.0.0.1", 6379, timeout, "123456");
        }
    }

    /**
     * 返还到连接池
     * 
     * @param pool
     * @param redis
     */
    public void close(Jedis redis) {if (redis != null) {redis.close();
        }
    }

    /**
     * @param lockKey 分布式锁的 key
     * @param lockVal 分布式锁得 value
     * @param expiredMilliSeconds key 的超时时间
     * @param maxWaitMilliSeconds 获取锁的最大超时时间
     * @return
     */
    public boolean tryLock(String lockKey, String lockVal, long expiredMilliSeconds, long maxWaitMilliSeconds) {long tryLockTime = System.currentTimeMillis();

        while (!tryLock(lockKey, lockVal, expiredMilliSeconds)) {if (System.currentTimeMillis() - tryLockTime > maxWaitMilliSeconds) {return false;}

            try {Thread.sleep(10L);
            } catch (InterruptedException arg9) {}}

        return true;
    }

    private boolean tryLock(String lockKey, String lockVal, long expiredMilliSeconds) {
        Jedis jedis = null;
        try {jedis = jedisPool.getResource();
            String result = jedis.set(PREFIX + lockKey, lockVal, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expiredMilliSeconds);
            return LOCK_SUCCESS.equals(result);
        } catch (Exception e) { } finally {close(jedis);
        }
        return false;
    }

    public boolean unlock(String lockKey, String lockVal) {
        Jedis jedis = null;
        try {jedis = jedisPool.getResource();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(lockVal));
            return RELEASE_SUCCESS.equals(result);
        } catch (Exception e) { } finally {close(jedis);
        }
        return false;
    }
}

正文完
 0

Redis分布式锁

33次阅读

共计 1569 个字符,预计需要花费 4 分钟才能阅读完成。

redis:Redis 是开源,内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理.
采用 单进程单线程 模型,并发能力强大,主流的分布式缓存工具。

加锁

通过 setnx 向特定的 key 值写入一个随机值,并且同时设置失效时间,写值成功即加锁成功.

注意点

  • 1. 必须给锁加一个失效时间,避免死锁.
  • 2. 加锁时,每个节点产生一个随机字符串,避免误删锁.

比如:A 节点加锁并设置有效时间,A 未执行完毕但是锁已经失效了,B 节点趁机加锁,此时 A 节点准备执行解锁动作,若 AB 节点加锁写入的值相等的话会造成 A 节点去解锁 B 节点的锁,显然不行.

  • 3. 写入随机值与设置有效时间必须同时的, 是原子操作,防止未设置有效时间.setnx 命令

maven 依赖:

       <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.7.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>

解锁

匹配随机值, 删除 redis 上的特定 key 的数据, 要保证删除动作是原子的, 即获取数据丶判断一致丶及删除数据三个操作是原子的。执行如下 lua 脚本:
ARV[1]是 value 值

if redis.call(“get”,KEYS[1])==ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
lua 脚本丢到服务器上,它认为是一个操作序列不可分开的序列,redis 介绍时说过是一个单进程单线程模型,这点要牢牢记住, 因此会通过一个线程执行这个脚本之后才会去处理别的请求,因此保证了 lua 脚本保证了原子性.

代码实现:
加锁:

public boolean tryLock() {
    // 随机生成字符串
    String uuid= UUID.randomUUID().toString();
    // 获取 redis 原始链接
    Jedis jedis = redisManager.getJedis();
    // 使用 setnx 命令请求写值,并设置有效时间
    String ret=jedis.set(KEY,uuid,"NX","PX",1000);
    if("OK".equals(ret)){
        // 供给 unlock 时从 threadLocal 获取此值,Thread 完成数据共享
        local.set(uuid);
        return  true;
    }
    return false;
}

解锁:

public void unlock() {

        // 读取 lua 脚本
        String script= FileUtils.readFileByLines("lua 脚本路径");
        Jedis jedis=redisManager.getJedis();
         // 链接 redis 执行 Lua 脚本,value 值无法从 unlock 方法入参了,因此用 threadLocal 来获取
        jedis.eval(script, Arrays.asList(KEY),Arrays.asList(local.get()));
        
    }    
    

这里是简单的代码实现具体讲怎么用 jedis 执行对应的方法,方法不齐的可以自行查阅资料

顺便简单讲下 ThreadLoacl
//1/ 做线程级的数据传递, 在 spring.mybatis 随处可见
//2. 比如 spring 的事务传播属性就是通过 threadLocal 来传递的
//3.tomcat 的 session 也是放在 threadLoacl 的
private static ThreadLocal<String> local = new ThreadLocal<>();

正文完
 0