redis分布式锁

1、前提条件

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。示例:客户端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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值