Python缓存框架Tache使用介绍

以云看科技 2024-07-06 03:53:16
介绍

Tache 是一个 Python 的缓存框架。它基于如下的目标而设计:

同时支持 Python2 和 Python3支持缓存普通函数/实例方法/类方法/静态方法支持 Batch 批量缓存支持基于 Tag 的缓存和失效支持基于参数显式声明 key 格式

前两个目标是必须的,后面三个目标是我们设计 Tache 的初衷。

Tag 的使用

案例一: 变更批量失效。当一个对象变更时,需要失效所有与该改对象相关的查询。

最初对象相关的查询可能比较少,随着业务的增长后来的查询条件越来越多,对象字段越来越多,甚至变成了一个聚合对象,内部各字段甚至都不来自一个存储,一个服务,每处都加上了缓存。这时本来只是一处的变更,要失效的代码散落在各地很容易出漏。怎么办呢,根据对象给缓存打上同样的标签,失效时只需失效一处。

假设有如下的代码 (以下代码均为演示而设计,勿对号入座):

@cache(tags=["comment:{0}"])def get_comment(comment_id): ...@cache(tags=["comment:{0}"])def get_comment_status_by_id(comment_id): ... @cache(tag=["comment:{0}"])def get_comment_likes_by_id(comment_id): ... @cache(tag=["comment:{0}", "member:{1}"])def get_replied_comment_by_member(comment_id, member_id):

那么最后实际需要失效的代码还需要多少呢?只需一行:

cache.invalidate_tag("comment:{0}".format(comment_id))

案例二: 分页操作。针对如下的分页操作,因为 offset, limit 的组合太多,如果对上面的操作整体缓存,如何保证失效时全部失效?

def get_comments(object_type, object_id, status, offset, limit):...

一样可以用 tag 来控制:

@cache(tags=["comment:{0}:{1}"])def get_comments(object_type, object_id, status, offset, limit): comments = comment_dao.get_comments(..., offset, limit) results = [] for c in comments: results.append(format_comment(c)) return results

这样当增加和删除评论时,只要失效 tag, 所有的 offset/limit 查询缓存全部都失效了。等等,你这样有另一个问题,会不会缓存失效粒度过大,造成数据库大量查询?

很遗憾,确实会这样。基于此,我们将基于 Batch 批量缓存来优化这个分页操作。

Batch 的使用

Batch 可以同时对列表中多个元素进行批量缓存,当失效列表中某一元素时不会影响被缓存的其他元素。当列表中有缓存和未缓存的数据都存在时,仅会对未缓存的数据进行查询。如:

list_objects(1,2,3,4,5) # no cache, 调用完毕全部一次缓存list_objects(3,4,5,6,7) # 3,4,5 从缓存中取,6,7 在调用完毕一次缓存

接着针对上面的分页操作,我们把它分为两步:1. 分页取 id 列表2. 用 id 列表批量获取实体 id 列表

以下是 get_comments 的重构:

@cache(tags=["comment:{0}:{1}"])def get_comments(object_type, object_id, status, offset, limit): comment_ids = comment_dao.get_comment_ids(..., offset, limit) results = get_comments_byids(*comment_ids) return results @batch()def get_comments_byids(*comment_ids): results = [] for cid in comment_ids: comment = comment_dao.get_comment(cid) results.append(format_comment(comment)) return results def add_comment(): ... cache.invalidate_tag("comment:{0}:{1}".format(obtype, obid))

这样在失效缓存时,我们失效的只是用 offset, limit 取 id 列表这个查询, 从 id 列表取出实体都还是被缓存的。

显式声明 Key

开源的 Python 缓存库通常生成缓存 key 的规则都是基于模块名、类名、方法名等的组合自动生成。作为一个有追求的程序员,重构是经常要做的事。但是重构过程中,如果改了函数名、方法名,或者移动了模块位置,经常会造成缓存失效,对于流量比较大的接口这是比较危险的事情。

Tache 允许你显式声明 Key 的生成规则, 这样不论代码如何重构生成的 key 都不会改变。

class B: def __init__(self): self.count = 0 @cache.cached("counter.B.add|{0}-{1}") def add(self, a, b): self.count += 1 return a + b + self.count

只要不改 key 的规则,不论代码如何重构,上述生成的 key 都会形如 counter.B.add|{0}-{1}.format(a, b)。

最后基于 tag 的缓存是个杀器,但要谨慎使用,使用场景推荐用于粗粒度的聚合接口,不建议用来缓存请求量大的细粒度接口。因为每次读取时会多取一次 tag, 因此对接口缓存的请求量会放大一倍更详细的使用说明参见 Tache 的项目文档。

作者:杨昆

出处:https://zhuanlan.zhihu.com/p/32191167

0 阅读:65
以云看科技

以云看科技

感谢大家的关注