做者:沈熠輝node
來源:恆生LIGHT雲社區算法
Redis使用場景
如今公司的業務愈來愈複雜,咱們須要抽出一個用戶系統,向各個業務系統提供用戶的基本信息。數據庫
業務方對用戶信息的查詢頻率很高,必定要注意性能問題哦。緩存
用戶信息固然是存放在數據庫裏,可是因爲咱們對用戶系統的性能要求比較高,顯然不能在每一次請求時都去查詢數據庫。服務器
因此,在內存中建立了一個哈希表做爲緩存,每當查找一個用戶時會先在哈希表中進行查詢,以此來提升訪問的性能。數據結構
很快發現了個問題
線上服務器宕機了!性能
糟了,是內存溢出了,用戶數量愈來愈多,當初設計的哈希表把內存給撐爆了,趕忙重啓吧!url
但是之後該怎麼辦呢?咱們能不能給服務器的硬件升級,或者加幾臺服務器呀?spa
那我能不能在內存快耗盡的時候,隨機刪掉一半用戶緩存呢?操作系統
唉,這樣也不妥,若是刪掉的用戶信息,正好是被高頻查詢的用戶,會影響系統性能的。
你據說過LRU算法嗎?
LRU全稱Least Recently Used,也就是最近最少使用的意思,是一種內存管理算法,該算法最先應用於Linux操做系統。
這個算法基於一種假設:長期不被使用的數據,在將來被用到的機率也不大。所以,當數據所佔內存達到必定閾值時,咱們要移除最近最少被使用的數據。
原來如此,這個算法正好對個人用戶系統有幫助!能夠在內存不夠時,從哈希表中移除一部分不多被訪問的用戶。
但是,我怎麼知道哈希表中哪些Key-Value最近被訪問過,哪些沒被訪問過?總不能給每個Value加上時間戳,而後遍歷整個哈希表吧?
這就能展示LRU算法的精妙所在了。在LRU算法中,使用了一種有趣的數據結構,這種數據結構叫做哈希鏈表。
什麼是哈希鏈表呢?
咱們都知道,哈希表是由若干個Key-Value組成的。在「邏輯」上,這些Key-Value是無所謂排列順序的,誰先誰後都同樣。
在哈希鏈表中,這些Key-Value再也不是彼此無關的了,而是被一個鏈條串了起來。每個Key-Value都具備它的前驅Key-Value、後繼Key-Value,就像雙向鏈表中的節點同樣。
這樣一來,本來無序的哈希表就擁有了固定的排列順序。
但是,這哈希鏈表和LRU算法有什麼關係呢?
依靠哈希鏈表的有序性,咱們能夠把Key-Value按照最後的使用時間進行排序。
讓咱們以用戶信息的需求爲例,來演示一下LRU算法的基本思路。
1.假設使用哈希鏈表來緩存用戶信息,目前緩存了4個用戶,這4個用戶是按照被訪問的時間順序依次從鏈表右端插入的。
4.接下來,若是業務方請求修改用戶4的信息。一樣的道理,咱們會把用戶4從原來 的位置移動到鏈表的最右側,並把用戶信息的值更新。這時,鏈表的最右端是最新被訪問的用戶4,最左端仍然是最近最少被訪問的用戶1。
5.後來業務方又要訪問用戶6,用戶6在緩存裏沒有,須要插入哈希鏈表中。假設這時緩存容量已經達到上限,必須先刪除最近最少被訪問的數據,那麼位於哈希鏈表最左端的用戶1就會被刪除,而後再把用戶6插入最右端的位置。
以上,就是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"))