redis分布式锁的设计

redis分布式锁的设计

今天我们来说一说基于redis分布式锁的设计(基于springboot框架下的实现):

1、首先我们设计一个接口:

/**
 * 分布式锁
 * 
 * @author fengjie song
 *
 */
public interface DistributedLock {

    /**
     * 分布式锁1
     * 
     * @param key
     * @return
     */
    boolean lock(String key);

    /**
     * 分布式锁2
     * 
     * @param key
     * @param expire
     * @return
     */
    boolean lock(String key, long expire);

    /**
     * 分布式锁3
     * 
     * @param key
     * @param expire
     * @param retryTime
     * @return
     */
    boolean lock(String key, long expire, int retryTime);

    /**
     * 分布式锁4
     * 
     * @param key         redis key
     * @param expire      key的实效时间
     * @param retryTime   获取锁的重试次数
     * @param sleepMillis 睡眠时间(毫秒)
     * @return
     */
    boolean lock(String key, long expire, int retryTime, long sleepMillis);

    /**
     * 释放锁
     * 
     * @param key
     * @return
     */
    boolean releaseLock(String key);
}

2、抽象类实现部分非核心的代码

/**
 * 模板设计模式
* 模板设计模式在这里可以有效避免方法多次实现,
* 例如增加基于zookeeper的分布式锁,我们只需要重写核心的方法即可 * * @author fengjie song * */ public abstract class AbstractDistributedLock implements DistributedLock { private static final long EXPIRE = 60; private static final int RETRY_TIME = 3; private static final long SLEEP_MILLIS = 1000; @Override public boolean lock(String key) { return lock(key, EXPIRE, RETRY_TIME, SLEEP_MILLIS); } @Override public boolean lock(String key, long expire) { return lock(key, expire, RETRY_TIME, SLEEP_MILLIS); } @Override public boolean lock(String key, long expire, int retryTime) { return lock(key, expire, retryTime, SLEEP_MILLIS); }

3、分布式锁实现类(基于redis),这里也可以设计基于zk等其他的分布式锁

/**
 * redis分布式锁的实现(策略模式)
* 这里只需要重写一个方法,其余方法在抽象类里面调用(模板设计模式) * * @author fengjie song * */ public class RedisDistributedLock extends AbstractDistributedLock { /** * 引入redisTemplate */ private RedisTemplate redisTemplate; /** * 记录redis锁的标识 */ private ThreadLocal lockFlag = new ThreadLocal(); /** * 释放锁的LUA脚本 */ private static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(64); sb.append(" redis.call('get',KEY[1]) == ARGV[1] "); sb.append(" then "); sb.append(" return redis.call('del',KEY[1]) "); sb.append(" else "); sb.append(" return 0 "); sb.append(" end "); UNLOCK_LUA = sb.toString(); } public RedisDistributedLock(RedisTemplate redisTemplate) { super(); this.redisTemplate = redisTemplate; } @Override public boolean lock(String key, long expire, int retryTime, long sleepMillis) { // 设置锁 boolean result = setRedis(key, expire); // 如果设置失败,根据设定的重试次数进行重试 while (!result && retryTime-- > 0) { try { Thread.sleep(sleepMillis); } catch (InterruptedException e) { return false; } result = setRedis(key, expire); } return result; } private boolean setRedis(String key, long expire) { String result = redisTemplate.execute(new RedisCallback() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { // 这里我们不知道redis的连接方式是单机、读写分离的主从还是集群,所以在这里返回他们的父级接口JedisCommands JedisCommands rc = (JedisCommands) connection.getNativeConnection(); String id = UUID.randomUUID().toString(); lockFlag.set(id); return rc.set(key, id, "NX", "PX", expire); } }); return !StringUtils.isEmpty(result); } @Override public boolean releaseLock(String key) { try { // 释放锁的时候,可能当前锁已经失效,对应的key锁可能已经被别的线程持有,所以不能直接删除锁,这里我们采用LUA脚本进行柔和删除 List keys = new ArrayList(); keys.add(key); List args = new ArrayList(); args.add(lockFlag.get()); // 利用LUA脚本进行匹配key-value进行删除,可以避免因锁失效,而误删别的线程已经持有的锁 Long result = redisTemplate.execute(new RedisCallback() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { Object conn = connection.getNativeConnection(); // 虽然redis集群模式和单机模式有一样的接口,但是在这里无法确定是单机还是集群所以需要分开执行 if (conn instanceof Jedis) { return (Long) ((Jedis) conn).eval(UNLOCK_LUA, keys, args); } if (conn instanceof JedisCluster) { return (Long) ((JedisCluster) conn).eval(UNLOCK_LUA, keys, args); } return 0L; } }); return result != null && result > 0; } finally { // 清除过期或者已经删除的锁标识,避免内存溢出 lockFlag.remove(); } } }

你可能感兴趣的:(redis)