Redis如何做持久化的?

bgsave做全量持久化到RDB二进制文件中,aof做增量持久化,存储的是文本协议数据。

它们的优缺点呢?

rdb二进制文件启动加载速度可以更快,aof要重放命令,所以速度比较慢
rdb,aof
RDB 快
浪费空间 消费性能 会丢失数据
AOF 备份稳健 默认取AOF得
占用空间 存在bug
RDB:Redis默认的持久化机制,将内存数据库快照保存在名字为 dump.rdb 的二进制文件中.RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ 优点:(1)整个持久化文件只有一个,方便备份.(2)性能最大化,持久化采用fork子进程,避免了主进程的IO操作.(3).相比于AOF,持久化的数据加载快,启动效率高.

​ 缺点:(1)在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失.(2)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟

jvm分区
jvm分区

​ AOF:AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

​ 优点:(1) 该机制可以带来更高的数据安全性,即数据持久性。(2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容.(3).AOF包含一个格式清晰,易于理解的日志文件用于记录所有的修改操作.

​ 缺点:(1).AOF文件记录的所有操作过程,比rdb文件大,启动恢复速度比rdb慢.(2).根据同步策略的不同,AOF在运行效率上往往会慢于RDB.总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效.

jvm分区
jvm分区

Redis持久化期间,主进程还能对外提供服务吗?

那Redis如何处理新写入的数据呢,这个数据也会直接进行持久化吗?

。。。这个可能吧!

Reids可以设置最大内存大小,如果数据达到了内存最大限制,Redis如何处理呢?

可以配置淘汰策略 LRU 或者 LFU 淘汰策略。

Redis 的LRU算法实现原理,可以讲讲吗?

这个不太清楚。

图片

Redis 核心数据类型有哪些?

string, hash, list, set, zset.

存储数据用 string 类型 和 hash 类型,你是如何选择的呢?

string 对大量字段的对象中的某个数据进行获取,需要进行整体的数据获取,在客户端完成反序列化,而hash可以获取指定字段获取数据。所以根据访问需求来选择。

还有其他的考虑吗?

没有

zset 底层的实现原理有了解过吗?

1.有序集合对象的编码可以是ziplist或者skiplist。同时满足以下条件时使用ziplist编码:

元素数量小于128个
所有member的长度都小于64字节
其他:
不能满足上面两个条件的使用 skiplist 编码。以上两个条件也可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改
对于一个 REDIS_ENCODING_ZIPLIST 编码的 Zset, 只要满足以上任一条件, 则会被转换为 REDIS_ENCODING_SKIPLIST 编码
skiplist 编码的 Zset 底层为一个被称为 zset 的结构体,这个结构体中包含一个字典和一个跳跃表。跳跃表按 score 从小到大保存所有集合元素,查找时间复杂度为平均

O(logN),最坏 O(N
。字典则保存着从 member 到 score 的映射,这样就可以用 O(1) 的复杂度来查找 member 对应的 score 值。虽然同时使用两种结构,但它们会通过指针来共享相同元素的 member 和 score,因此不会浪费额外的内存。

你能讲讲它的实现原理以及时间复杂度分析吗?

这个不太清楚。

图片

你能说说缓存穿透是怎么回事吗?

缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

解决方案:
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

可以用布隆过滤器来阻挡。

布隆过滤器的实现原理是什么?能讲讲么?

AtomicInteger+CAS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194

//https://blog.csdn.net/u014653197/article/details/76397037
public class BloomFilter implements Serializable{

private final int[] seeds;
private final int size;
private final BitSet notebook;
private final MisjudgmentRate rate;
private final AtomicInteger useCount = new AtomicInteger();
private final Double autoClearRate;

//dataCount逾预期处理的数据规模
public BloomFilter(int dataCount){
this(MisjudgmentRate.MIDDLE, dataCount, null);
}

//自动清空过滤器内部信息的使用比率,传null则表示不会自动清理;
//当过滤器使用率达到100%时,则无论传入什么数据,都会认为在数据已经存在了;
//当希望过滤器使用率达到80%时自动清空重新使用,则传入0.8
public BloomFilter(MisjudgmentRate rate, int dataCount, Double autoClearRate){
//每个字符串需要的bit位数*总数据量
long bitSize = rate.seeds.length * dataCount;
if(bitSize<0 || bitSize>Integer.MAX_VALUE){
throw new RuntimeException("位数太大溢出了,请降低误判率或者降低数据大小");
}
this.rate = rate;
seeds = rate.seeds;
size = (int)bitSize;
//创建一个BitSet位集合
notebook = new BitSet(size);
this.autoClearRate = autoClearRate;
}

//如果存在返回true,不存在返回false
public boolean addIfNotExist(String data){
//是否需要清理
checkNeedClear();
//seeds.length决定每一个string对应多少个bit位,每一位都有一个索引值
//给定data,求出data字符串的第一个索引值index,如果第一个index值对应的bit=false说明,该data值不存在,则直接将所有对应bit位置为true即可;
//如果第一个index值对应bit=true,则将index值保存,但此时并不能说明data已经存在,
//则继续求解第二个index值,若所有index值都不存在则说明该data值不存在,将之前保存的index数组对应的bit位置为true
int[] indexs = new int[seeds.length];
//假定data已经存在
boolean exist = true;
int index;
for(int i=0; i<seeds.length; i++){
//计算位hash值
indexs[i] = index = hash(data, seeds[i]);
if(exist){
//如果某一位bit不存在,则说明该data不存在
if(!notebook.get(index)){
exist = false;
//将之前的bit位置为true
for(int j=0; j<=i; j++){
setTrue(indexs[j]);
}
}
}else{
//如果不存在则直接置为true
setTrue(index);
}
}

return exist;
}

private int hash(String data, int seeds) {
char[] value = data.toCharArray();
int hash = 0;
if(value.length>0){
for(int i=0; i<value.length; i++){
hash = i * hash + value[i];
}
}
hash = hash * seeds % size;
return Math.abs(hash);
}

private void setTrue(int index) {
useCount.incrementAndGet();
notebook.set(index, true);
}

//如果BitSet使用比率超过阈值,则将BitSet清零
private void checkNeedClear() {
if(autoClearRate != null){
if(getUseRate() >= autoClearRate){
synchronized (this) {
if(getUseRate() >= autoClearRate){
notebook.clear();
useCount.set(0);
}
}
}
}
}

private Double getUseRate() {
return (double)useCount.intValue()/(double)size;
}

public void saveFilterToFile(String path) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) {
oos.writeObject(this);
} catch (Exception e) {
throw new RuntimeException(e);
}

}

public static BloomFilter readFilterFromFile(String path) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) {
return (BloomFilter) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 清空过滤器中的记录信息
*/
public void clear() {
useCount.set(0);
notebook.clear();
}

public MisjudgmentRate getRate() {
return rate;
}

/**
* 分配的位数越多,误判率越低但是越占内存
*
* 4个位误判率大概是0.14689159766308
*
* 8个位误判率大概是0.02157714146322
*
* 16个位误判率大概是0.00046557303372
*
* 32个位误判率大概是0.00000021167340
*
*/
public enum MisjudgmentRate {
// 这里要选取质数,能很好的降低错误率
/**
* 每个字符串分配4个位
*/
VERY_SMALL(new int[] { 2, 3, 5, 7 }),
/**
* 每个字符串分配8个位
*/
SMALL(new int[] { 2, 3, 5, 7, 11, 13, 17, 19 }), //
/**
* 每个字符串分配16个位
*/
MIDDLE(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 }), //
/**
* 每个字符串分配32个位
*/
HIGH(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131 });

private int[] seeds;

//枚举类型MIDDLE构造函数将seeds数组初始化
private MisjudgmentRate(int[] seeds) {
this.seeds = seeds;
}

public int[] getSeeds() {
return seeds;
}

public void setSeeds(int[] seeds) {
this.seeds = seeds;
}
}

public static void main(String[] args) {
BloomFilter fileter = new BloomFilter(7);
System.out.println(fileter.addIfNotExist("1111111111111"));
System.out.println(fileter.addIfNotExist("2222222222222222"));
System.out.println(fileter.addIfNotExist("3333333333333333"));
System.out.println(fileter.addIfNotExist("444444444444444"));
System.out.println(fileter.addIfNotExist("5555555555555"));
System.out.println(fileter.addIfNotExist("6666666666666"));
System.out.println(fileter.addIfNotExist("1111111111111"));
//fileter.saveFilterToFile("C:\\Users\\john\\Desktop\\1111\\11.obj");
//fileter = readFilterFromFile("C:\\Users\\john\\Desktop\\111\\11.obj");
System.out.println(fileter.getUseRate());
System.out.println(fileter.addIfNotExist("1111111111111"));
}

}