周天又寂寞了….今天天气不是太好,有些阴冷,估计大街上的小妞肯定不会穿裙子… 估计不能拿着板凳去看美女的大腿了….
最近一直对服务端的开发很是感兴趣,python本身的数据结构是很容易实现队列或者是缓存的服务的。 我曾经实现一个python机遇lru算法的一个缓存服务。 别问我Lru缓存算法是啥, 可以理解为保持热数据和新数据的缓存的策略,算法会剔除不活跃的数据。
正好这两天在家看redis的设计与实现,想到了redis的expire的功能, 他的expire是分两种,一个是间隔性质的去剔除过期的数据,还有一种是被动的剔除。 如果python的lru的数据源是在redis里面的话,很容易实现含有expire的lru。 lru和expire在缓存的策略是可以单独存在,又可以互相依赖的。 lru是用来保证存储的数据是活跃的,热的。 含有expire的缓存,是保证数据是新的,就算你的数据再热,但是他时间长了,就应该剔除他。 那么lru和expire如果一起用的话,不仅能实现热数据的存在,而且能实现数据过期。
python实现expire max的原文链接
这次就不用redis做数据源了,而是采用python的orderDict来实现。 orderDict是可以让字典有序的模块,orderDict自己会维持一个列表,通过这个列表来实现kv的有序。 我们主要是为了实现队列和缓存,首先先用orderDict实现一个简单的、类似FIFO先进先出的队列服务,附带了最大数据的控制。 我们会发现利用orderDict实现队列也是很容易的。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
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的时候,取模计算后顺手再去探测。
while len(self) >= self.limit: 第一段代码,没转义
是js代码颜色的问题