實現有過期時間的LRU緩存


一日一技:實現函數調用結果的 LRU 緩存一文中,我們提到Python自帶的LRU緩存lru_cache。通過這個裝飾器可以非常輕松地實現緩存。

現在我們考慮下面這個應用場景:MongoDB中有100對id-用戶名的對應關系,我從Redis中持續不斷讀取id,如果id能在MongoDB中找到對應關系,那么就把對應的用戶名打印出來。如果找不到對應關系,那么就把這個id丟棄。

為了防止頻繁讀取MongoDB,我在程序開始的時候直接讀取這一百對對應關系,並存為字典:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()
while True:
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,此時的結果也發生了變化。

利用這個特點,修改一下我們的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
 
 
while True:
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 編碼規范中建議的一種寫法。當一個變量不會被使用,但又需要保留時,就可以用下划線表示。

 

分享自:公眾號:未聞code‘


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM