面試遇到的redis相關問題

xiaohei_xiaobai發表於2020-11-10

1.redis持久化機制

1)實現:單獨建立fork()一個子程式,將當前父程式的資料庫資料複製到子程式的記憶體中,然後由子程式寫入到臨時檔案中,持久化的過程結束了,
        再用這個臨時檔案替換上次的快照檔案,然後子程式退出,記憶體釋放。
2)RDB(預設)(體積小,速度快,丟資料):按照一定的時間週期策略把記憶體的資料以快照的形式儲存到硬碟的二進位制檔案。即Snapshot快照儲存,對應產生的資料檔案為dump.rdb,
              通過配置檔案中的save引數來定義快照的週期。(快照可以是其所表示的資料的一個副本,也可以是資料的一個複製品。)
3)AOF(體積大,速度慢,資料完整):Redis會將每一個收到的寫命令都通過Write函式追加到檔案最後,類似於MySQL的binlog。
        當Redis重啟是會通過重新執行檔案中儲存的寫命令來在記憶體中重建整個資料庫的內容。
4)混合持久化(4.0以後版本支援):同時結合RDB持久化以及AOF持久化混合寫入AOF檔案。
4)當兩種方式同時開啟時,資料恢復Redis會優先選擇AOF恢復。

2.快取問題

1)快取雪崩:快取雪崩是指原本應該訪問快取的都直接訪問底層資料庫了,例如大量快取資料在同一時間過期,導致大量請求直接訪問底層資料庫,導致底層資料庫崩潰。
    解決方案:
        (1)快取資料過期時間分散(過期時間加隨機數)
        (2)底層資料庫訪問加鎖或者將讀寫請求放入消費佇列
2)快取穿透:使用者查詢的資料,資料庫中沒有,那快取中也不會有,使用者就會繞過快取直接查詢底層資料庫,如果使用者大量進行這種操作,比如黑客攻擊,就會造成底層資料庫崩潰,這種現象成為快取穿透。
    解決方案:
        (1)對查詢結果為空的情況也進行快取,快取時間設定短一點,或者該key對應的資料insert了之後清理快取。
        (2)對一定不存在的key進行過濾。可以把所有的可能存在的key放到一個大的Bitmap中,查詢時通過該bitmap過濾。
        (3)布隆過濾器:布隆過濾器的原理是,當一個元素被加入集合時,通過K個雜湊函式將這個元素對映成一個位陣列中的K個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。redis的布隆過濾器外掛可以很好的解決這個問題,詳情點選我
        (4)介面層校驗,不合法請求直接拒絕
3)快取擊穿:快取擊穿和和快取雪崩相似,例如某個key的訪問量非常大,突然過期了,導致大量的訪問直接打到底層資料庫,造成底層資料庫崩潰,這種現象成為快取擊穿。
    解決方案:
        (1)熱點資料永不過期
        (2)定時指令碼,定時更新熱點資料
4)快取預熱:為了避免系統上線後大量直接訪問底層資料庫操作,事先將某些資料放入快取,以緩解底層資料庫壓力

3.redis資料型別和應用場景

1)string:任何資料都可以用string快取
2)hash:json物件
3)list:訊息佇列
4)set:相當於python中的set,內部元素唯一,增刪改查複雜度都是O(1)操作,支援交併補操作。獲取購買相同記錄,唯一性時使用。
5)sorted set:有序集合,在set的基礎上增加了value的權重,利用跳躍表實現(64層),排行榜使用。

4.redis資料淘汰策略

1)volatile-lru:從已設定過期時間的資料集中挑選最近最少使用的資料淘汰
2)volatile-ttl:從已設定過期時間的資料集中挑選將要過期的資料淘汰
3)volatile-random:從已設定過期時間的資料集中任意選擇資料淘汰
4)allkeys-lru:淘汰最近最少使用的資料
5)allkeys-random:從資料集中任意選擇資料淘汰
6)no-eviction:禁止驅逐資料
7)volatile-lfu:從已設定過期時間的資料集中挑選最不經常使用的資料淘汰
8)allkeys-lfu:移除最不經常使用的資料

5.redis底層資料型別實現

redis有五大資料型別,字串物件(string)、列表物件(list)、雜湊物件(hash)、集合(set)物件和有序集合物件(zset),
redis構建了一個物件系統,每個資料型別都是一個物件,由於redis是key,value的形式儲存的,所以每新建一個資料,系統就會新建兩個物件,一個是key物件,一個是value物件,
具體檢視部落格:<https://blog.csdn.net/xiaohei_xiaobai/article/details/106061333>

6.redis-HyperLogLog基數統計

redis在2.8.9 版本新增了 HyperLogLog 資料結構,用於統計非重複資料個數,這個功能利用redis的set也能實現,但是為什麼還要提供HyperLogLog呢?
原因在於如果有時我們只需要計算基數個數,不要儲存具體的資料時,資料量很大,如果利用set會浪費很大的記憶體消耗,但是HyperLogLog的優點是,
他所需要的記憶體空間不會隨著資料量的增大而增大,當統計元素很少時,HyperLogLog佔用的記憶體空間也很小,當統計的資料很大時,HyperLogLog最大隻會佔用12k的記憶體空間。

7.redis分散式鎖方案

1)INCR:INCR命令會將key的值加一,如果key值不存在,則key值會被初始化為0,然後執行INCR操作。
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
     
    import redis
    import time
     
    rdb_host = '127.0.0.1'
    rdb_port = 6379
    rdb_pwd = 'root'
    rdb_db = 13
     
    db = redis.ConnectionPool(
        host=rdb_host,
        port=rdb_port,
        password=rdb_pwd,
        db=rdb_db)
    rdb = redis.Redis(connection_pool=db)
     
     
    def redis_lock(order_id):
        lock_key = 'LOCK_' + str(order_id)
        incr = rdb.incr(lock_key)
        
        if incr == 1:
            rdb.expire(lock_key, 10)
            print 'lock success'
            rdb.delete(lock_key)
        else:
            print 'current key has been occupied'
            time.sleep(1)
            redis_lock(order_id)
     
     
    if __name__ == '__main__':
        redis_lock(1234)
2)SETNX(SET is Not eXists):當key不存在時可以為key設定值,返回1,否則返回0。
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
     
    import redis
    import time
     
    rdb_host = '127.0.0.1'
    rdb_port = 6379
    rdb_pwd = 'root'
    rdb_db = 13
     
    db = redis.ConnectionPool(
        host=rdb_host,
        port=rdb_port,
        password=rdb_pwd,
        db=rdb_db)
    rdb = redis.Redis(connection_pool=db)
     
     
    def redis_lock_setnx(order_id):
        lock_key = 'LOCK_' + str(order_id)
        setnx = rdb.setnx(lock_key, 'lock')
     
        if setnx == 1:
            rdb.expire(lock_key, 10)
            print 'lock success'
            rdb.delete(lock_key)
        else:
            print 'current key has been occupied'
            time.sleep(1)
            redis_lock_setnx(order_id)
            
     
    if __name__ == '__main__':
        redis_lock_setnx(2345)
3)SET:Redis從2.6.12版本開始, SET命令的行為可以通過一系列引數來修改
    (1)EX:設定鍵的過期時間為 second 秒。 
    (2)PX:設定鍵的過期時間為 millisecond 毫秒。
    (3)NX:只在鍵不存在時,才對鍵進行設定操作。
    (4)XX:只在鍵已經存在時,才對鍵進行設定操作。
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
     
    import redis
    import time
     
    rdb_host = '127.0.0.1'
    rdb_port = 6379
    rdb_pwd = 'root'
    rdb_db = 13
     
    db = redis.ConnectionPool(
        host=rdb_host,
        port=rdb_port,
        password=rdb_pwd,
        db=rdb_db)
    rdb = redis.Redis(connection_pool=db)
     
     
    def redis_lock_set(order_id):
        lock_key = 'LOCK_' + str(order_id)
        set = rdb.set(lock_key, 'lock', ex=10, nx=True)
     
        if set:
            print 'lock success'
            rdb.delete(lock_key)
        else:
            print 'current key has been occupied'
            time.sleep(1)
            redis_lock_set(order_id)
     
     
    if __name__ == '__main__':
        redis_lock_set(3456)
4)以上方式存在的問題
以上方式都設定了過期時間,原因在於如果程式由於某些bug以外退出了,不加過期時間的話,這個key會一直被鎖定,無法更新。
但是加過期時間來處理就會有問題,如果客戶端1在設定的過期時間內程式正常執行,但是沒有處理完,這時過期時間失效了,然後這個key的鎖被客戶端2獲取,
在客戶端2還沒處理完的時候客戶端1處理完了,然後刪除了客戶端2的鎖。
針對這個問題可以在給鎖賦值的時候增加隨機字元,然後刪除的時候判斷下是否為自己賦的值就可以了。具體實現如下:
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
     
    import redis
    import time
     
    rdb_host = '127.0.0.1'
    rdb_port = 6379
    rdb_pwd = 'root'
    rdb_db = 13
     
    db = redis.ConnectionPool(
        host=rdb_host,
        port=rdb_port,
        password=rdb_pwd,
        db=rdb_db)
    rdb = redis.Redis(connection_pool=db)
            
     
    def redis_lock_set_random(order_id):
        lock_key = 'LOCK_' + str(order_id)
        lock_value = 'lock' + str(int(time.time()))
        set = rdb.set(lock_key, lock_value, ex=10, nx=True)
     
        if set:
            print 'lock success'
            if rdb.get(lock_key) == lock_value:
                rdb.delete(lock_key)
        else:
            print 'current key has been occupied'
            time.sleep(1)
            redis_lock_set_random(order_id)
     
     
    if __name__ == '__main__':
        redis_lock_set_random(4567)

8.Memcache與Redis的區別

1)儲存方式Memecache把資料全部存在記憶體之中,斷電後會掛掉,資料不能超過記憶體大小。Redis有部份存在硬碟上,redis可以持久化其資料。
2)資料支援型別memcached所有的值均是簡單的字串,redis作為其替代者,支援更為豐富的資料型別,提供list,set,zset,hash等資料結構的儲存。
3)value值大小不同:Redis最大可以達到512M;memcache只有1M。
4)redis的速度比memcached快很多。
5)Redis支援資料的備份,即master-slave模式的資料備份。

9.單執行緒的redis為什麼這麼快

1)純記憶體操作
2)單執行緒操作,避免了頻繁的上下文切換
3)採用了非阻塞I/O多路複用機制

10.高併發知識點

1)阻塞I/O:程式發起read後,如果kernel未準備好資料,程式就會被block,進入等待階段;等待kernel將資料準備好,才會返回資料,解除阻塞;
2)非阻塞I/O:程式發起read請求,kernel未準備好資料,直接返回一個error,程式無需阻塞等待;
            程式會輪詢傳送read請求,如此往復;一直到kernel準備好資料,才把資料拷貝並返回給程式,結束輪詢;
3)I/O多路複用:程式呼叫select/poll/epoll,select/poll/epoll會將程式block起來;kernel會‘監視’所有select負責的socket;
             當任何一個socket準備好資料,select就會返回(也就是圖中的return medable);程式呼叫read(圖中第二次system call),將資料從kernel拷貝到使用者程式;
    I/O多路複用主要的三種實現方式:
    (1)select:不是執行緒安全;只返回資料,不會告知是哪個sock返回的,只能自己遍歷去查詢;只能監視1024個連結。
    (2)poll:相對於select改進了:可監視無數個連結,缺點:仍舊不是執行緒安全。
    (3)epoll:改進:執行緒安全,只有linux支援。

11.Redis事務

Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的
1)MULTI命令用於開啟一個事務,它總是返回OK。 MULTI執行之後,客戶端可以繼續向伺服器傳送任意多條命令,這些命令不會立即被執行,而是被放到一個佇列中,當EXEC命令被呼叫時,所有佇列中的命令才會被執行。
2)EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列。 當操作被打斷時,返回空值 nil 。
3)通過呼叫DISCARD,客戶端可以清空事務佇列,並放棄執行事務, 並且客戶端會從事務狀態中退出。
4)WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為。 可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。

12.redis協議規範(RESP)

1)簡單字串 Simple Strings, 以 "+"加號 開頭
    eg: "+OK\r\n"
2)錯誤 Errors, 以"-"減號 開頭
    eg: "-Error unknow command 'foobar'\r\n"
3)整數型 Integer, 以 ":" 冒號開頭
    eg: ":1000\r\n"
4)大字串型別 Bulk Strings, 以 "$"美元符號開頭,長度限制512M
    eg: "$6\r\nfoobar\r\n"    其中字串為 foobar,而6就是foobar的字元長度
5)陣列型別 Arrays,以 "*"星號開頭
    eg: "*2\r\n$2\r\nfoo\r\n$3\r\nbar\r\n"      陣列包含2個元素,分別是字串foo和bar

相關文章