乙個Redis Cache實現

2021-08-03 05:32:55 字數 4816 閱讀 9102

應用中需要通過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...