数据库面试题汇总--非关系型数据库

1、redis 的底层数据结构有哪些

(1)字符串
(2)哈希
(3)链表
(4)set集合
(5)zset有序集合

2、redis 中的 SDS 和 C 语言中的字符串有什么区别,优点是什么

SDS(Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示。

SDS比C语言的字符串多了一个SDSHDR表头,存放了free(空闲空间)、len(已用空间)、buf(缓冲区)

优点:
(1)获取字符串长度更快。C语言获取字符串需要遍历整个字符串,时间复杂度为O(N),SDS的表头len已经存放了已使用空间,获取字符串长度只需要O(1)。
(2)杜绝缓冲区溢出。C语言字符串本身不记录字符串长度和已使用空间,容易造成缓冲区溢出。SDS表头free成员存放了空闲空间,拼接字符串前会先通过free判断剩余空间是否足够,如果不够就会进行扩容。
(3)减少内存分配次数。C语言对字符串进行增长或缩短操作,都要重新分配内存。SDS使用空间预分配和惰性空间释放策略,减少了内存分配的次数。
(4)二进制安全。C语言字符串遇0则止,会对文件进行截断。SDS判断字符串是否结尾的依据是表头的len成员,不会改变二进制文件。

SDS:https://redisbook.readthedocs.io/en/latest/internal-datastruct/sds.html

3、redis 中的字典是如何实现的,如何解决冲突和扩容

根据键值对的键计算哈希值和索引值,然后根据索引值,将包含键值对的哈希节点存放在哈希数组的指定索引上。

解决冲突:redis采用链地址法解决冲突,每个哈希节点有一个next指针,多个哈希节点通过next指针构成单向链表,总是将最新的节点添加到表头。

扩容:redis的扩容通过rehash(重新散列)实现,为字典ht[1]分配空间,ht[1]的大小为第一个大于等于ht[0].used * 2 的 2^n。

4、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;
		newNode.maxLevel = level;   // 通过随机函数改变索引层的结点布置
		Node update[] = new Node[level];
		for(int i = 0; i < level; ++i){
			update[i] = head;
		}
		
        Node p = head;
        for(int i = level - 1; i >= 0; --i){
        	while(p.next[i] != null && p.next[i].data < value){
        		p = p.next[i];
        	}
        	update[i] = p;
        }
        
        for(int i = 0; i < level; ++i){
        	newNode.next[i] = update[i].next[i];
        	update[i].next[i] = newNode;
        }
        if(levelCount < level){
        	levelCount = level;
        }
	}
	
	// 删除操作
	public void delete(int value){
		Node[] update = new Node[levelCount];
		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];
			}
			update[i] = p;
		}
		
		if(p.next[0] != null && p.next[0].data == value){
			for(int i = levelCount - 1; i >= 0; --i){
				if(update[i].next[i] != null && update[i].next[i].data == value){
					update[i].next[i] = update[i].next[i].next[i];
				}
			}
		}
	}
	
	// 随机函数
	private int randomLevel(){
		int level = 1;
		for(int i = 1; i < MAX_LEVEL; ++i){
			if(random.nextInt() % 2 == 1){
				level++;
			}
		}
		
		return level;
	}
	
	// Node内部类
	public class Node{
		private int data = -1;
		private Node next[] = new Node[MAX_LEVEL];
		private int maxLevel = 0;
		
		// 重写toString方法
		@Override
		public String toString(){
			StringBuilder builder = new StringBuilder();
			builder.append("{data:");
			builder.append(data);
			builder.append("; leves: ");
			builder.append(maxLevel);
			builder.append(" }");
			return builder.toString();
		}
	}
	
	// 显示跳表中的结点
	public void display(){
		Node p = head;
		while(p.next[0] != null){
			System.out.println(p.next[0] + " ");
			p = p.next[0];
		}
		System.out.println();
	}	
}

数据结构与算法——跳表:https://www.jianshu.com/p/fef9817cc943

5、redis 缓存穿透,缓存击穿,缓存雪崩,热点数据集中失效 (常问)

(1)缓存穿透:在缓存层和数据库层,都没有找到数据。解决方案1:把空数据存储起来,并设置过期时间。解决方案2:使用布隆过滤器。
(2)缓存击穿:缓存中的数据在某个时刻大批量过期,导致大部分的请求都会直接落在数据库上。解决方案1:针对不同类型的数据,设置不同的过期时间。解决方案2:使用分布式锁。
(3)缓存雪崩:缓存在某一时刻集中失效,或者缓存系统出现故障,所有的并发流量会直接到达数据库。解决方案1:保证redis的高可用,使用集群部署。解决方案2:降流限级。
(4)热点数据集中失效:类似于缓存击穿。热点数据同时失效,造成都去查数据库。解决方案:利用集群部署,保证redis的高可用。

6、redis 的淘汰策略,来写一下 LRU 吧

(1)noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
(2)allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
(3)volatile-lru:加入键的时候,如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
(4)allkeys-random:加入键的时候,如果过限,从所有key随机删除
(5)volatile-random:加入键的时候,如果过限,从过期键的集合中随机驱逐
(6)volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
(7)volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
(8)allkeys-lfu:从所有键中驱逐使用频率最少的键

实现

public class LRU<K,V> {
 
  private static final float hashLoadFactory = 0.75f;
  private LinkedHashMap<K,V> map;
  private int cacheSize;
 
  public LRU(int cacheSize) {
    this.cacheSize = cacheSize;
    int capacity = (int)Math.ceil(cacheSize / hashLoadFactory) + 1;
    map = new LinkedHashMap<K,V>(capacity, hashLoadFactory, true){
      private static final long serialVersionUID = 1;
 
      @Override
      protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > LRU.this.cacheSize;
      }
    };
  }
 
  public synchronized V get(K key) {
    return map.get(key);
  }
 
  public synchronized void put(K key, V value) {
    map.put(key, value);
  }
 
  public synchronized void clear() {
    map.clear();
  }
 
  public synchronized int usedSize() {
    return map.size();
  }
 
  public void print() {
    for (Map.Entry<K, V> entry : map.entrySet()) {
      System.out.print(entry.getValue() + "--");
    }
    System.out.println();
  }
}
7、redis 的持久化方式,RDB 和 AOF 分别的使用场景

持久化方式
(1)RDB:将某一时刻的内存快照,以二进制的方式写入磁盘
(2)AOF:将redis的操作日志以追加的方式写入文件

使用场景
(1)如果追求备份的速度,可以忽略部分数据丢失,推荐使用RDB
(2)如果追求数据的完整性,可以接受牺牲备份的速度,推荐使用AOF

8、redis 如何处理事务

redis使用MULTI、EXEC、DISCARD、WATCH四个事务命令

使用MULTI开启事务,客户端可以向服务器发送任意多个命令,这些命令不会立马执行,而是被放到一个队列中,当调用EXEC命令时,所有队列中的命令才会被执行。如果在执行事务的过程发生异常,而没有执行EXEC,那么事务中的所有命令都不会被执行。至于在执行EXEC命令之后发生了错误,及时事务中某个命令发生了错误,其他事务也会正常执行,没有回滚操作

通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务

WATCH命令可以为redis提供CAS行为

9、redis 为什么那么快?

(1)采用多路复用IO阻塞机制
(2)数据结构简单,操作节省时间
(3)运行在内存中

10、redis 是单线程为什么还那么快?

(1)采用了非阻塞IO多路复用机制
(2)单线程操作,避免了频繁的上下文切换
(3)运行在内存中

11、redis 的操作为什么是原子性的,如何保证原子性

因为redis是单线程的

对于redis来说,执行getset以及eval等api,都是一个一个的任务,这些任务都会由redis的线程去负责执行,任务要么执行成功,要么执行失败

12、redis 集群用过哪些方案,分别怎么做。讲一下一致性哈希

集群方案

(1)主从模式。只需要在配置文件加上slaveof 192.169.x.x 6379,指明master的ip和端口号

(2)哨兵模式。给几台哨兵服务添加不同的端口,配置主服务器的ip和端口,并且加上权值,使用Redis命令启动哨兵,./redis-sentinel sentinel-26380.conf

(3)cluster集群模式。配置集群地址,开启cluster,cluster-enable yes,集群配置文件:cluster-config-file nodes-7000.conf,这个可以不修改,集群超时时间:cluster-node-timeout 15000,槽是否全覆盖:cluster-require-full-coverage no,默认是yes,后台运行:daemonize yes,输出日志:logfile "./redis.log",监听端口:port 7000,拷贝同样的配置文件,修改端口

13、redis 什么情况下会出现性能问题,有什么处理办法?

(1)master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

(2)Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

(3)Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

(4)Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

14、有没有使用过 redis 的分布式锁,有什么优缺点

优点:
(1)redis性能很好
(2)redis对命令的支持较好,实现起来比较方便

缺点:
(1)redis分布式锁,需要自己不断尝试去获取锁,比较消耗性能
(2)redis获取锁的那个客户端挂了,只能等待超时时间后才能释放锁

15、说一下 redis 的内存模型

(1)数据
(2)redis主进程本身运行需要占用的内存。如代码、常量池等
(3)缓冲内存。
(4)内存碎片。redis在分配、回收物理内存过程中产生

16、说一下 redis 和 memcache 的区别

(1)memcache仅支持简单的key-value数据结构,redis支持字符串、哈希、链表、set集合,有序set集合
(2)redis不是所有的数据都存储在内存,会将一些很久没用的value交换到磁盘
(3)redis采用的单核,memcache可以采用多核,在存储小数据的时候,redis性能更好,而在大数据的时候,memcache性能更好
(4)redis集群可以采用一主多从,一主一从。memcache集群只能采用一主多从
(5)redis支持数据恢复。memcache不支持

17、你用 redis 做过什么?(这里尽量不要讲只做过缓存,可以说一下队列,排行榜/计数器,发布/订阅)

(1)缓存
(2)商品销量排行榜
(3)消息队列

18、你用过哪些非关系型数据库,都有什么特点,使用场景分别是什么(体现你技术广- 度的时刻到了,尽可能多说,但是不会的不要说,防止被问死)

(1)redis:可以通过key来添加、查询或者删除数据,鉴于使用主键访问,所以会获得不错的性能及扩展性。适用于储存用户信息,比如会话、配置文件、参数、购物车等等。
(2)MongoDB:将数据以文档的形式储存。适用于日志、分析
(3)HBase:将数据储存在列族中,一个列族存储经常被一起查询的相关数据。适用于日志、博客平台

19、Mongodb 相对于 Mysql 有哪些优势,底层索引使用的数据结构是什么,为什么要使用这个

(1)Mongod的访问速度优于MySQL
(2)Mongodb获取数据的方式比MySQL快捷
(3)Mongodb的表的扩展、删除、维护简单,不依赖于SQL
(4)Mongodb对开发更友好,拿到的数据都是json格式
(5)Mongodb会将系统内存作为缓存
(6)Mongodb自带分布式文件系统,可以很方便部署在服务器集群上

Mongodb索引数据结构为B-Tree

为什么要使用这个数据结构,参考文章:https://blog.csdn.net/u012939368/article/details/76696673

20、Mongodb 中的分片是什么意思

MongoDB分片集群将数据分布在一个或多个分片上。每个分片部署成一个MongoDB副本集,该副本集保存了集群整体数据的一部分。因为每个分片都是一个副本集,所以他们拥有自己的复制机制,能够自动进行故障转移。你可以直接连接单个分片,就像连接单独的副本集一样。但是,如果连接的副本集是分片集群的一部分,那么只能看到部分数据。

参考文章:https://blog.csdn.net/wanght89/article/details/77842336

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值