之前用 redisTemplate 实现setnx exptime 时 是分两步的
这样的不是原子性的 可能在第一步与第二步之间 重新发布了或者服务器重启了 这个key就永远不会消失了 可以采用以下的方法
在spiring boot 1.5 版本 可以使用以下扩展
public static boolean setIfAbsent(final String key, final Serializable value, final long exptime) {
Boolean result = (Boolean) redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
RedisSerializer keySerializer = redisTemplate.getKeySerializer();
Object obj = connection.execute("set", keySerializer.serialize(key),
valueSerializer.serialize(value),
"NX".getBytes(StandardCharsets.UTF_8),
"EX".getBytes(StandardCharsets.UTF_8),
String.valueOf(exptime).getBytes(StandardCharsets.UTF_8));
return obj != null;
}
});
return result;
}
在spiring boot 2 可以直接使用 redisTemplate的
Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);
public boolean setIfAbsent(String key, String value, long expireTime) {
Boolean result = redisTemplate.opsForValue().setIfAbsent(key,value,expireTime, TimeUnit.SECONDS);
return result;
}
单机RedisLock的正确姿势 (集群用redisson):
加锁:
1. 通过setnx 向特定的key写入一个随机数,并设置失效时间,写入成功即加锁成功
2. 注意点:
- 必须给锁设置一个失效时间 -----> 避免死锁
- 加锁时,每个节点产生一个随机字符串 -----> 避免锁误删
- 写入随机数与设置失效时间必须是同时 -----> 保证加锁的原子性
使用:
SET key value NX PX 3000
解锁:
解锁:
匹配随机数,删除redis上的特定的key数据,
要保证获取数据,判断一致以及删除数据三个操作是原子性
执行如下lua脚本:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
伪代码示例:使用的是Jedis客户端
redis.clients
jedis
2.9.0
package com.redis.mq.util;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.UUID;
/**
* @author qinjp
* @date 2019-05-31
**/
public class RedisLock {
/**
* RedisLock的正确姿势
* 加锁:
* 通过setnx 向特定的key写入一个随机数,并设置失效时间,写入成功即加锁成功
* 注意点:
* 必须给锁设置一个失效时间 -----> 避免死锁
* 加锁时,每个节点产生一个随机字符串 -----> 避免锁误删
* 写入随机数与设置失效时间必须是同时 -----> 保证加锁的原子性
* 使用:
* SET key value NX PX 3000
*
* 解锁:
* 匹配随机数,删除redis上的特定的key数据,
* 要保证获取数据,判断一致以及删除数据三个操作是原子性
* 执行如下lua脚本:
* if redis.call('get', KEYS[1]) == ARGV[1] then
* return redis.call('del', KEYS[1])
* else
* return 0
* end
*
*/
// 使用jedis 客户端的
/**SET key value NX PX 3000 成功返回值*/
private static final String LOCK_SUCCESS = "OK";
/**表示 NX 模式*/
private static final String SET_IF_NOT_EXIST = "NX";
/**单位 毫秒**/
private static final String SET_WITH_EXPIRE_TIME_PX = "PX";
/**lua脚本**/
private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**存储随机数**/
private static final ThreadLocal local = new ThreadLocal<>();
/**
* 加锁
*/
public static boolean lock(Jedis jedis, String key, int expireTime) {
// 产生随机数
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
String result = jedis.set(key, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME_PX, expireTime);
if (LOCK_SUCCESS.equals(result)) {
// 随机数绑定线程
local.set(uuid);
return true;
}
return false;
}
/**
* 释放分布式锁
*/
public static boolean unLock(Jedis jedis, String key) {
String uuid = local.get();
//当前线程没有绑定uuid
//直接返回
if (uuid == null || "".equals(uuid)) {
return false;
}
Object result = jedis.eval(SCRIPT, Collections.singletonList(key), Collections.singletonList(uuid));
if (Long.valueOf(1).equals(result)) {
// 解除绑定线程的随机数
local.remove();
return true;
}
return false;
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("373616885");
jedis.select(0);
final String LOCK_KEY = "LOCK_KEY";
RedisLock.lock(jedis,LOCK_KEY,5000);
RedisLock.unLock(jedis,LOCK_KEY);
}
}