Redis原生SET、SETNX、SETEX方法以及RedisTemplate的方法 + 分布式锁的实现

Redis原生GET/SET方法

  • SET()命令:有两个参数 set(key, value),将字符串值 value 关联到 key ,如果 key 已经持有其他值, SET 就覆写旧值,无视类型
  • SETNX():含义就是SET if Not Exists,有两个参数 setnx(key, value),该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。
  • SETEX()命令:有三个参数 set(key, time, value),在设置的时候给key设置一个过期时间time,时间到了key自动失效。
  • GET()命令:获取key的值,如果存在,则返回;如果不存在,则返回nil;

从 Redis 2.6.12 版本开始, SET 命令的行为可以通过添加一系列参数来修改:

EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。

因为 SET 命令可以通过参数来实现和 SETNX 、 SETEX 和 PSETEX三个命令的效果,所以将来的 Redis
版本可能会废弃并最终移除 SETNX 、 SETEX 和 PSETEX 这三个命令。

Spring框架中RedisTemplate.opsForValue()方法的使用。

方法解释
set(K key, V value)新增一个字符串类型的值,key是键,value是值。
setIfAbsent(K key, V value)如果键不存在则新增,存在则不改变已经有的值。相当于SETNX
set(K key, V value, long timeout, TimeUnit unit)添加数据,并设置过期时间。
setIfAbsent(K key, V value, long timeout, TimeUnit unit);当key不存在的时候添加数据,并设置过期时间
set(K key, V value, long offset)覆盖从指定位置开始的值。
get(Object key)获取key键对应的值。
getAndSet(K key, V value)获取原来key键对应的旧值并重新赋新值。
increment(K key, double delta)以增量的方式将double值存储在变量中。
increment(K key, long delta)以增量的方式将long值存储在变量中。
decrement(K key, long delta);自减运算,并设置步长 (默认是1)
size(K key)获取指定字符串的长度
multiSet(Map<? extends K,? extends V> map)设置map集合到redis。
multiGet(Collection keys)批量获取数据
multiSetIfAbsent(Map<? extends K,? extends V> map)如果对应的map集合名称不存在,则添加;如果存在则不做修改
append(K key, String value)在原有的值基础上新增字符串到末尾。
get(K key, long start, long end)截取key键对应值得字符串,从开始下标位置开始到结束下标的位置(包含结束下标)的字符串
setBit(K key, long offset, boolean value)key键对应的值value对应的ascii码,在offset的位置(从左向右数)变为value
getBit(K key, long offset)判断指定的位置ASCII码的bit位是否为1

基于Redis的分布式锁

之前在别的项目里看到的redis分布式锁通用代码:

  	/** 上锁 */
    public boolean lock(String key, String value) {
    	//如果key值不存在,返回 true,并设置 value
        if (redisClient.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        String currentValue = redisClient.opsForValue().get(key);
        //判断锁是否已过期
        if (!StringUtils.isEmpty(currentValue)&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //获得之前的value值,同时设置新的value。这个地方可能几个线程同时过来,但是redis本身天然是单线程的,所以getAndSet方法还是会安全执行。
            String oldValue = redisClient.opsForValue().getAndSet(key, value);
            //判断currentValue是否和oldVal值相等,如果是同一个值,该线程就set新的value,后面的线程就取不到锁了
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }
    
    /** 解锁 */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisClient.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                redisClient.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("RedisLock unlock error ", e);
        }
    }

但是在高并发访问情况下,会出现一个线程获取锁后被其他线程修改掉超时时间。并且解锁时也会出现一些问题;
redis官方给出了正确的加锁姿势!

要在Redis 中实现分布式锁,可以用下面的方法:

SET resource-name anystring NX EX lock-time

java中可以使用jedis set 命令加锁,即

jedis.set(keys,args,"NX","PX",3000)

该命令仅在密钥尚不存在时才设置密钥(NX选项),到期时间为3000毫秒(PX选项,EX选项单位为秒)

客户端执行以上的命令:

  • 如果服务器返回 OK ,那么这个客户端获得锁。
  • 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
  • 设置的过期时间到达之后,锁将自动释放。

可以通过以下修改,让这个锁的实现更健壮:

  • 不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。
  • 不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
    这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。

以下是一个简单的解锁脚本示例:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

这个脚本可以通过 EVAL …script… 1 resource-name token-value 命令来调用。

如果我的文章对您有帮助的话,请点个赞吧~

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis分布式锁实现可以使用RedisSETNX命令(SET if Not eXists)来完成。SETNX命令在键不存在时设置键的值,如果键已经存在则不执行任何操作。 以下是使用SETNX命令实现Redis分布式锁的示例代码(使用Python语言): ```python import redis def acquire_lock(redis_conn, lock_key, expire_time): # 使用SETNX命令尝试获取锁 lock_acquired = redis_conn.setnx(lock_key, 1) if lock_acquired: # 设置锁的过期时间 redis_conn.expire(lock_key, expire_time) return True else: return False def release_lock(redis_conn, lock_key): # 释放锁,删除键 redis_conn.delete(lock_key) # 创建Redis连接 redis_conn = redis.Redis(host='localhost', port=6379, db=0) # 获取锁 if acquire_lock(redis_conn, 'mylock', 10): try: # 执行业务逻辑 print("Lock acquired. Do something here...") finally: # 释放锁 release_lock(redis_conn, 'mylock') else: print("Failed to acquire lock. Another process holds the lock.") ``` 以上代码中,`acquire_lock`函数尝试获取锁并设置过期时间,如果成功获取到锁则返回True,否则返回False。`release_lock`函数用于释放锁,即删除键。 在使用分布式锁时,可以根据具体业务需求设置合适的锁键(`lock_key`)和过期时间(`expire_time`)。确保在获取到锁之后,执行业务逻辑的代码在适当的位置调用`release_lock`函数释放锁,避免锁一直被占用而无法释放。 需要注意的是,RedisSETNX命令是原子操作,可以确保在并发情况下只有一个客户端能够成功获取到锁。同时,设置合适的过期时间能够避免因为异常情况导致锁一直被占用而无法释放。 这只是一个简单的示例,实际使用中还需要考虑异常处理、加锁失败的重试策略以及其他线程安全的因素,以确保分布式锁的可靠性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值