redis分布式锁(一)set NX实现

最近是一直在研究redis,本篇则主要想谈谈对redis的锁的理解。


使用redis常用的两种加锁的机制:

  • SETNX命令
  • SET命令

一、SETNX命令的使用

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。

127.0.0.1:6379> SETNX test 'try'
(integer) 1
127.0.0.1:6379> get test
"try"
127.0.0.1:6379> SETNX test 'tryAgain'
(integer) 0
127.0.0.1:6379> get test
"try"

第一次给test赋值,返回表示成功;第二次再次尝试给test赋值,返回0,表示失败,并未再次操作,因为值已经存在。
比如有两个线程A和B,两个线程操作相同的key.
在这里插入图片描述
虽然流程上是能够走通的.

二、SET实现

如上的NX命令也就是简单介绍一下使用,一般说分布式锁NX命令其实是表达set 的一种命令,如下:

1)命令说明:

SET key value [EX seconds] [PX milliseconds] [NX|XX]
生存时间(TTL,以秒为单位)
Redis 2.6.12 版本开始:(等同SETNX 、 SETEX 和 PSETEX)
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 :只在键已经存在时,才对键进行设置操作。

2)set get 可以对已经存在的值重新赋值

127.0.0.1:6379> set test1 value
OK
127.0.0.1:6379> get test1
"value"
#可以重新对已经存在的值赋值
127.0.0.1:6379> set test1 new-value
OK
127.0.0.1:6379> get test1
"new-value"

3)可以设置过期时间 ex ttl

#设置过期时间为10000秒
127.0.0.1:6379> set test1-expire-time 'hello' ex 10000
OK
127.0.0.1:6379> get test1-expire-time
"hello"
#ttl 获取剩余过期时间
127.0.0.1:6379> ttl test1-expire-time
(integer) 9990
127.0.0.1:6379> ttl test1-expire-time
(integer) 9963
127.0.0.1:6379> ttl test1-expire-time
(integer) 9876

4)NX 已经存在的值不能再次赋值

#NX:已经存在的值不能再次赋值
127.0.0.1:6379> set not-test1-key 'hello' NX
OK
127.0.0.1:6379> get not-test1-key
"hello"
127.0.0.1:6379> set not-test1-key 'hello1' NX
(nil)
127.0.0.1:6379> get not-test1-key
"hello"

5)XX:不存在的key不能赋值,只能修改已经存在的key的value

#XX:未存在的key不能赋值,只能修改已经存在的key的value
127.0.0.1:6379> EXISTS exists-key
(integer) 0
127.0.0.1:6379> set exists-key 'value' XX
(nil)
127.0.0.1:6379> set exists-key 'value'
OK
127.0.0.1:6379> set exists-key 'value-new' XX
OK
127.0.0.1:6379> get exists-key
"value-new"

三、实现分布式锁

1)命令基本实现

命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法

#给lock设置了过期时间为60000毫秒(也可以用ex 6000,单位就变成了秒),当用NX再次赋值,则返回nil,不能重入操作
127.0.0.1:6379> set lock true NX px 60000
OK
127.0.0.1:6379> set lock true NX px 6000
(nil)
127.0.0.1:6379> get lock
"true"
127.0.0.1:6379> ttl lock
(integer) 43
#时间过期后再次get,返回nil,表明key 为 lock的锁已经释放
127.0.0.1:6379> get lock
(nil)

如果setnx 返回ok 说明拿到了锁;如果setnx 返回 nil,说明拿锁失败,被其他线程占用。
换成客户端服务器则是如下:
客户端执行以上的命令:
如果服务器返回 OK ,那么这个客户端获得锁。
如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。

2)图解

如上过程通过一个图解一下:
在这里插入图片描述

3)项目中代码实现

如下是程序中写的redisConfig。采用了jsdisCluster的set方法。详情:jedis set 的四个重载方法

    @Override
	public String lock(String key,long expireSecond) {
		final String value = String.valueOf(System.currentTimeMillis());
		//nx : not exists, 只有key 不存在时才把key value set 到redis
		final String nxxx = "NX";
		//ex : seconds 秒
		final String expx = "EX";
		boolean ret;
    	if(expireSecond <= 0L)
    		//jdeis setnx命令 失效时间
    		ret = jedisCluster.setnx(key, value) > 0L;
    	else
    	    //设置key的过期时间是expireSecond
    		ret = "ok".equalsIgnoreCase(jedisCluster.set(key, value, nxxx, expx, expireSecond));
        if(ret){
        	return value;
        }else{
        	return null;
        }
	}

4)问题一:那为什么要使用PX/XX 去设置一个超时时间?

是怕进程A不讲道理啊,锁没等释放呢,万一崩了,直接原地把锁带走了,导致系统中谁也拿不到锁。

5)问题二:设置了超时时间,就确保万无一失了吗?

如果进程A又不讲道理,操作锁内资源超过笔者设置的超时时间,那么就会导致其他进程拿到锁,等进程A回来了,回手就是把其他进程的锁删了
在这里插入图片描述
该如何解决如上场景呢?可以加一层判定,当自己的进程结束或者过期,若value不是自己的值,则不进行删除操作流程。

/**
	 *
	 * @param key
	 * @param keySign 可为空,为空则不断是否为上次获得的锁
	 * @return
	 */
	@Override
	public boolean unlock(final String key, final String keySign) {
    	if(StringUtils.isNotBlank(keySign)){
			String val = jedisCluster.get(key);
			if(!keySign.equals(val)) {
				return false;
			}
		}
        if(jedisCluster.del(key) <= 0){
        	if(jedisCluster.exists(key)){
        		return false;
        	}else{
        		return true;
        	}
        }
        return true;
	}

四、总结

目前项目中是使用了jedis客户端来操作Redis cluster。以上操作都得经过程序员的手动编码,以及能不能保证原子性的问题,当然业务量不大或者并发不大的情况还是很好用的。
下篇则讲述redission如何实现分布式锁?

  • 12
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Redis分布式锁可以通过多种方式实现。其中一种方式是使用Redisson分布式锁Redisson解决了「锁过期释放,业务没执行完」的问题。它通过在加锁时启动一个后台线程,每隔一段时间检查锁是否还被持有,如果是,则延长锁的生存时间。这样可以确保在业务逻辑执行期间锁不会过期。\[1\] 除了Redisson,还有其他的分布式锁实现方式。其中一种方式是使用SETNX + EXPIRE命令,通过设置一个键值对来表示锁的状态,并设置过期时间来自动释放锁。另一种方式是使用Lua脚本,通过原子性的执行SETNX和EXPIRE两条指令来实现加锁和设置过期时间。还有一种方式是使用Redis的扩展命令SET EX PX NX,通过设置过期时间和唯一随机值来实现加锁。此外,还有Redlock和Redission等开源框架可以实现多机的分布式锁。\[2\] 需要注意的是,分布式锁实现方式需要考虑一些问题,比如加锁后业务逻辑还未执行完成锁已经过期,这会导致其他客户端拿到锁。如果是单节点,这个问题不大,但是在集群环境下,加锁首先会落盘到master节点,然后再复制到slave节点。如果在复制之前master节点挂掉,就会导致锁丢失的问题。为了解决这些问题,Redis官方推荐使用Redisson分布式锁。\[3\] #### 引用[.reference_title] - *1* *2* [Redis实现分布式锁的7种方案](https://blog.csdn.net/qszfly/article/details/126100421)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] - *3* [Redis分布式锁的正确实现方式](https://blog.csdn.net/yaomingyang/article/details/104965554)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值