dogpile是一種控制結構,它允許選擇單個執行線程作爲某些資源的“創建者”,同時允許其他執行線程在創建過程中引用此資源的先前版本。
dogpile.cache是一個緩存API,它提供了一個通用接口來緩存任何種類的後端。
首先安裝dogpile:pip install dogpile.cache
現在有很多有名的key/value服務比如說redis Memcached,他們都很適合用於緩存,特別是Memcached。考慮到緩存系統,dogpile.cache提供了針對該系統的特定Python API的接口。
dogpile.cache配置包含以下組件:
- region: 是一個CacheRegion的實例,可以被看做是front end
- backend: 是一個CacheBackend的實例,描述了數據是怎麼被儲存的。這個接口只提供了get(),set(),delete()。用於特定CacheRegion的實際CacheBackend類型由用於與緩存通信的底層API,比如pylibmc(Memcached的python客戶端)確定。
- Value generation functions: 這些是用戶定義的函數,用於生成要放入緩存的數據。雖然godpile.cache提供了將數據放入緩存的常用set方法,但是通常使用方法是get。傳遞一個創建函數,當且僅當需要時,它將用於生成新值,這種“get-or-create”模式是“Dogpile”系統的全部關鍵。
初步使用:
dogpile.cache包含一個Pylibmc後端
from dogpile.cache import make_region
# 這裏創建了一個CacheRegion實例對象
Region = make_region()
# 對CacheRegion對象配置了backend
region = Region.configure(
'dogpile.cache.pylibmc', # 這是name of backend,是需要的唯一arguement
expiration_time = 3600,
arguments = {
'url': ["127.0.0.1"], # 這裏是傳遞Memcached server的URL
}
)
@region.cache_on_arguments()
def load_user_info(user_id):
return some_database.lookup_user_by_id(user_id)
所以最後都是使用region作爲裝飾器
make_region()
make_region()函數會直接調用CacheRegion的結構
該函數有以下參數:
- name: 可選,region的名字,可以用.name獲取,在config file中配置region時會有用
- function_key_generator: 可選,用於使用CacheRegion.cache_on_arguments() 方法,這個函數需要有兩個level。 一個是給出data creation function,然後需要返回一個新function用於基於給定的arguement生成key。
使用裝飾器:def my_key_generator(namespace, fn, **kw): fname = fn.__name__ def generate_key(*arg): return namespace + "_" + fname + "_".join(str(s) for s in arg) return generate_key region = make_region( function_key_generator = my_key_generator ).configure( "dogpile.cache.dbm", # python的database manager,python本身有很多dbm庫,但是dbm是一個方便選擇,不用選擇特定的dbm模塊,它可以自己選擇最適合的 expiration_time=300, arguments={ "filename":"file.dbm" } )
@my_region.cache_on_arguments(namespace=('x', 'y')) def my_function(a, b, **kw): return my_data()
- function_multi_key_generator:可選,用於使用CacheRegion.cache_multi_on_arguments()方法,Generated function會返回一個key列表。
def my_multi_key_generator(namespace, fn, **kw): namespace = fn.__name__ + (namespace or '') def generate_keys(*args): return [namespace + ':' + str(a) for a in args] return generate_keys
- key_mangler: 可選,比較典型的是SHA1,sha1_mangle_key() 方法會對所有的key做hash
- async_creation_runner: 它將通過互斥鎖並在完成時負責釋放該互斥鎖
import threading def async_creation_runner(cache, somekey, creator, mutex): def runner(): try: value = creator() cache.set(somekey, value) finally: mutex.release() thread = threading.Thread(target=runner) thread.start() region = make_region( async_creation_runner=async_creation_runner, ).configure( 'dogpile.cache.memcached', expiration_time=5, arguments={ 'url': '127.0.0.1:11211', 'distributed_lock': True, } )
CacheRegion.configure()
根據上面一步,當我們有了CacheRegion的時候,CacheRegion.cache_on_arguments() 方法就可以用來當做裝飾器了,但是這個緩存現在還不能用,必須經過CacheRegion.configure()配置了backend等信息才能使用
CacheRegion.configure() 有以下幾個參數:
- backend: CacheBackend的名字,必須項
- expiration_time: 在上次value creation function調用之後,經過這段時間,纔會調用下一次value creation function
- arguments: 指定backend的地址
- wrap: ProxyBackend類和/或實例的列表,每個類和/或實例用於包裝原始backend,以便可以應用自定義功能擴充。
- replace_existing_backend: 設置爲True的話,已經存在的cache backend會被替換,如果不設置,對存在的backend進行配置會報錯。
- region_invalidator: 重寫默認的無效策略
- CacheRegion.configure_from_config():使用configuration dictionary和裏面的前綴批量配置backend:
local_region = make_region() memcached_region = make_region() # 上面CacheRegion已經創建好,但是還沒有配置 myconfig = { "cache.local.backend":"dogpile.cache.dbm", "cache.local.arguments.filename":"/path/to/dbmfile.dbm", "cache.memcached.backend":"dogpile.cache.pylibmc", "cache.memcached.arguments.url":"127.0.0.1, 10.0.0.1", } local_region.configure_from_config(myconfig, "cache.local.") memcached_region.configure_from_config(myconfig,"cache.memcached.")
如何使用Region
上面對CacheRegion該配置的已經配置好了,這玩意兒相當於我們訪問cache的前端接口,它包含下面的方法:
首先介紹invalidate()方法:
默認的invalidation system就是爲value設置當前時間爲最小的創建時間,任何檢索值的創建時間早於這個timestamp就會被判定爲過期,它不會對緩存中的值產生影響。這個timestamp被進程儲存在本地內存,所以不會影響其他進程。但是如果這個timestamp被儲存在緩存中,各進程都會被同一個timestamp影響,那麼就需要制定一個custom RegionInvalidationStrategy.
這個方法可以有hard和soft兩個選項,如果是hard,那麼CacheRegion.get_or_create()會讓所有的getters都等待value重新生成,如果是soft,那麼隨後的getters會獲取old value直到新的value生成。
CacheRegion.get(key, expiration_time=None, ignore_expiration=False)
key: 根據key從Cache裏面獲取value,如果獲取不到,該方法就會返回一個token:NO_VALUE。
expiration_time: 這個過期時間,要麼是在region配置的時候設定好了,要麼是現在調用的時候傳遞的參數,它會比較檢索值的創建時間和現在時間(time.time()),如果過期了,這個緩存值會被忽略並且返回NO_VALUE。
ignore_expiration: 如果我們這樣設置參數ignore_expiration=True, 那麼會繞過這個過期時間檢測
CacheRegion.get_or_create(key, creator, expiration_time=None, should_cache_fn=None, creator_args=None)
根據給定的key在緩存中獲取對應的value,如果這個值不存在或者過期了,那麼給定的creation function可能會創建新的值並且保存。
併發鎖:
是否能使用這個creation function取決於是否能獲取這個dogpile lock,如果不能,證明此時正有另一個線程或者進程在使用creation function爲這個key創建值。
當這個dogpile不能獲取的時候:
如果previous value不存在,那麼這個方法就會block並且等待直到這個鎖被釋放,並且有一個新的值available。
如果previous value存在,那麼會直接被返回。
如果invalidate()方法被called,如果檢索值的timestamp比invalidation timestamp老,那麼該值不管如何都不會被返回,該方法會嘗試獲取dogpile lock去生成新的值,或者等待lock釋放然後返回新的值
key:檢索的key
creator:用於創建新的值的函數
creator_args: 向創建函數傳遞參數 tuple
expiration_time: 會重寫配置region時設置的過期時間,如果不需要過期時間,設置爲-1
should_cache_fn:這個方法會接受creation function的返回值,然後根據返回True或者False判斷新生成的值是否需要被保存到cache中:
def dont_cache_none(value):
return value is not None
# 如果這裏返回False,那麼該值不會被緩存
value = region.get_or_create("some key",
create_value, # 這是creation function
should_cache_fn=dont_cache_none)
CacheRegion.set(key, value)
在緩存中爲特定的key設置value
CacheRegion.delete(key)
緩存中刪除特定的鍵值對,該操作冪等
CacheRegion.cache_on_arguments(namespace=None, expiration_time=None, should_cache_fn=None, to_str=<class ‘str’>, function_key_generator=None)
這是一個裝飾器函數將creation function創建的鍵值對儲存到緩存中,這個裝飾器會內在的調用CacheRegion.get_or_create()方法去獲取緩存然後看是否需要調用該函數。
@someregion.cache_on_arguments()
def generate_something(x, y):
return somedatabase.query(x, y)
這個被裝飾的方法可以現在被調用了,數據會從cache裏面獲取,直到新的value被需要
result = generate_something(5, 6)
這個方法有一個屬性爲invalidate(),可以驗證value的timestamp:
generate_something.invalidate(5, 6)
這個方法還可以直接使用set(),這樣會直接儲存這個給定的value,而不需要調用這個被裝飾的函數:
generate_something.set(3, 5, 6)
這個方法有屬性refresh(),該方法會調用被裝飾函數去產生新的value放入緩存:
newvalue = generate_something.refresh(5, 6)
這個方法有屬性original(),它會調用被裝飾函數而不是獲取緩存中的數據:
newvalue = generate_something.original(5, 6)
這個方法有屬性get(),它會返回緩存中的值,或者返回NO_VALUE:
value = generate_something.get(5, 6)