redis的內存滿了以後,redis如何回收內存

做者:沈熠輝node

來源:恆生LIGHT雲社區算法

Redis使用場景

如今公司的業務愈來愈複雜,咱們須要抽出一個用戶系統,向各個業務系統提供用戶的基本信息。數據庫

業務001.jpg

 

業務方對用戶信息的查詢頻率很高,必定要注意性能問題哦。緩存

用戶信息固然是存放在數據庫裏,可是因爲咱們對用戶系統的性能要求比較高,顯然不能在每一次請求時都去查詢數據庫。服務器

因此,在內存中建立了一個哈希表做爲緩存,每當查找一個用戶時會先在哈希表中進行查詢,以此來提升訪問的性能。數據結構

業務002.jpg

很快發現了個問題

線上服務器宕機了!性能

糟了,是內存溢出了,用戶數量愈來愈多,當初設計的哈希表把內存給撐爆了,趕忙重啓吧!url

但是之後該怎麼辦呢?咱們能不能給服務器的硬件升級,或者加幾臺服務器呀?spa

那我能不能在內存快耗盡的時候,隨機刪掉一半用戶緩存呢?操作系統

唉,這樣也不妥,若是刪掉的用戶信息,正好是被高頻查詢的用戶,會影響系統性能的。

你據說過LRU算法嗎?

LRU全稱Least Recently Used,也就是最近最少使用的意思,是一種內存管理算法,該算法最先應用於Linux操做系統。

這個算法基於一種假設:長期不被使用的數據,在將來被用到的機率也不大。所以,當數據所佔內存達到必定閾值時,咱們要移除最近最少被使用的數據。

原來如此,這個算法正好對個人用戶系統有幫助!能夠在內存不夠時,從哈希表中移除一部分不多被訪問的用戶。

但是,我怎麼知道哈希表中哪些Key-Value最近被訪問過,哪些沒被訪問過?總不能給每個Value加上時間戳,而後遍歷整個哈希表吧?

這就能展示LRU算法的精妙所在了。在LRU算法中,使用了一種有趣的數據結構,這種數據結構叫做哈希鏈表。

什麼是哈希鏈表呢?

咱們都知道,哈希表是由若干個Key-Value組成的。在「邏輯」上,這些Key-Value是無所謂排列順序的,誰先誰後都同樣。

哈希鏈表01.jpg

在哈希鏈表中,這些Key-Value再也不是彼此無關的了,而是被一個鏈條串了起來。每個Key-Value都具備它的前驅Key-Value、後繼Key-Value,就像雙向鏈表中的節點同樣。

用戶信息02.jpg

這樣一來,本來無序的哈希表就擁有了固定的排列順序。

但是,這哈希鏈表和LRU算法有什麼關係呢?

依靠哈希鏈表的有序性,咱們能夠把Key-Value按照最後的使用時間進行排序。

讓咱們以用戶信息的需求爲例,來演示一下LRU算法的基本思路。

1.假設使用哈希鏈表來緩存用戶信息,目前緩存了4個用戶,這4個用戶是按照被訪問的時間順序依次從鏈表右端插入的。

用戶信息03.jpg

用戶信息04.jpg

4.接下來,若是業務方請求修改用戶4的信息。一樣的道理,咱們會把用戶4從原來 的位置移動到鏈表的最右側,並把用戶信息的值更新。這時,鏈表的最右端是最新被訪問的用戶4,最左端仍然是最近最少被訪問的用戶1。

用戶信息05.jpg

用戶信息06.jpg

5.後來業務方又要訪問用戶6,用戶6在緩存裏沒有,須要插入哈希鏈表中。假設這時緩存容量已經達到上限,必須先刪除最近最少被訪問的數據,那麼位於哈希鏈表最左端的用戶1就會被刪除,而後再把用戶6插入最右端的位置。

用戶信息07.jpg用戶信息08.jpg

以上,就是LRU算法的基本思路。

明白了,這真是個巧妙的算法!那麼LRU算法怎麼用代碼來實現呢?

簡單實現

class LRUCache:
    def __init__(self,limit):
        self.limit=limit
        self.hash={}
        self.head=None
        self.end=None
    def get(self,key):
        node=self.hash.get(key)
        if node is None:
            return None
        self.refresh_node(node)
        return node.value
    def put(self,key,value):
        node=self.hash.get(key)
        if node is None:
            #若是key不存在,插入key-value
            if len(self.hash) >= self.limit:
                old_key=self.remove_node(self.head)
                self.hash.pop(old_key)
            node=Node(key,value)
            self.add_node(node)
            self.hash[key]=node
        else:
            #若是key存在,刷新key-value
            node.value=value
            self.refresh_node(node)
    def remove(self,key):
        node=self.hash.get(key)
        self.remove_node(node)
        self.hash.remove(key)
    def refresh_node(self,node):
        #若是訪問的是尾節點,無需移動節點
        if node==self.end:
           return
        #移除節點
        self.remove_node(node)
        #從新插入節點
        self.add_node(node)
    def remove_node(self,node):
        if node==self.head and node==self.end:
            #移除惟一的節點
            self.head=None
            self.end=None
        elif node==self.end:
            #移除節點
            self.end=self.end.pre
            self.end.next=None
        elif node==self.head:
            #移除頭節點
            self.head=self.head.next
            self.head.pre=None
        else:
            #移除中間節點
            node.pre.next=node.pre
            node.next.pre=node.pre
        return node.key
    def add_node(self,node):
        if self.end is not None:
            self.end.next=node
            node.pre=self.end
            node.next=None
        self.end=node
        if self.head is None:
           self.head=node
​
class Node:
    def __init__(self,key,value):
        self.key=key
        self.value=value
        self.pre=None
        self.next=None
​
lruCache=LRUCache(5)
​
lruCache.put("001","用戶1信息")
lruCache.put("002","用戶2信息")
lruCache.put("003","用戶3信息")
lruCache.put("004","用戶4信息")
lruCache.put("005","用戶5信息")
​
print(lruCache.get("002"))
相關文章
相關標籤/搜索