數(shù)據(jù)庫面試題匯總--非關(guān)系型數(shù)據(jù)庫

1雷滚、redis 的底層數(shù)據(jù)結(jié)構(gòu)有哪些

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

2需曾、redis 中的 SDS 和 C 語言中的字符串有什么區(qū)別,優(yōu)點是什么

SDS(Simple Dynamic String揭措,簡單動態(tài)字符串)是 Redis 底層所使用的字符串表示胯舷。

SDS比C語言的字符串多了一個SDSHDR表頭,存放了free(空閑空間)绊含、len(已用空間)墓律、buf(緩沖區(qū))什荣。

優(yōu)點:
(1)獲取字符串長度更快。C語言獲取字符串需要遍歷整個字符串,時間復(fù)雜度為O(N)黑毅,SDS的表頭len已經(jīng)存放了已使用空間,獲取字符串長度只需要O(1)券盅。
(2)杜絕緩沖區(qū)溢出鞭执。C語言字符串本身不記錄字符串長度和已使用空間,容易造成緩沖區(qū)溢出伴找。SDS表頭free成員存放了空閑空間盈蛮,拼接字符串前會先通過free判斷剩余空間是否足夠,如果不夠就會進行擴容技矮。
(3)減少內(nèi)存分配次數(shù)抖誉。C語言對字符串進行增長或縮短操作殊轴,都要重新分配內(nèi)存。SDS使用空間預(yù)分配和惰性空間釋放策略袒炉,減少了內(nèi)存分配的次數(shù)旁理。
(4)二進制安全。C語言字符串遇0則止我磁,會對文件進行截斷孽文。SDS判斷字符串是否結(jié)尾的依據(jù)是表頭的len成員,不會改變二進制文件夺艰。

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

3芋哭、redis 中的字典是如何實現(xiàn)的,如何解決沖突和擴容

根據(jù)鍵值對的鍵計算哈希值和索引值劲适,然后根據(jù)索引值楷掉,將包含鍵值對的哈希節(jié)點存放在哈希數(shù)組的指定索引上。

解決沖突:redis采用鏈地址法解決沖突霞势,每個哈希節(jié)點有一個next指針烹植,多個哈希節(jié)點通過next指針構(gòu)成單向鏈表,總是將最新的節(jié)點添加到表頭愕贡。

擴容:redis的擴容通過rehash(重新散列)實現(xiàn)草雕,為字典ht[1]分配空間,ht[1]的大小為第一個大于等于ht[0].used * 2 的 2^n固以。

4墩虹、redis 的跳表的使用場景是什么,可以實現(xiàn)一下嗎

使用場景:有序列表憨琳。比如實時統(tǒng)計分?jǐn)?shù)排行榜诫钓。

java實現(xiàn)

// 跳表中存儲的是正整數(shù),并且存儲的數(shù)據(jù)是不重復(fù)的
public class SkipList {
    
    private static final int MAX_LEVEL = 16;    // 結(jié)點的個數(shù)
    
    private int levelCount = 1;   // 索引的層級數(shù)
    
    private Node head = new Node();    // 頭結(jié)點
    
    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];    // 找到篙螟,則返回原始鏈表中的結(jié)點
        }else{
            return null;
        }
    }
    
    // 插入操作
    public void insert(int value){
        int level = randomLevel();
        Node newNode = new Node();
        newNode.data = value;
        newNode.maxLevel = level;   // 通過隨機函數(shù)改變索引層的結(jié)點布置
        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];
                }
            }
        }
    }
    
    // 隨機函數(shù)
    private int randomLevel(){
        int level = 1;
        for(int i = 1; i < MAX_LEVEL; ++i){
            if(random.nextInt() % 2 == 1){
                level++;
            }
        }
        
        return level;
    }
    
    // Node內(nèi)部類
    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();
        }
    }
    
    // 顯示跳表中的結(jié)點
    public void display(){
        Node p = head;
        while(p.next[0] != null){
            System.out.println(p.next[0] + " ");
            p = p.next[0];
        }
        System.out.println();
    }   
}

數(shù)據(jù)結(jié)構(gòu)與算法——跳表:http://www.reibang.com/p/fef9817cc943

5菌湃、redis 緩存穿透,緩存擊穿遍略,緩存雪崩惧所,熱點數(shù)據(jù)集中失效 (常問)

(1)緩存穿透:在緩存層和數(shù)據(jù)庫層,都沒有找到數(shù)據(jù)绪杏。解決方案1:把空數(shù)據(jù)存儲起來下愈,并設(shè)置過期時間。解決方案2:使用布隆過濾器蕾久。
(2)緩存擊穿:緩存中的數(shù)據(jù)在某個時刻大批量過期势似,導(dǎo)致大部分的請求都會直接落在數(shù)據(jù)庫上。解決方案1:針對不同類型的數(shù)據(jù),設(shè)置不同的過期時間履因。解決方案2:使用分布式鎖辖佣。
(3)緩存雪崩:緩存在某一時刻集中失效,或者緩存系統(tǒng)出現(xiàn)故障搓逾,所有的并發(fā)流量會直接到達數(shù)據(jù)庫。解決方案1:保證redis的高可用杯拐,使用集群部署霞篡。解決方案2:降流限級。
(4)熱點數(shù)據(jù)集中失效:類似于緩存擊穿端逼。熱點數(shù)據(jù)同時失效朗兵,造成都去查數(shù)據(jù)庫。解決方案:利用集群部署顶滩,保證redis的高可用余掖。

6、redis 的淘汰策略礁鲁,來寫一下 LRU 吧

(1)noeviction:當(dāng)內(nèi)存使用超過配置的時候會返回錯誤盐欺,不會驅(qū)逐任何鍵
(2)allkeys-lru:加入鍵的時候,如果過限仅醇,首先通過LRU算法驅(qū)逐最久沒有使用的鍵
(3)volatile-lru:加入鍵的時候冗美,如果過限,首先從設(shè)置了過期時間的鍵集合中驅(qū)逐最久沒有使用的鍵
(4)allkeys-random:加入鍵的時候析二,如果過限粉洼,從所有key隨機刪除
(5)volatile-random:加入鍵的時候,如果過限叶摄,從過期鍵的集合中隨機驅(qū)逐
(6)volatile-ttl:從配置了過期時間的鍵中驅(qū)逐馬上就要過期的鍵
(7)volatile-lfu:從所有配置了過期時間的鍵中驅(qū)逐使用頻率最少的鍵
(8)allkeys-lfu:從所有鍵中驅(qū)逐使用頻率最少的鍵

實現(xiàn)

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:將某一時刻的內(nèi)存快照蛤吓,以二進制的方式寫入磁盤
(2)AOF:將redis的操作日志以追加的方式寫入文件

使用場景
(1)如果追求備份的速度宵喂,可以忽略部分?jǐn)?shù)據(jù)丟失,推薦使用RDB
(2)如果追求數(shù)據(jù)的完整性柱衔,可以接受犧牲備份的速度樊破,推薦使用AOF

8、redis 如何處理事務(wù)

redis使用MULTI唆铐、EXEC哲戚、DISCARD、WATCH四個事務(wù)命令

使用MULTI開啟事務(wù)艾岂,客戶端可以向服務(wù)器發(fā)送任意多個命令顺少,這些命令不會立馬執(zhí)行,而是被放到一個隊列中,當(dāng)調(diào)用EXEC命令時脆炎,所有隊列中的命令才會被執(zhí)行梅猿。如果在執(zhí)行事務(wù)的過程發(fā)生異常,而沒有執(zhí)行EXEC秒裕,那么事務(wù)中的所有命令都不會被執(zhí)行袱蚓。至于在執(zhí)行EXEC命令之后發(fā)生了錯誤,及時事務(wù)中某個命令發(fā)生了錯誤几蜻,其他事務(wù)也會正常執(zhí)行喇潘,沒有回滾操作

通過調(diào)用DISCARD,客戶端可以清空事務(wù)隊列梭稚,并放棄執(zhí)行事務(wù)

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

9颖低、redis 為什么那么快?

(1)采用多路復(fù)用IO阻塞機制
(2)數(shù)據(jù)結(jié)構(gòu)簡單弧烤,操作節(jié)省時間
(3)運行在內(nèi)存中

10忱屑、redis 是單線程為什么還那么快?

(1)采用了非阻塞IO多路復(fù)用機制
(2)單線程操作暇昂,避免了頻繁的上下文切換
(3)運行在內(nèi)存中

11莺戒、redis 的操作為什么是原子性的,如何保證原子性

因為redis是單線程的

對于redis來說话浇,執(zhí)行get脏毯、set以及eval等api,都是一個一個的任務(wù)幔崖,這些任務(wù)都會由redis的線程去負(fù)責(zé)執(zhí)行食店,任務(wù)要么執(zhí)行成功,要么執(zhí)行失敗

12赏寇、redis 集群用過哪些方案吉嫩,分別怎么做。講一下一致性哈希

集群方案

(1)主從模式嗅定。只需要在配置文件加上slaveof 192.169.x.x 6379自娩,指明master的ip和端口號

(2)哨兵模式。給幾臺哨兵服務(wù)添加不同的端口渠退,配置主服務(wù)器的ip和端口忙迁,并且加上權(quán)值,使用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嗅回,默認(rèn)是yes,后臺運行:daemonize yes摧茴,輸出日志:logfile "./redis.log"绵载,監(jiān)聽端口:port 7000,拷貝同樣的配置文件苛白,修改端口

13尘分、redis 什么情況下會出現(xiàn)性能問題,有什么處理辦法丸氛?

(1)master寫內(nèi)存快照,save命令調(diào)度rdbSave函數(shù)著摔,會阻塞主線程的工作缓窜,當(dāng)快照比較大時對性能影響是非常大的,會間斷性暫停服務(wù)谍咆,所以Master最好不要寫內(nèi)存快照禾锤。

(2)Master AOF持久化,如果不重寫AOF文件摹察,這個持久化方式對性能的影響是最小的恩掷,但是AOF文件會不斷增大,AOF文件過大會影響Master重啟的恢復(fù)速度供嚎。Master最好不要做任何持久化工作黄娘,包括內(nèi)存快照和AOF日志文件,特別是不要啟用內(nèi)存快照做持久化,如果數(shù)據(jù)比較關(guān)鍵克滴,某個Slave開啟AOF備份數(shù)據(jù)逼争,策略為每秒同步一次。

(3)Master調(diào)用BGREWRITEAOF重寫AOF文件劝赔,AOF在重寫的時候會占大量的CPU和內(nèi)存資源誓焦,導(dǎo)致服務(wù)load過高,出現(xiàn)短暫服務(wù)暫妥琶保現(xiàn)象杂伟。

(4)Redis主從復(fù)制的性能問題,為了主從復(fù)制的速度和連接的穩(wěn)定性仍翰,Slave和Master最好在同一個局域網(wǎng)內(nèi)

14赫粥、有沒有使用過 redis 的分布式鎖,有什么優(yōu)缺點

優(yōu)點:
(1)redis性能很好
(2)redis對命令的支持較好歉备,實現(xiàn)起來比較方便

缺點:
(1)redis分布式鎖傅是,需要自己不斷嘗試去獲取鎖,比較消耗性能
(2)redis獲取鎖的那個客戶端掛了,只能等待超時時間后才能釋放鎖

15喧笔、說一下 redis 的內(nèi)存模型

(1)數(shù)據(jù)
(2)redis主進程本身運行需要占用的內(nèi)存帽驯。如代碼、常量池等
(3)緩沖內(nèi)存书闸。
(4)內(nèi)存碎片尼变。redis在分配、回收物理內(nèi)存過程中產(chǎn)生

16浆劲、說一下 redis 和 memcache 的區(qū)別

(1)memcache僅支持簡單的key-value數(shù)據(jù)結(jié)構(gòu)嫌术,redis支持字符串、哈希牌借、鏈表度气、set集合,有序set集合
(2)redis不是所有的數(shù)據(jù)都存儲在內(nèi)存膨报,會將一些很久沒用的value交換到磁盤
(3)redis采用的單核磷籍,memcache可以采用多核,在存儲小數(shù)據(jù)的時候现柠,redis性能更好院领,而在大數(shù)據(jù)的時候,memcache性能更好
(4)redis集群可以采用一主多從够吩,一主一從比然。memcache集群只能采用一主多從
(5)redis支持?jǐn)?shù)據(jù)恢復(fù)。memcache不支持

17周循、你用 redis 做過什么强法?(這里盡量不要講只做過緩存,可以說一下隊列湾笛,排行榜/計數(shù)器拟烫,發(fā)布/訂閱)

(1)緩存
(2)商品銷量排行榜
(3)消息隊列

18、你用過哪些非關(guān)系型數(shù)據(jù)庫迄本,都有什么特點硕淑,使用場景分別是什么(體現(xiàn)你技術(shù)廣- 度的時刻到了,盡可能多說嘉赎,但是不會的不要說置媳,防止被問死)

(1)redis:可以通過key來添加、查詢或者刪除數(shù)據(jù)公条,鑒于使用主鍵訪問拇囊,所以會獲得不錯的性能及擴展性。適用于儲存用戶信息靶橱,比如會話寥袭、配置文件路捧、參數(shù)、購物車等等传黄。
(2)MongoDB:將數(shù)據(jù)以文檔的形式儲存杰扫。適用于日志、分析
(3)HBase:將數(shù)據(jù)儲存在列族中膘掰,一個列族存儲經(jīng)常被一起查詢的相關(guān)數(shù)據(jù)章姓。適用于日志、博客平臺

19识埋、Mongodb 相對于 Mysql 有哪些優(yōu)勢凡伊,底層索引使用的數(shù)據(jù)結(jié)構(gòu)是什么,為什么要使用這個

(1)Mongod的訪問速度優(yōu)于MySQL
(2)Mongodb獲取數(shù)據(jù)的方式比MySQL快捷
(3)Mongodb的表的擴展窒舟、刪除系忙、維護簡單,不依賴于SQL
(4)Mongodb對開發(fā)更友好惠豺,拿到的數(shù)據(jù)都是json格式
(5)Mongodb會將系統(tǒng)內(nèi)存作為緩存
(6)Mongodb自帶分布式文件系統(tǒng)笨觅,可以很方便部署在服務(wù)器集群上

Mongodb索引數(shù)據(jù)結(jié)構(gòu)為B-Tree

為什么要使用這個數(shù)據(jù)結(jié)構(gòu),參考文章:https://blog.csdn.net/u012939368/article/details/76696673

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

MongoDB分片集群將數(shù)據(jù)分布在一個或多個分片上。每個分片部署成一個MongoDB副本集杀糯,該副本集保存了集群整體數(shù)據(jù)的一部分扫俺。因為每個分片都是一個副本集,所以他們擁有自己的復(fù)制機制固翰,能夠自動進行故障轉(zhuǎn)移狼纬。你可以直接連接單個分片,就像連接單獨的副本集一樣骂际。但是疗琉,如果連接的副本集是分片集群的一部分,那么只能看到部分?jǐn)?shù)據(jù)歉铝。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盈简,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子太示,更是在濱河造成了極大的恐慌柠贤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件类缤,死亡現(xiàn)場離奇詭異臼勉,居然都是意外死亡,警方通過查閱死者的電腦和手機餐弱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門宴霸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囱晴,“玉大人,你說我怎么就攤上這事瓢谢』矗” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵恩闻,是天一觀的道長艺糜。 經(jīng)常有香客問我,道長幢尚,這世上最難降的妖魔是什么破停? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮尉剩,結(jié)果婚禮上真慢,老公的妹妹穿的比我還像新娘。我一直安慰自己理茎,他們只是感情好黑界,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著皂林,像睡著了一般朗鸠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上础倍,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天烛占,我揣著相機與錄音,去河邊找鬼沟启。 笑死忆家,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的德迹。 我是一名探鬼主播芽卿,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胳搞!你這毒婦竟也來了卸例?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤肌毅,失蹤者是張志新(化名)和其女友劉穎币厕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芽腾,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡旦装,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了摊滔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阴绢。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡店乐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呻袭,到底是詐尸還是另有隱情眨八,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布左电,位于F島的核電站廉侧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏篓足。R本人自食惡果不足惜段誊,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望栈拖。 院中可真熱鬧连舍,春花似錦、人聲如沸涩哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贴彼。三九已至潜腻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間器仗,已是汗流浹背融涣。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留青灼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓妓盲,卻偏偏與公主長得像杂拨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悯衬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容