在大型项目中,单机服务往往是不可行的。所以分布式环境的出现,但是分布式环境中如何保持数据的一致性,在这里我们只讨论下redis分布式锁这个解决方案:
使用redis分布式锁需要了解几个命令
redis命令介绍
SETNX命令
当且仅当 key
不存在,将 key
的值设为 value,并返回1
。
若给定的 key
已经存在,则SETNX不做任何动作,并返回0。
GET命令
返回 key
所关联的字符串值。
如果 key
不存在那么返回特殊值 nil
。
假如 key
储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。
DEL命令
删除给定的一个或多个 key
。
不存在的 key
会被忽略。
EXPIRE命令
为给定 key
设置生存时间,当 key
过期时(生存时间为 0
),它会被自动删除。
redis分布所实现
获得锁
使用SETNX命令可以直接加锁,客户端可以直接使用命令,假如返回值为1,表示客户端获得锁了,可以继续操作,最后使用DEL命令释放锁。
如果返回0,就意味着别的客户端获得锁了。这种想法是美好的,但是有许多不足的地方,接着往下看:
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 1
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 0
127.0.0.1:6379> DEL MY.LOCK
(integer) 1
解决死锁
在上面获得锁,处理业务,但是如果业务卡死,客户端没释放锁将需要怎么办?我们可以使用EXPIRE命令来设置锁过期。
假如客户端获得锁时间只有10S,超过这个时间锁就自动释放了,这么一看貌似解决了问题。我们接着往下看:
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 1
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 0
127.0.0.1:6379> EXPIRE MY.LOCK 10
(integer) 1
释放锁优化
上面解决了死锁,那么还有什么问题了。现在思考下,客户端获得锁了,10S后锁自动释放,这个时候某一个客户端业务处理超过10S,然后又把锁释放了,注意释放的这个锁并不是自己的锁,释放了别的客户端的锁,这个时候又如何解决了?现在我们在获得锁的时候,把VALUS的值设置为UUID,客户端去释放锁的时候,首先GET锁的值,如果获得了值去跟自己的UUID比对,做相对应的处理。这下看来就完美了。
客户端获得锁等待
如果同一个时间有多个客户端进来,只有一个客户端获得锁,那么其余的客户端要在等待中,一直去尝试获得锁。下面我们结合代码看下:
客户端代码:
@Service
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//请求次数
private final int waitCount=100;
/**
* 获得锁
* zwz 2016-07-12 17:05:32
* @param key
* @param uuid
* @return
*/
public boolean getNxLock(final String key,final String uuid){
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] keyByte = serializer.serialize(key+".lock");
byte[] valueByte = serializer.serialize("locked:"+uuid);
boolean isOk = false;
for(int i=1;i<=waitCount;i++){
isOk = connection.setNX(keyByte, valueByte);
if(isOk){
connection.expire(keyByte, 10);
break;
}else{
try{
Thread.sleep(100);//休眠100毫秒
}catch(Exception e){
e.printStackTrace();
}
}
}
return isOk;
}
});
return result;
}
/**
* 释放锁
* zwz 2016-07-12 17:06:32
* @param key
* @param uuid
*/
public void releaseNxLock(final String key,final String uuid){
redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] keyByte = serializer.serialize(key+".lock");
byte[] bs = connection.get(keyByte);
if(bs==null){
return false;
}
if(("locked:"+uuid).equals(new String(bs))){
return connection.del(keyByte) == 1;
}else{
return false;
}
}
});
}
}