面试有时候会问道如何自己实现分布式锁
定义一个锁接口,定义两个抽象tryLock和unLock;
通过一个类实现这个接口。
基础版:
@Override
public boolean tryLock(long timeOutSec) {
//获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
KEY_PREFIX + name, threadId + "", timeOutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unLock() {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
为了防止出现锁误删进行修改。
在获取锁时存入线程标识,通过UUID,释放锁时获取锁中标识是否与线程标识一致,如果一致就可以释放锁。
public class SimpleRedisLock implements ILock {
private StringRedisTemplate stringRedisTemplate;
private String name;
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
@Override
public boolean tryLock(long timeOutSec) {
//获取线程标识。
long threadId = Thread.currentThread().getId();
//获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
KEY_PREFIX + name, threadId + "", timeOutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unLock() {
String threadId = ID_PREFIX + Thread.currentThread();
String ID = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
//判断标识是否一致,
if(threadId.equals(ID)){
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
}
但是对于锁判断和锁释放并不是一个原子性的操作,仍然会存在一些问题。继续进行改进.
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
@Override
public boolean tryLock(long timeOutSec) {
//获取线程标识。
String threadId = KEY_PREFIX + Thread.currentThread().getId();
//获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
KEY_PREFIX + name, threadId, timeOutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unLock() {
stringRedisTemplate.execute(UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
KEY_PREFIX + Thread.currentThread().getId());
}
2.redis持久化
RDB方式:使用bgsave,异步执行,fork主进程执行一个子进程共享内存。子进程读取内存存数据写入新的RDB文件,用新的RDB文件替换旧的RDB文件。
(fork采用copy-on-write,当主进程执行读操作,访问共享内存,主进程执行写操作时,会会拷贝一份数据,执行写操作)。
缺点:RDB执行周期较长,两次RDB之间会有数据丢失的风险
fork子进程、压缩、写出RDB文件都比较耗时。
AOF方式:
redis处理的每一个写命令操作都会记录在AOF文件,可以看作时命令日志文件。
有三种方式:
always :同步刷盘,可靠性高,基本不会丢失数据,性能影响最大。everysec(默认):每秒刷盘,性能适中,最多丢失一秒的数据。
no:执行命令写入AOF缓冲区,由操作系统决定何时将缓冲区内容写入磁盘。
缺点:记录过多写操作造成冗余。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
3.RDB和AOF的区别:
4.Redis的数据结构?
对于String类型的字符串:
基本的编码方式是RAW,基于简单动态字符串SDS实现,存储上限是512mb。
SDS是C语言中的结构体,包含了属性,len,alloc,flag(不同sds头类型),char buf[]。
如果存储的SDS长度小于44字节(抛出redisObject的),则会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。 如果存储的字符串是整数值,并且大小在LONG_MAX范围内,则会采用INT编码:直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。
对于Zset的底层数据结构:
满足键值存储,键必须唯一,可排序。
redisObject的ptr指向一个Zset对象,Zset对象中保存了两个指针,一个*dic字典,一个*zsl跳表,字典负责查找,跳表负责排序。
缺点是保存了两份数据,浪费了内存空间。简述skipList:
双向链表,数值按升序排列,节点有多个指针,指针的跨度不同。