redis setnx实现分布式锁




redis在分布式环境下才需要实现锁,一个客户端下不会出现竞争问题。这种方法对时间同步、锁时间有要求,将锁时间设置到100ms,测试的时候仍然会出现漏加的情况

package 使用setnx_getset;

锁的实现:

package 使用setnx_getset;


import java.io.IOException;


import redis.clients.jedis.JedisCluster;


public class DistributedLockWithSetnxGetset {


private static final long LOCK_TIME = 100;


//setnx这种方法,对LOCK_TIME、以及时间同步有要求,LOCK_TIME太小的话,可能拥有锁的客户端还没来得及释放锁,另外的客户端就通过getset判断锁超时,自己来拿锁操作,导致最后测试的累加值少
public boolean tryLock(JedisCluster jc, String lockKey, long timeout) {


boolean result = false;
long current = System.currentTimeMillis();


while (true) {
// 超时退出
if ((System.currentTimeMillis() - current) / 1000 >= timeout) {
System.out.println("超时退出,获取锁失败 ");
break;
}
// setnx获取锁
long c = System.currentTimeMillis();
if (1 == jc.setnx(lockKey,String.valueOf(c + LOCK_TIME+1))) {
result = true;
//System.out.println(Thread.currentThread().getName()+"获得锁");
jc.incr("count");
break;

//jc.set(key, value, nxxx, expx, time)
if (0 == jc.setnx(lockKey,String.valueOf(c + LOCK_TIME+1))) {

// 锁已经被占用,或者其他客户端异常,等待时间戳判断来释放锁


// 判断超时,直接调用getset会导致锁超时
String t = jc.get(lockKey);
if (t!=null && System.currentTimeMillis() > Long.valueOf(t)) {
// getset判断锁是否过期,先执行getset的客户端获得锁
String preLockValue = jc.getSet(lockKey,String.valueOf(System.currentTimeMillis()+ LOCK_TIME+1));
if (preLockValue != null && Long.valueOf(preLockValue) < System.currentTimeMillis()) {
// if (preLockValue != null && preLockValue.equals(t)) {
//preLockValue==null说明某个客户端把锁删除,为了防止锁竞争,应该让应用重新去尝试获取锁setnx,而不是将锁直接给客户端,否则会出现多个客户端同时获得锁的情况
//System.out.println(Thread.currentThread().getName()+"获得锁");
jc.incr("count");
result = true;
break;


}
} else {
//System.out.println("当前锁仍被占用,sleep 100ms");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}


}


}


return result;
}


}


测试代码:

import java.util.HashSet;
import java.util.Set;


import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

//这里使用get set值的方法来测试,正常情况下可以直接用redis的incr来实现。如果这种锁的实现没有问题,最后得到的值应用是3000,但测试下来,还是会出现少加的情况
public class ClientTest {

static JedisCluster jc;
private static final String LOCK = "LOCKTEST";
public static void main(String[] args) {
// TODO Auto-generated method stub
String serverInfo = "99.12.226.30:6379,99.12.226.31:6379,99.12.226.32:6379";
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
String ipPort[] = serverInfo.split(",");
for(int i =0 ;i < ipPort.length;i++){
String server[] = ipPort[i].split(":");
jedisClusterNodes.add(new HostAndPort(server[0], Integer.valueOf(server[1])));
}
jc = new JedisCluster(jedisClusterNodes);
jc.set("testvalue","0");
jc.set("count","0");
jc.set("addcount","0");
jc.del(LOCK);
new Thread("1"){


@Override
public void run() {
try{
DistributedLockWithSetnxGetset d1 = new DistributedLockWithSetnxGetset();
for(int i =0;i<1000;i++){
if(d1.tryLock(jc, LOCK, 100)==true){
//jc.set("test",jc.get("test")+"a");
String v = jc.get("testvalue");
//Thread.sleep(3);
jc.set("testvalue", String.valueOf(Long.valueOf(v)+10));
   jc.incr("addcount");
}
}}catch(Exception e){}finally{
/*String t = jc.get(LOCK);
if(t!=null && Long.valueOf(t)>System.currentTimeMillis())*/
jc.del(LOCK);}

}}.start();

new Thread("2"){


@Override
public void run() {
try{
DistributedLockWithSetnxGetset d2 = new DistributedLockWithSetnxGetset();
for(int i =0;i<1000;i++){
if(d2.tryLock(jc, LOCK, 100)==true){
//jc.set("test",jc.get("test")+"a");
String v = jc.get("testvalue");
//Thread.sleep(3);
jc.set("testvalue", String.valueOf(Long.valueOf(v)+10));
jc.incr("addcount");
}
}}catch(Exception e){}finally{
/*String t = jc.get(LOCK);
if(t!=null && Long.valueOf(t)>System.currentTimeMillis())*/
jc.del(LOCK);}

}}.start();

new Thread("3"){


@Override
public void run() {
try{
DistributedLockWithSetnxGetset d3 = new DistributedLockWithSetnxGetset();
for(int i =0;i<1000;i++){
if(d3.tryLock(jc, LOCK, 100)==true){
//jc.set("test",jc.get("test")+"a");
String v = jc.get("testvalue");
//Thread.sleep(3);
jc.set("testvalue", String.valueOf(Long.valueOf(v)+10));
jc.incr("addcount");
}
}}catch(Exception e){}finally{
/*String t = jc.get(LOCK);
if(Long.valueOf(t)>System.currentTimeMillis())*/
jc.del(LOCK);}

}}.start();
}



}


RedisSETNX命令用于在键不存在时设置键值对,如果键已经存在,则不做任何操作。利用SETNX命令可以实现简单的分布式锁。以下是一个使用SETNX实现分布式锁的示例代码: ```python import redis import time def acquire_lock(lock_name, acquire_timeout=10, lock_timeout=10): # 创建redis连接 redis_conn = redis.Redis() # 生成唯一的锁标识 lock_key = f"lock:{lock_name}" # 获取当前时间戳,用于计算锁超时时间 timestamp = int(time.time()) + acquire_timeout # 循环尝试获取锁 while int(time.time()) < timestamp: # 尝试获取锁 if redis_conn.setnx(lock_key, "locked"): # 设置锁超时时间 redis_conn.expire(lock_key, lock_timeout) return True # 短暂休眠,避免频繁尝试获取锁 time.sleep(0.1) return False def release_lock(lock_name): # 创建redis连接 redis_conn = redis.Redis() # 生成唯一的锁标识 lock_key = f"lock:{lock_name}" # 删除锁 redis_conn.delete(lock_key) ``` 在上述示例代码中,`acquire_lock`函数用于获取分布式锁,`release_lock`函数用于释放分布式锁。具体实现过程如下: 1. 创建Redis连接。 2. 生成唯一的锁标识,一般以`lock:`为前缀加上具体的锁名。 3. 计算获取锁的超时时间戳,即当前时间戳加上获取锁的超时时间。 4. 循环尝试获取锁,如果成功获取到锁,则设置锁的超时时间,并返回True;如果超过超时时间仍未获取到锁,则返回False。 5. 释放锁的过程比较简单,直接删除对应的锁标识即可。 需要注意的是,分布式锁实现还需要考虑异常情况下的处理、防止锁被误释放等问题,上述代码仅作为示例,具体应用场景中可能需要根据实际需求进行适当修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值