常量定义及构造函数

class TTLCache(object):

    NOT_FOUND = None

    def __init__(self):
        self.datas = dict()
        self.expires = dict()
  • self.datas:记录key to value映射关系的dict
  • self.expires:记录key to expire_ts映射关系的dict
  • NOT_FOUND:get操作在key不存在时的返回值

判断给定key是否存在对应的value、是否该key是否仍在存活期内

	def is_key_valid_on_existence(self, key):
        return key in self.datas

    def is_key_valid_on_liveness(self, key, now):
        return key in self.expires and now < self.expires[key]
  • is_key_valid_on_existence:仅从“存在性”的角度来看,key是否在cache中
  • is_key_valid_on_liveness:仅从“存活性”的角度来看,key是否在cache中

这两个函数是get操作的辅助判断函数

get、set操作

	def get(self, key):
        now = curr_ts()
        if self.is_key_valid_on_existence(key) and self.is_key_valid_on_liveness(key, now):
            return self.datas[key], True
        return TTLCache.NOT_FOUND, False

    def set(self, key, value, ttl=86400):
        now = curr_ts()
        self.datas[key] = value
        self.expires[key] = now + ttl
  • get:类似golang的map,此函数的返回值被设计为“多返回值”。
  • 第一个参数是value,第二个参数是代表“value是否有效”的bool类型标记
  • 当value不存在于cache中时,value被设置为常量NOT_FOUND
  • “value存在于cache中”的判断条件是is_key_valid_on_existence、is_key_valid_on_liveness都返回True
  • set:分别把value和expire值赋值到self.datas和self.expires两个dict中

自动清理过期key

	def gc_main(self):
        from time import sleep

        while True:
            sleep(5)
            now = curr_ts()
            keys = list(self.datas.keys())
            for key in keys:
                exp = self.expires.get(key)
                if exp > now:
                    continue
                else:
                    del self.datas[key]
                    del self.expires[key]

    def start_gc(self):
        from threading import Thread
        thr = Thread(target=self.gc_main)
        thr.daemon = True
        thr.start()
  • start_gc:启动gc线程的函数
  • gc线程的主函数为gc_main
  • gc线程作为辅助线程,设置为daemon,使得gc线程不掺和到“判断进程是否还有非守护线程,如果没有的话进程就exit了”的逻辑中去
  • gc_main:gc线程主函数
  • 每隔5秒执行一次gc逻辑
  • keys = list(self.datas.keys()):先取出所有key组成的清单(如果一边迭代dict一边删除dict里的key,会报错。所以要提前取一次全量key的清单)
  • exp = self.expires.get(key):取出key的过期时间
  • 如果exp>now,说明过期时间在当前时间的将来,即“未过期”
  • 如果not exp > now,说明过期时间在当前时间的过去,即“已过期”。此时需要对self.datas、self.expires中的key都执行删除操作

最后还要在构造函数中加入启动gc线程的操作

class TTLCache(object):

    ......

    def __init__(self):
		......
        self.start_gc()

完整代码如下

import time


def curr_ts():
    return time.time()


class TTLCache(object):

    NOT_FOUND = None

    def __init__(self):
        self.datas = dict()
        self.expires = dict()
        self.start_gc()

    def get(self, key):
        now = curr_ts()
        if self.is_key_valid_on_existence(key) and self.is_key_valid_on_liveness(key, now):
            return self.datas[key], True
        return TTLCache.NOT_FOUND, False

    def set(self, key, value, ttl=86400):
        now = curr_ts()
        self.datas[key] = value
        self.expires[key] = now + ttl

    def is_key_valid_on_existence(self, key):
        return key in self.datas

    def is_key_valid_on_liveness(self, key, now):
        return key in self.expires and now < self.expires[key]

    def gc_main(self):
        from time import sleep

        while True:
            sleep(5)
            now = curr_ts()
            keys = list(self.datas.keys())
            for key in keys:
                exp = self.expires.get(key)
                if exp > now:
                    continue
                else:
                    del self.datas[key]
                    del self.expires[key]

    def start_gc(self):
        from threading import Thread
        thr = Thread(target=self.gc_main)
        thr.daemon = True
        thr.start()


if __name__ == '__main__':
    cache = TTLCache()
    cache.set('name', 'John', 5)
    value_valid = cache.get('name')
    print(value_valid)

    from time import sleep
    sleep(6)

    value_valid = cache.get('name')
    print(value_valid)

    print(cache.datas, cache.expires)