您的位置:首页 > 数据库 > Redis

基于单点redis服务的分布式锁简单实现

2017-11-14 14:32 766 查看
由于项目中有需要需要加锁的方法,而项目的pc和和移动端又在不同的服务器里。由于项目处于不同的jvm中,jdk自带的锁机制无法达到效果了。故看了一些基于redis实现分布式锁的方式,总结如下:

分布式锁可以基于很多种方式实现,比如zookeeper、redis…。基本原理是相通的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识

在 Redis 里,有一个 SETNX函数,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

利用该函数的特性,即如果key已经存在,setnx返回1,如果key已经存在,setnx返回0。通过返回值来判断能否将key插入到redis中。

为了避免因为某一个线程意外挂起或因故无法释放锁的问题,需要为锁增加一个有效时间概念。redis的expire()函数来给key设置过期时间,当各种原因未能对锁unlock时,可以通过过期时间来实现锁的释放。

如下是实现分布式锁的及测试代码(实际应用中应注意timeout和expireSecs的值设定):

1.分布式锁实现

import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.Jedis;

public class RedisLock {
private RedisLock redisLock;

/** 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒 */
private static final long MILLI_NANO_CONVERSION = 1000 * 1000L;

private static final String EXPIRE_KEY_PREFIX = "lock:%s";

private boolean locked;

private String key;

public RedisLock(String key) {
if (StringUtils.isEmpty(key)) {
throw new IllegalArgumentException("Param key can not be null or empty");
}
this.key = String.format(EXPIRE_KEY_PREFIX, key);
this.locked = false;
redisLock = this;
}

/**
* 获取锁
* 在timeout时间内会一直尝试获取锁,如果timeout=0,则表示获取失败后直接返回不再尝试
* 锁的有效期expireSecs必须大于0,当超过有效期未被unlock时,系统将会强制释放
* @param timeout     获取锁的等待时间
* @param expireSecs  锁的有效时间,必须大于0
* @return
*/
public boolean lock(long timeout, int expireSecs) {
if (expireSecs <= 0) {
throw new IllegalArgumentException("Param expireSecs must lager than zero.");
}
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
Jedis jedis = new Jedis("127.0.0.1", 6379);//连接redis服务器,127.0.0.1:6379
try {
while ((System.nanoTime() - nano) < timeout) {
String lockStart = String.valueOf(System.currentTimeMillis());
if (jedis.setnx(this.key, lockStart) == 1) {
jedis.expire(this.key, expireSecs);
this.locked = true;
return this.locked;
}
Thread.sleep(30);
}

String expireStr = jedis.get(key);
Long now = System.currentTimeMillis();
String nowStr = String.valueOf(now);
if (!StringUtils.isNumeric(expireStr))
{
return false;
}
//ttl小于0 表示key上没有设置生存时间(key是不会不存在的,因为前面setnx会自动创建)
//出现这种状况,是因为某个实例setnx成功后,expire由于各种可能原因而没有被调用造成的
//这时可以直接设置expire来占有锁
long ttlValue = jedis.ttl(this.key);
if(ttlValue<0){
jedis.setnx(key, nowStr);
jedis.expire(key, expireSecs);
System.out.println("key上没有设置生存时间,获得锁");
return true;
}

Long expireLong = Long.parseLong(expireStr);
//若锁超时,则直接删除key
if (now - expireLong > expireSecs * 1000)
{
jedis.del(key);
jedis.setnx(key, nowStr);
jedis.expire(key, expireSecs);
System.out.println("锁超时:"+(now - expireLong)+",获得锁");
return true;
}

} catch (Exception e) {
throw new RuntimeException("Locking error", e);
}
return false;
}

/**
* 释放锁
* @return
*/
public boolean unLock() {
Jedis jedis = new Jedis("127.0.0.1", 6379);//连接redis服务器,127.0.0.1:6379

try {
if (jedis.del(this.key) == 1) {
this.locked = false;
return true;
}
return false;
} catch (Exception e) {
throw new RuntimeException("UnLocking error", e);
}
}

}


2.测试代码

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RedisLockTest {
//等待时间
private static final int timeout=25*1000;
//锁过期时间
private static final int expireSecs=10;

public static void main(String[] args) {
RedisLock yuxinLock = new RedisLock("yuxin");
new RedisLockTest().testM(yuxinLock);
}

public void testM(RedisLock yuxinLock){
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for(int i = 0; i < 5; i++){
fixedThreadPool.execute(new Task("woker_" + i,yuxinLock));
}
}

class Task implements Runnable{

private String name;
private RedisLock yuxinLock;

public Task(String name,RedisLock yuxinLock){
this.name = name;
this.yuxinLock=yuxinLock;
System.out.println(name);
}
public void run() {
try{
System.out.println(name+"开始夺锁大战");
if(yuxinLock.lock(timeout,expireSecs)){
System.out.println(this.name + "开始工作了"+new Date());
int time = 5000;
if(time > 0){
Thread.sleep(time);
}
System.out.println(this.name + "结束工作了;" + new Date());
}else{
System.out.println(name+"等待超时");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
yuxinLock.unLock();
}
}

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息