bk-nodeman
bk-nodeman copied to clipboard
[FEATURE] Big Key 缓存优化
背景
为什么需要
功能
你想要什么功能
功能实现
建议的方案
实现方案
功能自测
代码变更覆盖功能点需要自测并截图
功能点 1
描述代码变更涉及功能点及自测截图
功能点 2
描述代码变更涉及功能点及自测截图
...
初版方案
缓存超过
5MB
以上的都应该处理
- 通过判断
key
大小决定是否分割 - 如果需要分割, 区分
REDIS BACKEND
和DB BACKEND
是否有区别处理,建议DB
缓存使用 字符串分片 - 这里可能要考虑增加中间层去缓存分片信息
- 通过缓存的分片信息逐个 join 之前切分的 key
具体实现
采取字符串分片的方式存储:
[ ] 缓存内容通过转为utf-8字节表示判断大小并根据字节码拆分
[ ] 缓存内容通过sys.getsizeof计算判断大小并拆分
针对DB以3MB
大小为标准对缓存项进行判断拆分
针对Redis以5MB
大小为标准对缓存项进行判断拆分(Redis的存取比DB存储效率更高可以使用相对更大的分片存储来减少多次获取数据的动作)
分片大小可设置为可配置项
原有的key根据分片数量修改为key|{num}
的形式并将其维护以 dict 的方式存储在缓存后端中
缓存数据为key|{num}: value_{num}
形式存储到缓存后端中
获取时考虑使用多线程方式获取数据?
@ZhuoZhuoCrayon
修改apps/core/concurrent/cache.py 装饰器类FuncCacheDecorator
新增定义
DEFAULT_CACHE_PIECE_LENGTH_DB = 1024 * 1024 * 3
DEFAULT_CACHE_PIECE_LENGTH_REDIS = 1024 * 1024 * 5
新增分片相关函数
# 判断缓存对象大小
def check_cache_length(self, using: str, value: typing.Any) -> bool:
# 转utf-8计算缓存数据长度 根据缓存后端判断是否需要分片
'''
value转码
if value > DEFAULT_CACHE_PIECE_LENGTH_DB:
return True
return False
'''
# 分片函数
def split_cache(self, using: str, key: str, value: typing.Any) -> typing.Dict[str, str]:
# 根据缓存后端将缓存对象按对应的大小切分并返回分片字典
'''
片数 = value转码 / DEFAULT_CACHE_PIECE_LENGTH_DB
parts = [value转码[i:i+1024*1024].decode('utf-8') for i in range(0, len(value转码), DEFAULT_CACHE_PIECE_LENGTH_DB)]
return {f'key_{num}':parts[num] for num in range(片数)}
'''
# 判断是否分片存储
def check_is_split(self, key: str, cache_result: typing.Any) -> bool
# 判断cache_result是不是分片的key组合成的列表
'''
cache_result判断是否为列表
非列表返回False
列表则判断是否形如key_{num}的形式,是返回True
'''
# 组合分片信息函数
def join_splitted_cache(self, keys: List[str]) -> typing.Any
# 根据获取到的分片的key到缓存后端中获取对应的分片结果 按照keys顺序组合并返回
# 这里使用多线程的方式获取 等待任务完成后进行组合
'''
创建线程池
并发任务 map()
组合结果返回
'''
修改相关函数
def get_from_cache(self, using: str, key: str) -> typing.Any
# 增加分片的判断分支
'''
是被分片的key走分片获取分支
否则走原流程
'''
def set_to_cache(self, using: str, key: str, value: typing.Any):
# 增加分片的判断分支
'''
是需要分片的key走分片分支存入缓存后端
否则走原流程
'''