一、悲观锁
使用redis实现分布式悲观锁,主要是使用了redis原生命令的set命令(http://redisdoc.com/string/set.html),
SET key value [EX seconds] [PX milliseconds] [NX|XX]
从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改,比如NX参数,就能取代SETNX命令。【EX seconds】参数又将SET命令和EXPIRE命令结合为原子操作,这就为我们的分布式悲观锁打下了基础。
假设A、B分别是分布式系统同个资源的消费者,在获取该资源前,需要获取锁,才能对资源进行操作(否则会引起冲突)。此时A、B同时向redis发起命令:
//mykey代表key值,myValue代表value值,EX 10代表10秒失效,NX代表不存在该key才进行操作,否则返回nil
set myKey myValue EX 10 NX
有且只有一个消费者能set成功(获得OK),其余的都会返回nil。
该锁的好处在于,如果A消费者获取锁成功后,即使A挂了,锁也会在规定的失效时间内解锁,不会造成死锁问题。
Java代码的实现如下(此处使用了redisTemplate的连接工具):
public String setRedis(String key, String value) {
Boolean execute = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
return redisConnection.set(key.getBytes(), value.getBytes(), Expiration.from(Duration.ofSeconds(100)), RedisStringCommands.SetOption.ifAbsent());
}
});
if (execute)
return "OK";
else
return "Failed";
}
二、乐观锁
redis实现乐观锁,主要是使用了redis的如下事务的命令:
1.multi,开启Redis的事务,置客户端为事务态。
2.exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
3.discard,取消事务,置客户端为非事务态。
4.watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。
消费者A、B需要对变量v进行修改,他们同时进行如下操作:观察(watch命令)v,开启事务(multi),执行修改v的操作,提交事务(exec),中途如果有其他消费者已经修改了变量v,那么根据redis的事务的特性,exec提交的结果就是不成功。此时不成功的消费者可以继续重试,直至成功或者达到最大重试次数。
以下是java代码的实现方式:
public Object positiveLock(String key, String value) throws Exception {
int maxTryTime = 100;
while(true) {
//execute返回事务内多个命令的执行结果
List execute = (List)redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.watch(key);
operations.multi();
operations.delete("lalall");
operations.opsForValue().set(key, value);
operations.opsForValue().set("abcdefg", value);
Object val = operations.exec();
return val;
}
});
if(execute == null){
throw new Exception("未知异常");
}
//乐观锁获取成功
if(execute != null && execute.size() > 0){
break;
}
//重试次数太多
if(maxTryTime-- < 0){
break;
}
}
return "OK";
}