應用中需要通過http呼叫遠端的資料,但是這個獲取過程需要執行較長時間,而且這個資料本身的變化也不頻繁,這種情況最適合用乙個cache來優化。
前兩年在做短鏈結實現的時候,曾經用最好的語言php做過乙個redis cache實現《乙個簡單的redis應用(修訂版)》,但那個畢竟是乙個特定的實現,而且我現在需要的是python版。
這次的目標是需要實現乙個比較通用的cache,支援各種資料型別,有超時更新機制,超時更新需要有鎖(防止前文那個例子裡發生過的問題)。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import hashlib
import pickle
from functools import wraps
from redis import redis
import logging
__author__ = 'raptor'
logger = logging.getlogger(__name__)
class
rediscache
(object):
max_expires = 86400
serializer = pickle
locker = set()
def__init__
(self, name, host='localhost', port=6379, db=0, max_expires=max_expires):
self.db = redis(host=host, port=port, db=db)
self.name = name
self.max_expires = max_expires
def_getkey
(self, *keys):
return
":".join([self.name] + list(keys))
def_get_data
(self, key):
result = self.db.get(key)
return
none
if result == b'none'
else result
defget(self, key):
result = self._get_data(self._getkey(key))
return self.serializer.loads(result) if result is
notnone
else result
defset(self, key, value, ex=none):
k = self._getkey(key)
v = self.serializer.dumps(value)
if ex is
none:
self.db.set(k, v)
else:
self.db.setex(k, v, ex)
defdelete
(self, key):
self.db.delete(self._getkey(key))
@staticmethod
defbuild_key
(name, *args, **kwargs):
m = hashlib.md5()
m.update(name.encode('utf-8'))
m.update(pickle.dumps(args))
m.update(pickle.dumps(kwargs))
return m.hexdigest()
defcached
(self, key, func, ex=none):
if ex is
none:
ex = self.max_expires
min_ttl = self.max_expires - ex # ex <= 0 : force refresh data
key = ":".join([self.name, key])
result = self._get_data(key)
if key not
in self.locker:
self.locker.add(key)
try:
ttl = self.db.ttl(key)
if ttl is
none
or ttl < min_ttl:
result = func()
if result is
notnone:
result = self.serializer.dumps(result)
self.db.setex(key, result, self.max_expires)
finally:
self.locker.remove(key)
try:
result = self.serializer.loads(result) if result is
notnone
else
none
except:
pass
return result
defredis_cached
(db, ex=none):
defdecorator
(fn):
@wraps(fn)
def(*args, **kwargs):
key = rediscache.build_key(fn.__name__, *args, **kwargs)
return db.cached(key, lambda: fn(*args, **kwargs), ex)
return decorator
rediscache本身也可以當乙個redis資料庫物件使用,比如:
db = rediscache('tablename', max_expires=3600) # tablename是乙個是自定義的key字首,可以用於當作表名使用。
# 最大超時時間(max_expires)僅供cached使用,使用set時,如果不指定超時時間則永不超時
db.set('aaa', , 7200) # value可以是作何可序列化資料型別,比如字典,不指定超時則永不超時
db.get('aaa')['key'] # 結果為1234
db.delete('aaa')
但這個不是重點,重點是cached功能。對於慢速函式,加上db.cached以後,可以對函式呼叫的結果進行cache,在cache有效的情況下,大幅提高函式在反覆呼叫時的效能。
下面是乙個例子,具體見**中的注釋:
db = rediscache('tablename')
deffunc
(url, **kwargs):
result = requests.get("?".join([url, urlencode(kwargs)]))
return result
url = ''
t = time()
func(url, wd="測試")
print(time()-t) # 較慢
t = time()
db.cached('test_cache', lambda: func(url, wd="測試"), 10)
print(time()-t) # 第一次執行仍然較慢
t = time()
db.cached('test_cache', lambda: func(url, wd="測試"), 10)
print(time()-t) # 從redis裡讀取cache很快
sleep(11) # 等待到超時
t = time()
db.cached('test_cache', lambda: func(url, wd="測試"), 10)
print(time()-t) # 超時後會再次執行func更新cache
t = time()
db.cached('test_cache_new', lambda: func(url, wd="新的測試"))
print(time()-t) # 不同的呼叫引數用不同的key作cache
t = time()
因為對於不同的函式呼叫引數,函式可能有不同的返回結果,所以應該用不同的key進行cache。為簡單起見,可以把函式簽名做乙個hash,然後以此為key進行cache。最後把這個操作做成乙個decorator,這樣,只需要給函式加上這個decorator即可自動提供所需要的cache支援。
最終的簡單用法如下:
db = rediscache('tablename')
@redis_cached(db, 10)
deffunc
(url, **kwargs):
result = requests.get("?".join([url, urlencode(kwargs)]))
return result
t = time()
func(url, wd="測試")
print(time()-t)
t = time()
func(url, wd="測試")
print(time()-t)
sleep(11)
t = time()
func(url, wd="測試")
print(time()-t)
t = time()
func(url, wd="新的測試")
print(time()-t)
是不是簡單得多了。 實現乙個Semaphore
其實這是我boss的想法,我一開始聽他這麼說也覺得比較差異,ms已經寫好了何必再自己寫乙個.答案有兩個 1ms寫的東西未必就是最好的,如完成埠,heap等.2semaphore是多執行緒程式設計中的核心元素所以有必要提速.我們都知道在多執行緒中ms提供的多個現成阻塞核心物件中critical mon...
實現乙個call
call是js最好用的函式之一,改變函式上下文是外掛程式編寫最經常使用的特性。var name 小鋼炮 var cat function say name say ketty 小鋼炮 ketty say.call cat,ketty 貓 ketty看下面 function say name var ...
實現乙個mvvm
最近在團隊內做了一次vue原理分享,現場手寫了乙個乞丐版mvvm,這裡記錄一下這個mvvm實現的過程。原始碼 這個mvvm是基於發布訂閱模式實現 也是vue本身的實現原理 最終達到的效果如下 使用方式也跟vue一樣 重置 實現很簡單 class mvvm options this.methods m...