jedis分布式锁实现

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: jedis分布式锁实现

方案1

利用setNX若存在则不插入,不存在则插入成功,同时value为时间戳,没拿到锁则判断时间是否过期,get拿到时间戳,过期则用,getset方式赋值,在比较时间戳是否过期,过期则拿到锁;

@Component
public class RedisLock {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    public boolean addLock(String key, long expireTime) throws InterruptedException {
        boolean lock = true;
        long now = System.currentTimeMillis();
        //setNx
        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, expireTime + "")) {
            String lastTime = stringRedisTemplate.opsForValue().get(key);
            if (StringUtils.isNotEmpty(lastTime) && Long.parseLong(lastTime) < now) {
                // getset
                String recentTime = stringRedisTemplate.opsForValue().getAndSet(key, expireTime + "");
                //必须判断上一次时间和当前取出来的时间一致才行
                if (StringUtils.isNotEmpty(recentTime) && recentTime.equals(lastTime)) {
                    lock = true;
                    break;
                }
            }
            System.out.println("等待...");
            Thread.sleep(1000);
        }
        return lock;
    }
    public void unLock(String key, long nowTime) {
        //加锁解锁同一个人
        if (nowTime == Long.parseLong(stringRedisTemplate.opsForValue().get(key))) {
            stringRedisTemplate.delete(key);
        }
    }
}

方案2:

加锁:set(key,value,‘NX’,‘EX’,expireTime)

解锁:eval(lua语句,key1,value)

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
@Component
public class RedisFlusterLock {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    private static final String LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1]  then "
            + "    return redis.call(\"del\",KEYS[1]) " + "else " + "    return 0 " + "end ";
    public void tryLock(String key,String value, Long expireTime) {
        while (1 == 1) {
            String setResult = stringRedisTemplate.execute((RedisCallback<String>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                String result = null;
                if (nativeConnection instanceof JedisCluster) {
                    result = ((JedisCluster) nativeConnection).set(key, value, "NX", "EX", expireTime);
                }
                if (nativeConnection instanceof Jedis) {
                    result = ((Jedis) nativeConnection).set(key, value, "NX", "EX", expireTime);
                }
                return result;
            });
            if ("ok".equalsIgnoreCase(setResult)) {
                break;
            } else {
                try {
                    System.out.println("等待...");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public boolean unLock(String key, String value) {
        if (stringRedisTemplate.opsForValue().get(key).equals(value)) {
            Boolean delResult = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                long result = 0L;
                List<String> keys = new ArrayList<>();
                keys.add(key);
                List<String> values = new ArrayList<>();
                values.add(value);
                if (nativeConnection instanceof JedisCluster) {
                    result = (Long) ((JedisCluster) nativeConnection).eval(LUA, keys, values);
                }
                if (nativeConnection instanceof Jedis) {
                    result = (Long) ((Jedis) nativeConnection).eval(LUA, keys, values);
                }
                return result == 1L;
            });
            return delResult;
        } else {
            return false;
        }
    }
}

redis官网


其他参考

基于Redis命令:

SET key valueNX EX max-lock-time  

适用于redis单机和redis集群模式

  1. SET命令是原子性操作,NX指令保证只要当key不存在时才会设置value
  2. 设置的value要有唯一性,来确保锁不会被误删(value=系统时间戳+UUID)
  3. 当上述命令执行返回OK时,客户端获取锁成功,否则失败
  4. 客户端可以通过redis释放脚本来释放锁
  5. 如果锁到达了最大生存时间将会自动释放

只有当前key的value和传入的value相同才会执行DEL命令

if redis.call("get",KEYS[1]) == ARGV[1]  
then 
  return redis.call("del",KEYS[1]) 
else 
   return 0
end 
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
 * redis锁工具类
 */
public class RedisLock {
    public static final String OK = "OK";
    public static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] " + "then "
            + "    return redis.call(\"del\",KEYS[1]) " + "else " + "    return 0 " + "end ";
    /**
     * 单机和集群redis分布式锁
     * 
     * 参考:https://redis.io/commands/set
     * 
     * 版本:Redis Version >= 2.6.12
     * 
     * 命令:SET key-name uuid NX EX max-lock-time
     * 
     * @param keyName redis key name
     * @param stringRedisTemplate stringRedisTemplate
     * @param expireSeconds 锁定的最大时长
     * @return 锁定结果
     */
    public static LockRes tryLock(String keyName, StringRedisTemplate stringRedisTemplate, Integer expireSeconds) {
        // 将value设置为当前时间戳+随机数
        String lockValue = System.currentTimeMillis() + UUID.randomUUID().toString();
        String redisLockResult = stringRedisTemplate.execute((RedisCallback<String>) connection -> {
            Object nativeConnection = connection.getNativeConnection();
            String result = null;
            // 集群
            if (nativeConnection instanceof JedisCluster) {
                result = ((JedisCluster) nativeConnection).set(keyName, lockValue, "NX", "EX", expireSeconds);
            }
            // 单机
            if (nativeConnection instanceof Jedis) {
                result = ((Jedis) nativeConnection).set(keyName, lockValue, "NX", "EX", expireSeconds);
            }
            return result;
        });
        if (OK.equalsIgnoreCase(redisLockResult)) {
            return new LockRes(true, keyName, lockValue);
        } else {
            return new LockRes(false, keyName, null);
        }
    }
    public static Boolean unlock(LockRes lockRes, StringRedisTemplate stringRedisTemplate) {
        if (lockRes.isFlag()) {
            return stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                Long result = 0L;
                List<String> keys = new ArrayList<>();
                keys.add(lockRes.getKey());
                List<String> values = new ArrayList<>();
                values.add(lockRes.getValue());
                // 集群
                if (nativeConnection instanceof JedisCluster) {
                    result = (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, values);
                }
                // 单机
                if (nativeConnection instanceof Jedis) {
                    result = (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, values);
                }
                return result == 1L;
            });
        } else {
            return true;
        }
    }
}
public class LockRes {
    // 是否拿到锁,false:没拿到,true:拿到
    private boolean flag;
    // 缓存的键
    private String key;
    // 缓存的值
    private String value;
    public LockRes(boolean flag, String key, String value) {
        super();
        this.flag = flag;
        this.key = key;
        this.value = value;
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
18天前
|
NoSQL Redis 数据库
Redis原子操作和分布式锁setnx
Redis原子操作和分布式锁setnx
|
18天前
|
存储 分布式计算 NoSQL
setnx分布式锁原理RedisTemplate实现setnx分布式锁
setnx分布式锁原理RedisTemplate实现setnx分布式锁
26 0
|
18天前
|
存储 NoSQL Java
Redisson实现分布式锁
Redisson实现分布式锁
41 0
|
7月前
|
缓存 NoSQL Redis
redisson实现分布式锁
redisson实现分布式锁
38 1
|
11月前
|
NoSQL Java 测试技术
redisson中的分布式锁解读(下)
redisson中的分布式锁解读(下)
|
11月前
|
监控 NoSQL 算法
redisson中的分布式锁解读(上)
redisson中的分布式锁解读
Redisson 分布式锁的正确使用
你会正确使用分布式锁吗?
1850 0
Redisson 分布式锁的正确使用
|
监控 NoSQL Java
Redisson 完成分布式锁
Redisson 完成分布式锁
Redisson 完成分布式锁
|
存储 消息中间件 缓存
分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇
分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇
288 0
分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇
|
缓存 NoSQL Java
redis分布式锁(1)
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
redis分布式锁(1)

热门文章

最新文章