1、前提条件
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。示例:客户端A锁超时了,客户端B获得了锁,客户端A是不能是否客户单B的锁的。

2、java实现(lettuce)
2.1 pom.xml
<dependencies>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.0集成redis所需common-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
2.2 锁实现
public boolean lock(final String lockKey, final String value) throws InterruptedException {
boolean result = false;
String script = "local key = KEYS[1]; local value = ARGV[1]; if redis.call('set', key, value, 'NX' ,'EX', ARGV[2]) then return 1 else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value,
commonConfig.getRedisLockTimeout());
if (execute != null && execute > 0) {
result = true;
}
return result;
}
lock中的key和value必须都是唯一的。value保持唯一是在解锁时会用到,保证了条件4。如果是集群,key应该包含"{}"部分,具体原因,可查看redis集群执行lua。
2.3 解锁
public <K, V> boolean release(final String lockKey, final String value) {
boolean result = false;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
if (execute != null && execute > 0) {
result = true;
}
return result;
}
2.4 测试
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.spring.pro.ProviderApplication;
import com.spring.pro.constant.RedisPre;
import com.spring.pro.service.RedisService;
@RunWith(value = SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RedisTest {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource(name = "redisService")
private RedisService redisService;
@Test
public void lock() throws InterruptedException {
String lockKey=RedisPre.GOODS_KEY;
String value="fff";
boolean flag=redisService.lock(lockKey,value);
logger.info("lockKey:{}",flag);
}
@Test
public void release() throws InterruptedException {
String lockKey=RedisPre.GOODS_KEY;
String value="fff";
boolean flag=redisService.release(lockKey,value);
logger.info("flag:{}",flag);
}
}
3、锁用到的redis命令
lock用到的redis命令:SET key value [EX seconds] [PX milliseconds] [NX|XX]。
可选参数:
从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:
- 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 :只在键已经存在时,才对键进行设置操作。
因为 SET 命令可以通过参数来实现和 SETNX 、 SETEX 和 PSETEX 三个命令的效果,所以将来的 Redis 版本可能会废弃并最终移除SETNX 、 SETEX 和 PSETEX 这三个命令。
返回值:
在 Redis 2.6.12 版本以前, SET 命令总是返回 OK 。
从 Redis 2.6.12 版本开始, SET 在设置操作成功完成时,才返回 OK 。
如果设置了 NX 或者 XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回复(NULL Bulk Reply)。

3114

被折叠的 条评论
为什么被折叠?



