本文答案整理为原创,转载请说明出处
文章目录
- redis 的底层数据结构有哪些
- redis 中的 SDS 和 C 语言中的字符串有什么区别,优点是什么
- redis 中的字典是如何实现的,如何解决冲突和扩容
- redis 的跳表的使用场景是什么,可以实现一下吗
- redis 缓存穿透,缓存击穿,缓存雪崩,热点数据集中失效
- redis 的淘汰策略,来写一下 LRU 吧
- redis 的持久化方式,RDB 和 AOF 分别的使用场景
- redis 如何处理事务
- redis 为什么那么快?
- redis 是单线程为什么还那么快?
- redis 的操作为什么是原子性的,如何保证原子性
- redis 集群用过哪些方案,分别怎么做。讲一下一致性哈希
- redis 什么情况下会出现性能问题,有什么处理办法?
- 有没有使用过 redis 的分布式锁,有什么优缺点
- 说一下 redis 的内存模型
- 说一下 redis 和 memcache 的区别
- 你用 redis 做过什么?
- 你用过哪些非关系型数据库,都有什么特点,使用场景分别是什么
- Mongodb 相对于 Mysql 有哪些优势,底层索引使用的数据结构是什么,为什么要使用这个
- Mongodb 中的分片是什么意思
题目来源: 2020 字节跳动 数据库面试题汇总
题目列表
- redis 的底层数据结构有哪些
- redis 中的 SDS 和 C 语言中的字符串有什么区别,优点是什么
- redis 中的字典是如何实现的,如何解决冲突和扩容
- redis 的跳表的使用场景是什么,可以实现一下吗
- redis 缓存穿透,缓存击穿,缓存雪崩,热点数据集中失效 (常问)
- redis 的淘汰策略,来写一下 LRU 吧
- redis 的持久化方式,RDB 和 AOF 分别的使用场景
- redis 如何处理事务
- redis 为什么那么快?
- redis 是单线程为什么还那么快?
- redis 的操作为什么是原子性的,如何保证原子性
- redis 集群用过哪些方案,分别怎么做。讲一下一致性哈希
- redis 什么情况下会出现性能问题,有什么处理办法?
- 有没有使用过 redis 的分布式锁,有什么优缺点
- 说一下 redis 的内存模型
- 说一下 redis 和 memcache 的区别
- 你用 redis 做过什么?(这里尽量不要讲只做过缓存,可以说一下队列,排行榜/计数器,发布/订阅)
- 你用过哪些非关系型数据库,都有什么特点,使用场景分别是什么(体现你技术广度的时刻到了,尽可能多说,* 但是不会的不要说,防止被问死)
- Mongodb 相对于 Mysql 有哪些优势,底层索引使用的数据结构是什么,为什么要使用这个
- Mongodb 中的分片是什么意思
redis 的底层数据结构有哪些
- 字符串
- 哈希
- 链表
- set 集合
- zset 有序集合
redis 中的 SDS 和 C 语言中的字符串有什么区别,优点是什么
SDS比C语言的字符串多了一个SDSHDR表头。里面存放free(空闲空间)、len(已用空间)、buf(缓冲区)
优点:
- 获取字符串长度更快。C语言获取字符串长度需要遍历整个字符串,时间复杂度为O(N),SDS的表头
len
成员存放了已使用空间,获取字符串长度复杂度为O(1) - 杜绝缓冲区溢出。C语言字符串本身不记录自身长度和空闲空间,容易造成缓冲区溢出。SDS表头
free
成员存放了空闲空间,拼接字符串前会先通过free
字段检测剩余空间是否能满足,如果空间不够就会扩容 - 减少内存分配次数。C语言对字符串进行增长或缩短操作,都需要重新分配内存。SDS使用了空间预分配和惰性空间释放策略,减少了内存分配次数
- 二进制安全。C语言字符串遇0则止,会对文件进行截断。SDS判断字符串是否结尾的依据是表头的
len
成员,不会改变二进制文件。
redis 中的字典是如何实现的,如何解决冲突和扩容
根据键值对的键计算哈希值和索引值,然后根据索引值,将包含键值对的哈希节点放到哈希数组的指定索引上
如何解决冲突:
redis采用链地址法解决键冲突。每个哈希节点有一个next指针,多个哈希节点通过next指针构成一个单向链表。总是将最新的节点添加到表头
扩容:
redis的扩容通过rehash(重新散列)
实现,为字典ht[1]
分配空间,ht[1]
的大小为第一个大于等于ht[0].used * 2
的 2n
redis 的跳表的使用场景是什么,可以实现一下吗
使用场景:有序列表。如实时统计分数排行榜
java实现:
// 跳表中存储的是正整数,并且存储的数据是不重复的
public class SkipList {
private static final int MAX_LEVEL = 16; // 结点的个数
private int levelCount = 1; // 索引的层级数
private Node head = new Node(); // 头结点
private Random random = new Random();
// 查找操作
public Node find(int value){
Node p = head;
for(int i = levelCount - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
}
if(p.next[0] != null && p.next[0].data == value){
return p.next[0]; // 找到,则返回原始链表中的结点
}else{
return null;
}
}
// 插入操作
public void insert(int value){
int level = randomLevel();
Node newNode = new Node();
newNode.data = value;