一日一技:实现有过期时间的LRU缓存

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: 一日一技:实现有过期时间的LRU缓存

摄影:产品经理下厨:kingname

一日一技:实现函数调用结果的 LRU 缓存一文中,我们提到Python自带的LRU缓存lru_cache。通过这个装饰器可以非常轻松地实现缓存。

现在我们考虑下面这个应用场景:MongoDB中有100对id-用户名的对应关系,我从Redis中持续不断读取id,如果id能在MongoDB中找到对应关系,那么就把对应的用户名打印出来。如果找不到对应关系,那么就把这个id丢弃。

为了防止频繁读取MongoDB,我在程序开始的时候直接读取这一百对对应关系,并存为字典:

import pymongo
import redis
client = redis.Redis()
handler = pymongo.MongoClient().weibo.id_name_map
def read_id_name_map():
    id_name = {}
    for row in handler.find():
        id_name[row['id']] = row['name']
    return id_name
id_name_map = read_id_name_map()
whileTrue:
    data = client.blpop('weibo_id')
    user_id = data[1].decode()
    if user_id in id_name_map:
        print(id_name_map[user_id])

大家可以思考一下,上面这段代码有没有什么问题。然后继续看后面。

如果我现在需要再增加100个id-用户名的对应关系怎么办?

由于这个程序运行以后就一直阻塞式地读取Redis,不会停止,所以整个过程只会读取一次MongoDB。后面即使我向MongoDB中添加了新的对应关系,只要程序不重启,就无法读取到新的对应关系。

肯定有同学想到,在while循环里面增加一个计时器,每x分钟就重新调用一下read_id_name_map()函数,更新对应关系。

不过今天我们要讲的是另一个更有创意的办法,使用lru_cache来实现。

对于这个例子来说,lru_cache的maxsize参数只需要设置为1,因为只需要存放1份对应关系即可。那么我们如何做到,比如每10分钟更新一次呢?我们知道,在使用lru_cache时,如果调用同一个函数,并且传入的参数相同,那么从第二次开始就会使用缓存。现在我们如何让时间在每10分钟内相同呢?

我们来看现在的时间戳:1578399211.30042

它除以600,值是1578399211.30042 // 600 = 2630665.0。然后我让这个时间戳加5分钟,也就是增加300秒,变成1578399511.30042。这个新的时间戳再除以600,发现结果还是2630665.0。但如果原来的时间戳增加超过10分钟,例如增加了601秒,我们再来看看效果(1578399211.30042 + 601) // 600 = 2630666.0,此时的结果也发生了变化。

利用这个特点,修改一下我们的代码:

import pymongo
import redis
import time
from functools import lru_cache
client = redis.Redis()
handler = pymongo.MongoClient().weibo.id_name_map
@lru_cache(maxsize=1)
def read_id_name_map(_):
    id_name = {}
    for row in handler.find():
        id_name[row['id']] = row['name']
    return id_name
whileTrue:
    data = client.blpop('weibo_id')
    id_name_map = read_id_name_map(time.time() // 600)
    user_id = data[1].decode()
    if user_id in id_name_map:
        print(id_name_map[user_id])

现在,我们直接在while循环内部调用read_id_name_map,如果两次调用的时间间隔小于600秒,那么time.time() // 600的值是相同的,第二次直接使用缓存,也就不会查询MongoDB了。当时间超过10分钟后,时间戳除以600的值增加了,于是缓存没有命中,进入查询MongoDB的过程,更新id_name_map。实现了有过期时间的LRU缓存。

补充:可能有同学注意到定义read_id_name_map函数的时候,参数我写的是下划线。这是Python 编码规范中建议的一种写法。当一个变量不会被使用,但又需要保留时,就可以用下划线表示。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
25天前
|
缓存 Python
在Python中,`functools`模块提供了一个非常有用的装饰器`lru_cache()`,它实现了最近最少使用(Least Recently Used, LRU)缓存策略。
在Python中,`functools`模块提供了一个非常有用的装饰器`lru_cache()`,它实现了最近最少使用(Least Recently Used, LRU)缓存策略。
|
7天前
|
存储 缓存 算法
Python 从零开始实现一个简单的LRU缓存
Python 从零开始实现一个简单的LRU缓存
17 0
|
1月前
|
缓存 算法 前端开发
前端 JS 经典:LRU 缓存算法
前端 JS 经典:LRU 缓存算法
25 0
|
2月前
|
缓存 JavaScript
请问如何在 keep-alive 组件中设置缓存的最大数量和过期时间
请问如何在 keep-alive 组件中设置缓存的最大数量和过期时间
|
2月前
|
缓存 算法 索引
LeetCode146:LRU缓存
LeetCode146:LRU缓存
23 1
|
3月前
|
缓存 算法 Java
数据结构~缓存淘汰算法--LRU算法(Java的俩种实现方式,万字解析
数据结构~缓存淘汰算法--LRU算法(Java的俩种实现方式,万字解析
|
3月前
|
缓存 算法 前端开发
前端开发者必知的缓存淘汰策略:LRU算法解析与实践
前端开发者必知的缓存淘汰策略:LRU算法解析与实践
|
23天前
|
缓存 NoSQL Java
Redis 缓存与数据库数据不一致问题
Redis 缓存与数据库数据不一致问题
50 3