[Java複習] 緩存Cache part1

1. 在項目中是如何使用緩存的?爲何要用?不用行不行?用了可能會有哪些不良後果?redis

結合項目業務,主要兩個目的:高性能高併發。緩存走內存,自然支持高併發。算法

不良後果:緩存

  1.   緩存與DB雙寫不一致
  2.   緩存雪崩,緩存穿透
  3.   緩存併發競爭

 

2. Redis的線程模型是什麼?網絡

Redis內部使用文件事件處理器(file event handler),這個處理器是單線程,全部Redis才叫單線程模型。多線程

採用IO 多路複用機制同時監聽多個 socket,將產生事件的 socket 壓入內存隊列中,事件分派器根據 socket 上的事件類型來選擇對應的事件處理器進行處理。架構

文件處理器包含4個部分:併發

  • 多個 socket
  • IO 多路複用程序
  • 文件事件分派器
  • 事件處理器(鏈接應答處理器、命令請求處理器、命令回覆處理器)

socket併發不一樣操做,對應不一樣文件事件處理器,IO多路複用監聽多個socket,將產生事件的socket放入隊列排隊,less

文件事件分發器每次從隊列中取出一個socket,根據socket事件類型交給對應的事件處理器進行處理。dom

I/O多路複用:異步

 

在同一個線程裏面, 經過撥開關的方式,來同時傳輸多個I/O流。

Redis 通訊過程:

 

 

 

3. 爲何Redis單線程模型也能效率這麼高?

  • 純內存操做。
  • 核心是基於非阻塞的 IO 多路複用機制。
  • C 語言實現,通常來講,C 語言實現的程序「距離」操做系統更近,執行速度相對會更快。
  • 單線程反而避免了多線程的頻繁上下文切換問題,預防了多線程可能產生的競爭問題。

 

4. Redis 都有哪些數據類型?分別在哪些場景下使用比較合適?

  數據類型:

     string:最基本。普通Set get,簡單K-V緩存

set name zhangsan

    hash:類map結構。能夠把結構化數據(對象)緩存。把簡單對象緩存,後續操做能夠只修改對象中某個字段。

 key=user
 value={
    "id": 100, 
    "name":"zhangsan",
    "age",20
 }
hset user id 100
hset user name zhangsan
hset user age 20

    list:有序列表。微博大V粉絲以list格式放Redis緩存。

key=某大V  value=[zhangsan, li, wangwu]

    list的lrange命令:從某個元素開始讀取多少個元素,能夠基於list實現簡單分頁。

lrange mylist 0 -1

    0開始位置,-1結束位置,結束位置爲-1時,表示列表的最後一個位置,即查看全部。

lpush, lpop //棧(FILO)

    set: 無序,自動去重。

   JVM的hashset能夠去重,可是多臺機器的呢?這個Redis的set適用於分佈式全集去重。能夠基於set作交集,並集,差集。查看大V共同好友等等。

sadd myset 1 // 添加元素1
smembers myset // 查看所有元素
sismember myset 2 // 判斷是否包含某個元素
srem myset 1 // 刪除元素1
srem myset 1 3 // 刪除某些元素
scrad myset // 查看元素個數
spop myset  // 隨機刪除一個元素
smove testset myset abc // 將testset的元素abc移到myset
sinter testset myset  // 求兩個set交集
sunion testset myset // 求兩個元素並集
sdiff testset myset //求差集 在testset中而不宅myset的元素

    sorted set: 排序的set,去重還能夠排序。例如寫入元素帶分數,自動根據分數排序。

zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
zadd board 63 zhaoliu
zrevrange board 0 3 // 獲取排名前三   rev 改降序
zrank board zhaoliu

 

5. Redis的過時策略都有哪些?內存淘汰機制都有哪些?手寫一下 LRU 代碼實現?(往Redis寫入數據怎麼會沒了?)

  緩存基於內存,內存有限,寫入超過內存容量,確定有數據失效。要麼設置過時時間,要麼被redis幹掉。

  設置過時時間:

    若是設置一批key只能存活1個小時,1個消息後,redis是怎麼對這批數據進行刪除的?

    redis過時策略是:按期刪除 + 惰性刪除

      按期刪除:隨機抽取一些過時key來檢查和刪除。

        爲何?若是不少key,10W個key設了過時時間,每隔幾百毫秒,去檢查,會形成高CPU負載。因此實際上redis時隨機抽取key來刪除。

      惰性刪除:並非key到時間就被刪除,而是過時後查詢這個key時,redis查詢下這key過時了,刪除。不會返回值。

  數據明明過時了,怎麼還佔用着內存?

  可是實際上這仍是有問題的,若是按期刪除漏掉了不少過時 key,而後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?

  若是大量過時 key 堆積在內存裏,致使 redis 內存塊耗盡了,咋整?

  解決方案是:走內存淘汰機制。

  內存淘汰機制:

    Redis內存淘汰機制以下:

  • noeviction: 當內存不足時,新寫入操做會報錯。 (不會用)
  • allkeys-lru:當內存不足時,移除最近最少使用的 key(最經常使用的)。
  • allkeys-random:當內存不足時,隨機刪除。
  • volatile-lru:當內存不足時,設置了過時時間的鍵中,移除最近最少使用的 key(這個通常不太合適)。
  • volatile-random:當內存不足時,設置了過時時間的key中,隨機移除某個 key。
  • volatile-ttl:當內存不足時,設置了過時時間的key中,有更早過時時間的 key 優先移除。

  手寫一個 LRU(Least Recent Used) 算法:

    利用JDK實現一個Java版LRU.

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int CACHE_SIZE; //  傳遞進來最多能緩存多少數據

    public LRUCache(int cacheSize) {
   // 設置一個hashmap的初始大小     
   // true 表示讓 linkedHashMap 按照訪問順序來進行排序,最近訪問的放在頭部,最老訪問的放在尾部
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 當 map中的數據量大於指定的緩存個數的時候,就自動刪除最老的數據。
        return size() > CACHE_SIZE;
    }
}

用到的LinkedHashMap的構造函數:

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 
{
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
}

參數說明:

  initialCapacity:   初始容量大小,使用無參構造方法時,此值默認是16

  loadFactor:       負載因子,使用無參構造方法時,此值默認是 0.75f

  accessOrder:   false: 基於插入順序     true:  基於訪問順序

  重點看看accessOrder的做用,使用無參構造方法時,此值默認是false。

  accessOrder = true: 基於訪問的順序,get一個元素後,這個元素被加到最後(使用了LRU 最近最少被使用的調度算法)

 

6. 如何保證 redis 的高併發和高可用?redis 的主從複製原理能介紹一下麼?redis 的哨兵原理能介紹一下麼?

Redis實現高併發:

  主從架構,一主多從,讀寫分離,主節點負責寫,將數據同步到其餘從節點,從節點負責讀。

  好處:輕鬆水平擴容,支撐高併發。

 

 

Redis Replication 的核心機制:

    Master節點異步複製到Slave節點。

    注意點:1. Master節點必須使用持久化。

                   2. Master的各類備份方案。如從備份中挑一份RDB去恢復Master,才能確保Master啓動時,是有數據的。

 

Redis 主從複製的核心原理

  Slave第一次連Master, 發送PSYNC給Master, 觸發full resynchronization全量複製。

  Master生產快照RDB文件,同時在內存緩存最新數據(從客戶端接收最新寫命令),發送RDB給Slave。

  Slave先把RDB寫磁盤,再加載到內存。而後Master再把內存中緩存的寫命令發到Slave,Slave再同步這些寫命令。

  若是Slave與Master因網絡緣由斷開後,再鏈接時,Master只會發缺乏的部分數據到Slave。

 

  主從複製的斷點續傳:

    master內存中維護一個backlog,master和slave都有一個replica offset,還有一個master run id.

    master run id:是一個節點的惟一id, Host + IP有可能能變動。

    若是鏈接斷開,再連上後slave就從上次replica offset開始複製,若是沒有找到,就全量複製。

  無磁盤化複製:

   master在內存中建立RDB,不寫磁盤,發給Slave。

repl-diskless-sync yes

   # 等待 5s 後再開始複製,由於要等更多 slave 從新鏈接過來

repl-diskless-sync-delay 5

複製的流程:

 

 

 

全量複製:

若是在複製期間,內存緩衝區持續消耗超過 64MB,或者一次性超過 256MB,那麼中止複製,複製失敗。

client-output-buffer-limit slave 256MB 64MB 60

heartbeat:

master默認每隔10秒發送一次heartbeat,slave每隔1秒發送一個 heartbeat。

 

Redis如何作到高可用:

redis的高可用架構,叫作failover故障轉移,也能夠叫作主備切換。

master在故障時,自動檢測,而且將某個slave自動切換爲master的過程,叫作主備切換。

Redis高可用,作主從架構,加上哨兵機制,實現主備切換。

 

Redis 哨兵集羣實現高可用:

  sentinel(哨兵)主要功能:

  • 集羣監控:監控master和slave進程是否正常
  • 消息通知:實例有故障時,發送消息給管理員
  • 故障轉移:master掛了,自動轉移到slave
  • 配置中心:若是故障轉移,通知客戶端新的master地址

  哨兵至少須要 3 個實例,來保證本身的健壯性。

  經典的 3 節點哨兵集羣:

      

 

 配置 quorum=2,若是 M1 所在機器宕機了,那麼三個哨兵還剩下 2 個,S2 和 S3 能夠一致認爲 master 宕機了,而後選舉出一個來執行故障轉移,同時3個哨兵的majority是2,因此還剩下的2個哨兵運行着,就能夠容許執行故障轉移。

redis 哨兵主備切換的數據丟失問題:

  兩種狀況和致使數據丟失:

    1. 異步複製致使的數據丟失

      有部分數據尚未來得及複製到slave,master就掛了,這部分數據就丟失。

    2. 腦裂致使的數據丟失

      腦裂,指某個master忽然脫離正常網絡,沒法與其餘slave鏈接,但master還在運行,

      此時哨兵認爲master掛了,開啓選舉,將其餘slave選爲master。這個時候集羣內有2個mster, 就叫腦裂。

      此時,客戶端會向舊master寫數據,當舊master恢復成一個新的slave掛到新master時,新master並無這段時間數據,就丟失了。  

  

數據丟失問題的解決方案:

min-slaves-to-write 1
min-slaves-max-lag 10

   要求至少有 1 個 slave(min-slaves-to-write),數據複製和同步的延遲不能超過 10 秒,若是超過了,master再也不接收請求。

   有了 min-slaves-max-lag 這個配置,減小異步複製數據的丟失量。

   若是不能繼續給指定數量slave發送數據,且slave超過10秒沒有給本身發ack,則拒絕客戶端寫請求,最多丟10秒數據。

 

  sdown 和 odown 轉換機制:

    sdown 是主觀宕機,就一個哨兵若是本身以爲一個 master 宕機了,那麼就是主觀宕機。

    odown 是客觀宕機,若是 quorum 數量的哨兵都以爲一個 master 宕機了,那麼就是客觀宕機。

    sdown: 若是一個哨兵 ping 一個 master,超過了 is-master-down-after-milliseconds 指定的毫秒數以後,就主觀認爲 master 宕機了

 

   哨兵集羣的自動發現機制:

   哨兵以前經過Redis的pub/sub實現互相發現。每隔兩秒,每一個哨兵會往本身監控的master+slave對應的__sentinel__:hello這個channel裏發送消息,

   消息包括本身的host,ip, run id還有對master的監控配置。每一個哨兵都監聽本身監控的channel, 消費其餘哨兵的消息,感知其餘哨兵的存在。

   

  slave 配置的自動糾正:

   當一個slave成爲master時,哨兵確保其他slave鏈接到新的master上。 

   

  slave -> master選舉算法:

     slave選舉考慮因素:

       跟master斷開鏈接的時長,slave優先級,複製數據的offset,run id

       若是一個 slave 跟 master 斷開鏈接的時間已經超過了 down-after-milliseconds 的 10 倍,外加 master 宕機的時長,那麼 slave 就被認爲不適合選舉爲 master。

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

    選舉排序:

      1. slave priority越低,優先級越高。默認配置中slave-priority=100

      2. slave priority相同時,看replica offset,哪一個複製的數據多,offset靠後,優先級高

      3. 以上相同時,選run id小的slave

    quorum和majority:

      哨兵主備切換時,須要quorum數量的哨兵認爲sdown(主觀down),才能轉換爲odown。

      這個時候選一個哨兵來作切換,這個哨兵還須要獲得majority哨兵的受權,才能正式執行切換。

    configuration epoch(version):

      哨兵會對一套redis的  master + slave 進行監控,有相應的監控配置。

      執行切換的哨兵,會重新master(slave->master)獲得一個configuration epoch,是一個version號,每次切換的version號必須惟一。

      若是選舉出的哨兵進行切換失敗,則其餘哨兵等待failover-timeout時間後,接替作切換,從新獲取一個新的configuration epoch做爲新的version。

    configuration傳播:

      哨兵完成切換後,在本身本地更新生成最新master配置,而後經過hello channel同步給其餘哨兵。

      新的master配置是跟着新的version號,其餘哨兵都是根據版本號的大小來更新本身的master配置。

 

Part1總結:

  redis高併發:主從架構,一主多從,通常項目足夠,一主寫入數據,單機幾W的QPS,多從用來查詢數據,多個實例能夠提供10W的QPS。

  好比Redis主只有8G內存,其實最多隻能容納8G的數據量。若是要容量的數據量更大,就須要redis集羣,用集羣后能夠提供每秒幾十萬的讀寫併發。

  redis高可用:若是主從架構,加上哨兵集羣就能夠實現。一個實例宕機,自動進行主備切換。

相關文章
相關標籤/搜索