本文目录导读:

在Python中平衡缓存读写性能,核心在于根据数据访问模式选择合适的数据结构、替换策略和并发控制,以下是系统性的优化方案和代码示例:
核心权衡点
- 读多写少:字典 + LRU/TTL
- 写多读少:写入缓冲区 + 批量合并
- 读写平衡:分片锁 + 近似淘汰算法
具体实现策略
带有过期时间的LRU缓存(最常用)
from collections import OrderedDict
import time
import threading
class LRUCacheWithTTL:
def __init__(self, capacity=1000, default_ttl=300):
self.capacity = capacity
self.default_ttl = default_ttl # 秒
self.cache = OrderedDict()
self.expiry = {} # key -> 过期时间戳
self.lock = threading.RLock() # 读写锁
def get(self, key):
with self.lock:
if key not in self.cache:
return None
# 检查过期
if time.time() > self.expiry.get(key, float('inf')):
self._pop(key)
return None
# 移动到最后(表示最近使用)
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key, value, ttl=None):
with self.lock:
if len(self.cache) >= self.capacity and key not in self.cache:
# 淘汰最早使用的(LRU)
oldest_key, _ = self.cache.popitem(last=False)
del self.expiry[oldest_key]
self.cache[key] = value
self.expiry[key] = time.time() + (ttl or self.default_ttl)
self.cache.move_to_end(key)
def _pop(self, key):
self.cache.pop(key, None)
self.expiry.pop(key, None)
平衡点:O(1)读写,但持有锁时间短,适合10万级缓存。
Sharded分段缓存(高并发读写)
import hashlib
from functools import lru_cache
class ShardedCache:
def __init__(self, shard_count=16, maxsize_per_shard=1000):
self.shards = [lru_cache(maxsize=maxsize_per_shard) for _ in range(shard_count)]
self.locks = [threading.Lock() for _ in range(shard_count)]
def _get_shard(self, key):
# 一致性哈希分片
hash_val = hashlib.md5(str(key).encode()).hexdigest()
return int(hash_val, 16) % len(self.shards)
def get(self, key):
shard_idx = self._get_shard(key)
with self.locks[shard_idx]:
return self.shards[shard_idx](key) # lru_cache的调用方式
def put(self, key, value):
shard_idx = self._get_shard(key)
with self.locks[shard_idx]:
# 需要绕过lru_cache直接调用
self.shards[shard_idx].cache_clear()
# 重新设置(或自定义存储)
平衡点:分段锁减少竞争,适合16-64核服务器。
Write-Through + Write-Behind 混合模式
class HybridCache:
def __init__(self, backend_store, buffer_size=100):
self.cache = {} # 热点数据
self.write_buffer = [] # 写缓冲
self.buffer_size = buffer_size
self.backend = backend_store
self.lock = threading.RLock()
# 后台写线程
self._flush_thread = threading.Thread(target=self._periodic_flush, daemon=True)
self._flush_thread.start()
def get(self, key):
with self.lock:
# 先查缓存
if key in self.cache:
return self.cache[key]
# 缓存未命中读后端
value = self.backend.get(key)
if value is not None:
self.cache[key] = value # 写入缓存
return value
def put(self, key, value):
with self.lock:
# 写入缓冲(快速返回)
self.write_buffer.append((key, value))
if len(self.write_buffer) >= self.buffer_size:
# 同步写回后端
self._flush_buffer()
# 更新缓存
self.cache[key] = value
def _flush_buffer(self):
# 批量写入后端
batch = self.write_buffer[:]
self.write_buffer.clear()
self.backend.batch_set(batch)
def _periodic_flush(self):
while True:
time.sleep(1)
if self.write_buffer:
with self.lock:
self._flush_buffer()
平衡点:写操作先缓冲再批量提交,适合后端是数据库或磁盘的场景。
性能对比与选择建议
| 策略 | 读性能 | 写性能 | 内存开销 | 适用场景 |
|---|---|---|---|---|
| 简单字典+Lru | 极高 | 高 | 高 | 单机临时热点 |
| 分片+LRU | 高 | 中等 | 中 | 多核服务器 |
| 写缓冲混合 | 中等 | 极高 | 低 | 数据库前端缓存 |
| Redis外部缓存 | 网络延迟 | 网络延迟 | 由配置决定 | 分布式系统 |
极致优化技巧
使用slots减少内存
class CacheEntry:
__slots__ = ('key', 'value', 'expiry')
def __init__(self, key, value, expiry):
self.key = key
self.value = value
self.expiry = expiry
避免频繁哈希(预计算)
class PrehashedCache:
def __init__(self):
self._store = {}
self._hash_func = hashlib.sha256 # 预定义
def put(self, key, value):
h = self._prehash(key) # 对外接口用原始key
self._store[h] = value
使用memoryview处理大对象
# 对大二进制数据用memoryview避免拷贝 cache[b'large_data'] = memoryview(big_bytes)
性能测试模板
import time
import random
from concurrent.futures import ThreadPoolExecutor
def test_cache(cache_class, **kwargs):
cache = cache_class(**kwargs)
keys = [f"key_{i}" for i in range(10000)]
# 写入测试
start = time.time()
for k in keys:
cache.put(k, random.randint(1,100))
write_time = time.time() - start
# 并发读取测试
def read_worker():
for _ in range(1000):
k = random.choice(keys)
cache.get(k)
start = time.time()
with ThreadPoolExecutor(max_workers=16) as ex:
list(ex.map(lambda _: read_worker(), range(100)))
read_time = time.time() - start
return {"write_ops/s": len(keys)/write_time,
"read_ops/s": 100000/read_time}
最终建议
- 读密集型(80%读):使用标准库
functools.lru_cache或cachetools - 写密集型:采用写缓冲+批量提交模式
- 混合场景:使用
OrderedDict+ 分片锁 + 后台淘汰线程 - 内存敏感:用
__slots__和array模块代替列表
平衡的关键在于分析实际负载:先profile看缓存命中率,再决定是优化读路径还是写路径,多数情况下,合理的TTL设置+LRU已经能解决90%的性能问题。