Redis分布式锁命令
setnx:
当且仅当 key 不存在。若给定的 key 已经存在,则 setnx不做任何动作。setnx 是『set if not exists』(如果不存在,则 set)的简写,setnx 具有原子性。
getset:
先 get 旧值,后set 新值,并返回 key 的旧值(old value),具有原子性。当 key 存在但不是字符串类型时,返回一个错误;当key 不存在的时候,返回nil ,在Java里就是 null。
expire:设置 key 的有效期
del:删除 key
与时间戳的结合
分布式锁的值是按系统当前时间 System.currentTimeMillis()+Key 有效期组成
Redis分布式锁流程图
Spring Schedule + Redis分布式锁构建分布式任务调度
引入maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot - Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 高版本redis的lettuce需要commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Redis相关配置
spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.port=6379
spring.redis.database=0
#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal
spring.redis.jedis.pool.max-active=600
#最大等待连接中的数量,设 0 为没有限制
spring.redis.jedis.pool.max-idle=8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
spring.redis.jedis.pool.max-wait=-1ms
#最小等待连接中的数量,设 0 为没有限制
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=30000
创建RedisConfig配置类
@Configuration
@Slf4j
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.min-idlel}")
private int minIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private int maxWaitMillis;
@Bean
public JedisPool jedisPoolFactory() {
log.info("JedisPool注入开始...");
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
// 连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认true
jedisPoolConfig.setBlockWhenExhausted(true);
// 是否启用pool的jmx管理功能, 默认true
jedisPoolConfig.setJmxEnabled(true);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);
log.info("JedisPool注入成功...");
return jedisPool;
}
}
Redis工具类RedisUtil
@Component
@Slf4j
public class RedisUtil {
@Autowired
private JedisPool jedisPool;
/**
* 通过key获取储存在redis中的value
* 并释放连接
*
* @param key
* @return 成功返回value 失败返回null
*/
public String get(String key) {
Jedis jedis = null;
String value = null;
try {
jedis = jedisPool.getResource();
value = jedis.get(key);
log.info(value);
} catch (Exception e) {
log.error(e.getMessage());
} finally {
jedis.close();
}
return value;
}
/**
* 设置key value,如果key已经存在则返回0,nx==> not exist
*
* @param key
* @param value
* @return 成功返回1 如果存在 和 发生异常 返回 0
*/
public Long setnx(String key, String value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.setnx(key, value);
} catch (Exception e) {
log.error(e.getMessage());
return 0L;
} finally {
jedis.close();
}
}
/**
* 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
*
* @param key
* @param value 过期时间,单位:秒
* @return 成功返回1 如果存在 和 发生异常 返回 0
*/
public Long expire(String key, int value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.expire(key, value);
} catch (Exception e) {
log.error(e.getMessage());
return 0L;
} finally {
jedis.close();
}
}
/**
* 删除指定的key,也可以传入一个包含key的数组
*
* @param keys 一个key 也可以使 string 数组
* @return 返回删除成功的个数
*/
public Long del(String... keys) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.del(keys);
} catch (Exception e) {
log.error(e.getMessage());
return 0L;
} finally {
jedis.close();
}
}
/**
* 设置key的值,并返回一个旧值
*
* @param key
* @param value
* @return 旧值 如果key不存在 则返回null
*/
public String getset(String key, String value) {
Jedis jedis = null;
String res = null;
try {
jedis = jedisPool.getResource();
res = jedis.getSet(key, value);
} catch (Exception e) {
log.error(e.getMessage());
} finally {
jedis.close();
}
return res;
}
}
分布式锁的具体代码
@Component
@Slf4j
public class CloseOrderTask {
//redis 分布式锁的key
private static final String DISTRIBUTED_LOCK = "DISTRIBUTED_LOCK";
//redis 分布式锁的超时时间
private static final long lockTimeout = 5000;
@Autowired
private RedisUtil redisUtil;
@Scheduled(cron = "0 */1 * * * ?")
public void closeOrderTask(){
log.info("------------关闭订单定时任务启动------------");
Long setnxResult = redisUtil.setnx(DISTRIBUTED_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
if(setnxResult != null && setnxResult == 1){
//如果返回值为1,代表设置成功,获取锁
this.setExpireAndDel(DISTRIBUTED_LOCK);
}else {
//未获取到锁,继续判断,判断时间戳,看是否可以重置并获取到锁,并且判断获取到的值是否为null
String lockValueStr = redisUtil.get(DISTRIBUTED_LOCK);
if(lockValueStr == null || (lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr))){
//锁不存在,那么可以直接getset
//锁存在,但是锁已经超时,那么用新的值替换旧的值,并返回旧的值
//再次用当前时间戳进行getset
//返回给定key的旧值,用旧值判断,是否可以获取锁
//当key没有旧值时,即key不存在时,返回nil,获取锁
//这里我们set了一个新的value值,获取旧的值
String getsetResult = redisUtil.getset(DISTRIBUTED_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
if(StringUtils.isEmpty(getsetResult) || (!StringUtils.isEmpty(getsetResult) && getsetResult.equals(lockValueStr))){
//真正获取到分布式锁
this.setExpireAndDel(DISTRIBUTED_LOCK);
}else {
log.info("没有获得分布式锁:{}",DISTRIBUTED_LOCK);
}
}else{
log.info("没有获得分布式锁:{}",DISTRIBUTED_LOCK);
}
}
log.info("------------关闭订单定时任务结束------------");
}
/**
* 设置分布式锁的有效期
* @param lockName
*/
private void setExpireAndDel(String lockName){
//有效期5秒,防止死锁
redisUtil.expire(lockName,50);
log.info("获取{},ThreadName:{}",lockName,Thread.currentThread().getName());
//执行相关业务。。。。
//业务执行完毕之后,释放分布式锁
redisUtil.del(lockName);
log.info("释放{},ThreadName:{}",lockName,Thread.currentThread().getName());
}
}
Redisson版分布式锁
Redisson介绍
- Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)
- Redisson在基于NIO的Netty框架上,充分利用了Redis键值数据库提供的一系列优势
- Redisson在Java实用工具包中常用接口基础上,为使用者提供了一系列具有分布式特性的常用工具类
- Redisson使得原本作为协调单机多线程开发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度
- Redisson同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作
引入maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot - Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 高版本redis的lettuce需要commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Redis相关配置
#redis集群的配置
spring.redis.cluster.nodes=192.168.159.129:7001,192.168.159.129:7002,192.168.159.129:7003,192.168.159.129:7004,192.168.159.129:7005,192.168.159.129:7006
spring.redis.cluster.max-redirects=3
spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=30000
#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal
spring.redis.lettuce.pool.max-active=600
#最大等待连接中的数量,设 0 为没有限制
spring.redis.lettuce.pool.max-idle=8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
spring.redis.lettuce.pool.max-wait=-1ms
#最小等待连接中的数量,设 0 为没有限制
spring.redis.lettuce.pool.min-idle=0
创建RedisConfig配置类
@Configuration
@Slf4j
public class RedissonManager {
@Value("${spring.redis.host}")
private String host;
private String password;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.lettuce.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.lettuce.pool.max-active}")
private int maxActive;
@Value("${spring.redis.lettuce.pool.min-idlel}")
private int minIdle;
@Value("${spring.redis.lettuce.pool.max-wait}")
private int maxWaitMillis;
@Value("${spring.redis.cluster.nodes}")
private String nodesStr;
/**
* 单机模式 redisson 客户端
*/
@Bean
public RedissonClient redissonSingle() {
Config config = new Config();
String singleAddress = "redis://" + host +":" + port;
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(singleAddress)
.setTimeout(timeout)
.setConnectionMinimumIdleSize(minIdle);
if (!StringUtils.isEmpty(password)) {
serverConfig.setPassword(password);
}
return Redisson.create(config);
}
/**
* 集群模式的 redisson 客户端
* @return
*/
@Bean
public Redisson redisson() {
//redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加
List<String> clusterNodes = new ArrayList<>();
String[] nodes = nodesStr.split(",");
for (int i = 0; i < nodes.length; i++) {
clusterNodes.add("redis://" + nodes[i]);
}
Config config = new Config();
ClusterServersConfig clusterServersConfig = config.useClusterServers()
.addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
if (!StringUtils.isEmpty(password)) {
clusterServersConfig.setPassword(password);//设置密码
}
return (Redisson) Redisson.create(config);
}
}
Redisson分布式锁的具体代码
@Component
@Slf4j
public class CloseOrderTask {
//redis 分布式锁的key
private static final String DISTRIBUTED_LOCK = "DISTRIBUTED_LOCK";
@Autowired
private RedissonClient redissonSingle;
@Scheduled(cron = "0 */1 * * * ?")
public void closeOrderTask(){
RLock rLock = redissonSingle.getLock(DISTRIBUTED_LOCK);
boolean getLock = false;
try {
//尝试获取锁,各线程在竞争锁的时候不等待,0秒,5秒释放锁,单位为秒
getLock = rLock.tryLock(0, 5, TimeUnit.SECONDS);
if(getLock){
log.info("Redisson获取到分布式锁:{},ThreadName:{}",DISTRIBUTED_LOCK,Thread.currentThread().getName());
//执行相关业务......
}else {
log.info("Redisson未获取到分布式锁:{},ThreadName:{}",DISTRIBUTED_LOCK,Thread.currentThread().getName());
}
} catch (InterruptedException e) {
log.error("Redisson获取到分布式锁异常:{}",e);
}finally {
if(!getLock){
return;
}
rLock.unlock();//释放锁
log.info("Redisson分布式释放");
}
}
}
RedissonUtil
@Component
public class RedissonUtil {
@Autowired
private Redisson redisson;
private static final String LOCK_TITLE = "redisLock_";
/**
* 加锁
* @param lockName
* @return
*/
public boolean acquire(String lockName){
//声明key对象
String key = LOCK_TITLE + lockName;
//获取锁对象
RLock mylock = redisson.getLock(key);
//加锁,并且设置锁过期时间3秒,防止死锁的产生 uuid+threadId
mylock.lock(3, TimeUnit.SECONDS);
//加锁成功
return true;
}
/**
* 锁的释放
* @param lockName
*/
public void release(String lockName) {
//必须是和加锁时的同一个key
String key = LOCK_TITLE + lockName;
//获取所对象
RLock mylock = redisson.getLock(key);
//释放锁(解锁)
mylock.unlock();
}
}
业务逻辑中使用分布式锁
@Service
@Slf4j
public class OrderService {
@Autowired
private RedissonUtil redissonUtil;
public String discount() {
String key = null;
try {
key = "lock001";
//加锁
redissonUtil.acquire(key);
//执行具体业务逻辑
dosoming();
} catch (Exception e) {
log.error("执行dosoming,Exception", e);
} finally {
//释放锁
redissonUtil.release(key);
}
//返回结果
return "soming";
}
}
参考:
github源码地址
redisson详细配置:https://blog.csdn.net/qq_33814088/article/details/83347757
https://blog.csdn.net/chuanchengdabing/article/details/121210426
https://blog.51cto.com/u_15175390/2929494
https://www.it610.com/article/1282673798079856640.htm