python OrderedDict实现有expire和max的队列和缓存服务

    周天又寂寞了….今天天气不是太好,有些阴冷,估计大街上的小妞肯定不会穿裙子…   估计不能拿着板凳去看美女的大腿了….  

     最近一直对服务端的开发很是感兴趣,python本身的数据结构是很容易实现队列或者是缓存的服务的。  我曾经实现一个python机遇lru算法的一个缓存服务。  别问我Lru缓存算法是啥, 可以理解为保持热数据和新数据的缓存的策略,算法会剔除不活跃的数据。 

http://xiaorui.cc/2014/11/01/python%E5%AE%9E%E7%8E%B0%E6%9C%80%E5%B0%91%E4%BD%BF%E7%94%A8%E7%AE%97%E6%B3%95lru%E5%8C%85%E6%8B%ACdict%E5%92%8Clist%E9%98%9F%E5%88%97/


正好这两天在家看redis的设计与实现,想到了redis的expire的功能, 他的expire是分两种,一个是间隔性质的去剔除过期的数据,还有一种是被动的剔除。  如果python的lru的数据源是在redis里面的话,很容易实现含有expire的lru。 lru和expire在缓存的策略是可以单独存在,又可以互相依赖的。 lru是用来保证存储的数据是活跃的,热的。 含有expire的缓存,是保证数据是新的,就算你的数据再热,但是他时间长了,就应该剔除他。   那么lru和expire如果一起用的话,不仅能实现热数据的存在,而且能实现数据过期。  


python实现expire max的原文链接 

http://xiaorui.cc

http://xiaorui.cc/?p=1289

这次就不用redis做数据源了,而是采用python的orderDict来实现。 orderDict是可以让字典有序的模块,orderDict自己会维持一个列表,通过这个列表来实现kv的有序。  我们主要是为了实现队列和缓存,首先先用orderDict实现一个简单的、类似FIFO先进先出的队列服务,附带了最大数据的控制。 我们会发现利用orderDict实现队列也是很容易的。 


from collections import OrderedDict

class LocalCache(OrderedDict):
    """会删除旧的数据,popitem默认是从最后去除的,我们可以指定从头部去掉 """

    def __init__(self, limit=None):
        super(LocalCache, self).__init__()
        self.limit = limit

    def __setitem__(self, key, value):
        while len(self) >= self.limit:
            self.popitem(last=False)
        super(LocalCache, self).__setitem__(key, value)

那咱们再度深入下,基于上面的例子,我们该如何实现expire这个含有TTL功能,应该是如何的实现呢..   有人已经在githun里面分享了代码.

https://github.com/mailgun/expiringdict/blob/master/expiringdict/__init__.py  


import time
from threading import RLock
#Rlock 是用来保持数据,唯一锁
try:
    from collections import OrderedDict
except ImportError:
    # Python < 2.7
    from ordereddict import OrderedDict


class ExpiringDict(OrderedDict):
    def __init__(self, max_len, max_age_seconds):
        assert max_age_seconds >= 0
        assert max_len >= 1

        OrderedDict.__init__(self)
        self.max_len = max_len
        self.max_age = max_age_seconds
        self.lock = RLock()
#这里用的是python魔法方法,__contains__ ,每次get数据的时候,都会看看他是否过期,如果过期的话,直接干掉.   逻辑是被with lock包含的,做了一个互斥的效果。 
    def __contains__(self, key):
        """ Return True if the dict has a key, else return False. """
        try:
            with self.lock:
                item = OrderedDict.__getitem__(self, key)
                if time.time() - item[1] < self.max_age:
                    return True
                else:
                    del self[key]
        except KeyError:
            pass
        return False
# 和上面contains差不多的功能
    def __getitem__(self, key, with_age=False):
        """ Return the item of the dict.
        Raises a KeyError if key is not in the map.
        """
        with self.lock:
            item = OrderedDict.__getitem__(self, key)
            item_age = time.time() - item[1]
            if item_age < self.max_age:
                if with_age:
                    return item[0], item_age
                else:
                    return item[0]
            else:
                del self[key]
                raise KeyError(key)
#查看数据的个数是否已经等同于先前设置的大小,如果匹配max_len的条件,就从头部干掉一个。 这里OrderDict可以set我们传递的key value 并传送时间戳
    def __setitem__(self, key, value):
        """ Set d[key] to value. """
        with self.lock:
            if len(self) == self.max_len:
                self.popitem(last=False)
            OrderedDict.__setitem__(self, key, (value, time.time()))

    def pop(self, key, default=None):
        """ Get item from the dict and remove it.
        Return default if expired or does not exist. Never raise KeyError.
        """
        with self.lock:
            try:
                item = OrderedDict.__getitem__(self, key)
                del self[key]
                return item[0]
            except KeyError:
                return default

    def ttl(self, key):
        """ 查看ttl时间
        """
        key_value, key_age = self.get(key, with_age=True)
        if key_age:
            key_ttl = self.max_age - key_age
            if key_ttl > 0:
                return key_ttl
        return None

    def get(self, key, default=None, with_age=False):
        " Return the value for key if key is in the dictionary, else default. "
        try:
            return self.__getitem__(key, with_age)
        except KeyError:
            if with_age:
                return default, None
            else:
                return default

    def items(self):
        """ Return a copy of the dictionary's list of (key, value) pairs. """
        r = []
        for key in self:
            try:
                r.append((key, self[key]))
            except KeyError:
                pass
        return r

    def values(self):
        """ Return a copy of the dictionary's list of values.
        See the note for dict.items(). """
        r = []
        for key in self:
            try:
                r.append(self[key])
            except KeyError:
                pass
        return r

    def fromkeys(self):
        " Create a new dictionary with keys from seq and values set to value. "
        raise NotImplementedError()

    def iteritems(self):
        """ Return an iterator over the dictionary's (key, value) pairs. """
        raise NotImplementedError()

    def itervalues(self):
        """ Return an iterator over the dictionary's values. """
        raise NotImplementedError()

    def viewitems(self):
        " Return a new view of the dictionary's items ((key, value) pairs). "
        raise NotImplementedError()

    def viewkeys(self):
        """ Return a new view of the dictionary's keys. """
        raise NotImplementedError()

    def viewvalues(self):
        """ Return a new view of the dictionary's values. """
        raise NotImplementedError()

这样expire和max的功能都有了,到现在为止,这个模块还只是实现了redis主键消逝的被动模式,如果是想用主动模式的话,是需要在模块里面开一个线程,一直去loop了。  个人觉得没必要设计一套可以主动剔除失效key的逻辑。   一个简单的服务,你还要开一个线程搞探测实在是折腾,如果非要用主动探测的话,我觉得可以set 和 get的时候,取模计算后顺手再去探测。   






大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

2 Responses

  1. 你好 2015年4月23日 / 上午10:27

    while len(self) >= self.limit: 第一段代码,没转义

发表评论

电子邮件地址不会被公开。 必填项已用*标注