集群缓存库
项目描述
依赖项:
numpy (用于 FilesCacheClient 中的优化)
python-memcached (用于 MemcachedClient)
pyzmq < 14 (用于一致性支持)
dnspython (用于 MemcachedClient 的基于 dns 的动态负载平衡)
cython (用于 InprocCacheClient 中的优化)
可选功能
在项目中声明依赖项时,可以引用以下可选功能:
mq (chorde.mq 下的所有内容,主要是拉 pyzmq)
shmem (共享内存工具,优化文件缓存客户端所需)
内存缓存
弹力痛
基本用法:
最简单的使用方法是创建一个受支持的缓存客户端,然后直接使用它,比如
from chorde.clients.inproc import InprocCacheClient
from chorde import CacheMissError
c = InprocCacheClient(200)
c.put(3, 10, 300) # put value 10 on key 3, TTL 5min
assert 10 == c.get(3)
try:
c.get(5)
except CacheMissError:
print "miss"
这将创建一个进程内 LRU 缓存。进程内部分表示它是进程私有的,不与其他进程共享。
LRU 有两种实现,具有不同的性能特征。InprocCacheClient可以将替代存储实现作为参数,有关详细信息,请参阅模块。
默认的 LRUCache,可作为chorde.clients.inproc.Cache访问,是一个常规的 LRU,它使用优先级队列和串联的哈希表实现,因此它具有O(log n)写入和O(1)读取,但默认情况下所有读取需要写入(以更新 LRU)。这可以通过指定自定义选项来禁用,有关更多详细信息,请参阅模块的文档。
还有一个替代的近似 LRU,可在 chorde.clients.inproc.CuckooCache中访问,它实现了 cuckoo 哈希表的惰性版本,并具有O(1)读取和分期O(1)写入。它也比常规的 LRU 更节省空间,因此它更适合非常大的缓存,但它的驱逐策略将是近似的,因此不能保证总是驱逐实际最近最少使用的项目。
多级缓存
处理远程缓存时的一种常见方法,例如上面使用 memcached 的示例,至少具有两个级别:memcached 本身和进程内小型缓存,以避免一直与 memcached 通信。
这可以通过分层客户端直接完成:
from chorde.clients.memcached import MemcachedClient
from chorde.clients.inproc import InprocCacheClient
from chorde.clients.tiered import TieredInclusiveClient
from chorde import CacheMissError
l1 = InprocCacheClient(10)
l2 = MemcachedClient(["localhost:11211"], checksum_key="test")
c = TieredInclusiveClient(l1,l2)
c.put(3, 10, 300)
assert 10 == c.get(3)
try:
c.get(5)
except CacheMissError:
print "miss"
在这里,我们构建了一个包容的分层客户端,其中较高级别的元素通过复制而不是交换来提升到较低级别。这意味着它们之间存在重复,但这通常在这样的情况下是最好的,其中上层在进程之间共享。
目前不提供独占客户端,因为在这些类型的缓存上很少使用独占模式。
装饰器
考虑缓存的一种更自然的方式是,它是普通函数的装饰器。
与其显式地放入和从缓存中获取,人们可以简单地将缓存视为对其他昂贵功能的优化。
chorde.decorators 中的装饰器提供了大量的功能和灵活性,这些示例仅涵盖最基本的用法:
假设c是我们要用于缓存的客户端,
from chorde.decorators import cached
import random
@cached(c, ttl=300, async_ttl=-60)
def expensive_func(x):
return x * random.random()
print expensive_func(1)
print expensive_func(1) # Should return the same
print expensive_func.async()(1) # will refresh asynchronously every minute
print expensive_func.future()(1).result() # same as before, but using the futures interface
print expensive_func.peek(1) # just check the cache
print expensive_func.put(1, _cache_put=5) # write an explicit value
print expensive_func.async().lazy(1) # don't wait, raise CacheMissError if not available, compute in background
print expensive_func.future().lazy(1).result() # same as before, but using the futures interface
在那里,async_ttl 表示触发异步重新计算的最小 TTL(您可以使用它来避免不得不等待重新计算)。负值使其相对于总 TTL,因此 -60 始终意味着每分钟(60 秒)重新计算一次。普通的 ttl 是一个绝对限制,不会返回比这更早的结果。
chorde.decorators.cached 上的文档将更多地说明调用缓存函数的方式。
一般来说,这些条款是:
懒惰:不等待计算,返回缓存结果或引发 CacheMissError。当与 async 结合使用时,它将在后台计算。
窥视:不要计算。类似于惰性,但它永远不会触发计算
异步:昂贵的事情(计算)发生在后台线程池上。
未来:返回期货而不是结果,使用未来在结果可用时得到通知。实际的缓存访问发生在线程池上。一种非阻塞的调用方式。
刷新:立即重新计算值。
与其他库集成
装饰器的 future() 接口特别适合与其他可以与 futures 对话的库集成。然而,Chorde 的 future 并不直接与其他库兼容,但它们可以很容易地像这样包装:
import tornado.web
import tornado.gen
from chorde.clients.async import makeFutureWrapper
WF = makeFutureWrapper(tornado.web.Future)
...
@tornado.gen.coroutine
def get(self):
some_result = yield WF(some_func.future()(some_args))
有更好的方法与 tornado >= 4.0 集成
from chorde.external_integration import monkey_patch_tornado
monkey_patch_tornado()
import tornado.web
import tornado.gen
...
@tornado.gen.coroutine
def get(self):
some_result = yield some_func.future()(some_args)