现场是这样的。给检索函数加了 functools.lru_cache(maxsize=1024),参数是 dict 形式的 filter,调用直接抛 TypeError: unhashable type: 'dict';改成 tuple 又因为键顺序不同命中不了同一份缓存,命中率只有 12%。
中间走过弯路,把「嵌套的 list 仍然不可哈希」当成主因,改完现象没变。
这类报错背后常见的原因有几处,逐个排除就行。dict 不可哈希,lru_cache 要求参数可哈希;filter 的键顺序不稳定,tuple 化之后 key 不同;maxsize 太小,热点结果被挤掉。
复现不可哈希的报错
python -c "from functools import lru_cache;@lru_cache(None)\ndef f(d):return d\nf({'a':1})"
接着把参数规范化为排序后的 JSON 字符串
python -c "import json;print(json.dumps({'b':2,'a':1},sort_keys=True,separators=(',',':')))"
再往下用规范化 key 包一层再缓存
python -c "from functools import lru_cache;import json;@lru_cache(4096)\ndef f(k):return k\nprint(f(json.dumps({'a':1},sort_keys=True)))"
末了观察命中率并调整 maxsize
python -c "from functools import lru_cache;f=lru_cache(4096)(lambda k:k);print(f.cache_info())"
同样的操作我在测试机上先走了一遍,线上只做验证过的部分。
怎么算真的好了,看的是同一批现象。同样的 filter 无论键顺序如何都命中同一条缓存,命中率从 12% 涨到 70% 以上,接口 P95 明显下降。
lru_cache 的 key 是参数的哈希,dict 和 list 天生不行;规范化成排序 JSON 是最稳的做法,直接 tuple(dict.items()) 在键顺序变化时仍会漏命中。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。
