【Python】7天-python实现缓存-day01

使用Python实现类似redis的缓存,原文是使用go实现的,本文使用python实现,用来比较两者的区别,方便从python转go的开发者比较二者的不同。
PS:原文链接是:https://geektutu.com/post/geecache-day1.html
PS: 预计在完成前还会对本文多次修改 仅作参考
PS: 测试代码也会在后续补充

一、LRU和LRU-K

原文使用的是LRU算法,这里改成LRU-K算法。先说明一下两者区别。

LRU

LRU(最近最少使用)算法在处理周期性或突发查询时会导致缓存命中率下降,主要原因在于其工作机制和数据访问模式之间的不匹配。

LRU算法的工作机制:
LRU算法的基本思想是,当缓存空间不足时,淘汰最近最少使用的缓存项。具体步骤如下:

1、访问缓存项:
如果缓存项存在,则将其标记为最新使用。
如果缓存项不存在,则将其加载到缓存中,并可能会淘汰最久未使用的缓存项。
2、淘汰策略:
当缓存空间不足时,选择最久未使用的缓存项进行淘汰。

LRU-K

LRU-K 是一种先进的缓存替换算法,它结合了最近最少使用(LRU)和频率(K)的概念。LRU-K 算法会记录每个缓存项最近的 K 次访问时间,并根据这些时间来决定缓存项的替换顺序。与传统的 LRU 算法相比,LRU-K 算法能够更好地捕捉缓存项的访问模式,从而提高缓存命中率。

工作原理
1、初始化
设置缓存的大小和 K 的值(例如,K=2 表示记录最近两次访问时间)。
2、访问缓存项:
如果缓存项在缓存中,更新其最近 K 次访问时间。
如果缓存项不在缓存中,加载缓存项并记录其访问时间。如果缓存已满,按照 LRU-K 的替换策略进行替换。
3、替换缓存项:
选择最近 K 次访问时间中最早的缓存项进行替换。

LRU-K 算法的优点

  • 适应性强
    相比于传统的 LRU 算法,LRU-K 算法能够更好地适应不同的访问模式。
  • 高命中率
    通过记录 K 次访问时间,LRU-K 算法能够更准确地捕捉缓存项的访问频率,提高缓存命中率。

LRU-K 算法的应用场景
LRU-K 算法适用于访问模式复杂且需要高缓存命中率的场景,例如数据库缓冲池、网页缓存等。在这些场景中,LRU-K 算法能够有效地提高缓存性能,减少缓存替换次数。

周期性查询和突发查询对LRU算法的影响

周期性查询

周期性查询指的是数据访问具有明显的周期性,比如每隔一段时间会重新访问某些数据。这种访问模式的特点是,数据在每个周期内是高度集中的,但在周期之间的间隔期可能不被访问。

影响

1、缓存项被淘汰:
在周期的间隔期内,缓存中的这些周期性数据可能会被淘汰,因为它们在此期间不被访问,导致其在LRU队列中位置靠后。
当下一周期开始时,这些数据需要重新加载到缓存中,导致缓存命中率下降。

2、缓存空间占用:
如果缓存空间有限,周期性数据可能会占用大量缓存空间,导致其他数据无法被缓存,进一步降低缓存命中率。

突发查询

突发查询指的是数据访问在某个时刻突然增加,随后又恢复正常。这种访问模式的特点是短时间内大量访问某些数据,而其他时间访问量较少。

影响

1、缓存污染:
突发查询会导致大量数据短时间内被加载到缓存中,原本的缓存数据可能被淘汰。这种现象被称为缓存污染。
当突发查询结束后,这些新加载的数据可能不再被访问,而原本的缓存数据已经被淘汰,导致缓存命中率下降。

2、缓存稳定性降低:
突发查询会导致缓存内容频繁变化,缓存项在短时间内被频繁替换,无法稳定保持较高的缓存命中率。

LRU算法的python实现(基于本次项目,非纯算法)

ps:双向链表的算法可以根据另一个文章内容进行实现

from Dlist import DLinkedList

class PCache:

    def __init__(self, max_bytes, nbytes, func=None):
        self.max_bytes = max_bytes
        self.nbytes = nbytes
        self.ll = DLinkedList()
        self.cache = dict()
        self.OnEvicted = func  # 回调函数

    def add(self, key, value):
        if self.cache.get(key):
            # 更新
            entry = self.cache.get(key)
            self.ll.move_to_front(entry)
            self.cache[key] = Entry(key, value)
            self.nbytes += len(value) - len(entry.value)
        else:
            # 新增
            entry = Entry(key, value)
            self.ll.append(entry)
            self.cache[key] = entry
            self.nbytes = len(key) + len(value)
        while self.max_bytes != 0 and self.max_bytes < self.nbytes:
            self.remove_oldest()

    def remove_oldest(self):
        # lru算法
        ele = self.ll.back()
        del self.cache[ele.key]
        self.nbytes -= len(ele.key) + ele.value
        if self.OnEvicted:
            self.OnEvicted()
        
    def query(self, key):
        ele = self.cache.get(key)
        self.ll.move_to_front(ele)
        if ele:
            return ele.value, True
        

class Entry:

    def __init__(self, key, value):
        self.key = key
        self.value = value

LRU-K算法的python实现(基于本次项目,非纯算法)

import time

from collections import defaultdict, deque
from Dlist import DLinkedList

class PCache:

    def __init__(self, max_bytes, nbytes, k=2, func=None):
        self.max_bytes = max_bytes
        self.nbytes = nbytes
        # lru 需要的数据结构 双向链表
        self.ll = DLinkedList()
        self.cache = dict()
        self.OnEvicted = func  # 回调函数

        # lru-k 所需数据结构 deque是双向队列
        self.k = k
        self.history = defaultdict(deque)
    
    def _current_time(self):
        return time.time()

    def add(self, key, value):
        if self.cache.get(key):
            # 更新
            entry = self.cache.get(key)
            self.history[key].append(self._current_time())
            if len(self.history[key]) > self.k:
                self.history[key].popleft()
            self.cache[key] = Entry(key, value)
            self.nbytes += len(value) - len(entry.value)
        else:
            # 新增
            entry = Entry(key, value)
            self.cache[key] = entry
            self.nbytes = len(key) + len(value)
            self.history[key].append(self._current_time())
        while self.max_bytes != 0 and self.max_bytes < self.nbytes:
            self.remove_oldest_k()

    def remove_oldest(self):
        # lru算法
        ele = self.ll.back()
        del self.cache[ele.key]
        self.nbytes -= len(ele.key) + ele.value
        if self.OnEvicted:
            self.OnEvicted()
    
    def remove_oldest_k(self):
        # lru-k算法
        oldest_key = None
        oldest_time = float('inf')
        for key, times in self.history.items():
            # 先淘汰频率低的
            if len(times) < self.k:
                oldest_key = key
                break
            # 再淘汰最早使用的
            if times[0] < oldest_time:
                oldest_time = times[0]
                oldest_key = key
        if oldest_key is not None:
            self.history.pop(oldest_key)
            ele = self.cache[oldest_key]
            del self.cache[oldest_key]
            self.nbytes -= len(oldest_key) + ele.value
            if self.OnEvicted:
                self.OnEvicted()
        
    def query(self, key):
        ele = self.cache.get(key)
        if ele:
            self.history[key].append(self._current_time())
            if len(self.history[key]) > self.k:
                self.history[key].popleft()
            
            return ele.value, True
        

class Entry:

    def __init__(self, key, value):
        self.key = key
        self.value = value

你可能感兴趣的:(Python-7天小项目,python,python,缓存,开发语言)