Redis 高级使用
生成全局 id
id 使用Long 类型 ,8个字节 , 64 bit
ID的组成部分:
符号位: 1bit,永远为0
时间戳:31bit,以秒为单位,可以使用69年
序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID
防止用户从id上推断出业务相关信息 ,为了增加ID的安全性,可以不直接使用Redis自增的数值,而是拼接一些其它信息 , 例如时间戳
@Component
public class RedisIdUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 计算的开始时间 可以自定义
private static final Long BEGIN_TIMESTAMP = 000L ;
private static final Integer COUNT_BITS = 32 ;
public long generateId(String keyPrefix) {
// 1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序列号
// 2.1.获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 2.2.自增长
long count = stringRedisTemplate.opsForValue().increment( "icr:" + keyPrefix + ":" + date);
// 3.拼接并返回
return timestamp << COUNT_BITS | count;
}
}
实现分布式锁
Lua 脚本
if(redis.call('get', KEYS[1]) ==ARGV[1]) then
-- 郑放锁 del key
end
return redis.call('del', KEYS[1])
return 0
使用
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Author: tang
* @Date: 2022/12/8
* @Description:
*/
public class RedisLockUtil {
private String name ;
private StringRedisTemplate stringRedisTemplate;
public RedisLockUtil(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
private static final String KEY_PREFIX = "lock:";
// 集群模式下 设置不同的jvm标识
private static final String ID_PREFIX = UUID.randomUUID().toString()+"-" ;
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);
}
public boolean tryLock(long timeoutSec) {
// 获取线程标识 防止锁超时后 误删
String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent( KEY_PREFIX +name , threadId , timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public void unlock() {
//限用Lua脚本 确保原子性操作
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId());
/* // 获取线程标示 防止锁超时后 误删
String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中的标示
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标示是否一致
if(threadId.equals(id))
if(redis.call('get', KEYS[1]) ==ARGV[1]) then
-- 郑放锁 del key
end
return redis.call('del', KEYS[1])
return 0
// 释放锁
stringRedisTemplate .delete( KEY_PREFIX + name);*/
}
}
存在的问题:
不可重入
同一个线程无法多次获取同一把锁
不可重试
获取锁只尝试一次就返回false,没有重试机制
超时释放
锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患
主从一致性
如果Redis提供了主从集群主从同步存在延迟,当主岩机时,如果从并同步主中的锁数据,则会出现锁实现