news 2026/4/23 3:00:33

从零打造超快本地 KV 存储:mmap + 哈希索引完胜 Redis 的极致优化之旅

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零打造超快本地 KV 存储:mmap + 哈希索引完胜 Redis 的极致优化之旅

从零打造超快本地 KV 存储:mmap + 哈希索引完胜 Redis 的极致优化之旅

开篇:当我决定挑战 Redis

三个月前,我在优化一个实时推荐系统时遇到了瓶颈。系统需要在 10ms 内完成用户画像查询,但 Redis 的网络往返时间(RTT)就占用了 3-5ms。即使使用 Redis Pipeline,批量操作的延迟仍然无法满足需求。

一个大胆的想法浮现:能否在本地实现一个比 Redis 更快的 KV 存储?

经过数周的研发和优化,我基于mmap(内存映射文件)+ 哈希索引实现了一个单机 KV 存储引擎,性能测试结果令人震撼:

操作Redis(本地)我的实现提升倍数
单次 GET0.08ms0.003ms26.7x
单次 SET0.12ms0.005ms24.0x
批量读取(1000条)15ms0.8ms18.8x
内存占用(100万条)180MB85MB2.1x

今天,我将手把手带你实现这个超快的本地 KV 存储引擎,揭开性能优化的底层秘密。

核心设计:为什么 mmap + 哈希索引如此之快?

设计哲学

  1. 零拷贝:mmap 将文件直接映射到内存,避免 read/write 系统调用
  2. 本地访问:消除网络 RTT,直接内存操作
  3. 高效索引:哈希表 O(1) 查找,远超 B+ 树的 O(log n)
  4. 持久化:利用操作系统的页缓存机制,自动同步磁盘

架构设计图

┌─────────────────────────────────────────────┐ │ 应用层 API │ │ get(key) / set(key, value) / delete(key) │ └──────────────────┬──────────────────────────┘ │ ┌──────────────────▼──────────────────────────┐ │ 哈希索引层 │ │ ┌──────────┬──────────┬──────────┐ │ │ │ Bucket 0 │ Bucket 1 │ Bucket N │ │ │ └────┬─────┴────┬─────┴────┬─────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ [Entry] → [Entry] → [Entry] │ │ (key_hash, offset, length) │ └──────────────────┬──────────────────────────┘ │ ┌──────────────────▼──────────────────────────┐ │ mmap 存储层 │ │ ┌────────────────────────────────────┐ │ │ │ Header | Data Block 1 | Block 2 │ │ │ └────────────────────────────────────┘ │ │ ▲ │ │ │ mmap() 映射 │ │ ┌─────────┴──────────────────────────┐ │ │ │ 磁盘文件 (data.db) │ │ │ └────────────────────────────────────┘ │ └─────────────────────────────────────────────┘

第一步:实现 mmap 存储引擎

核心代码实现

# mmap_storage.pyimportmmapimportosimportstructfromtypingimportOptional,TupleclassMmapStorage:""" 基于 mmap 的存储引擎 文件格式: ┌──────────────┬──────────────┬─────────────┐ │ Header (64B) │ Data Block 1 │ Data Block 2│ └──────────────┴──────────────┴─────────────┘ Header: - magic (4B): 魔数,用于文件校验 - version (4B): 版本号 - data_offset (8B): 数据起始位置 - data_size (8B): 已使用的数据大小 - reserved (40B): 保留字段 Data Block: - length (4B): 数据长度 - data (variable): 实际数据 """HEADER_SIZE=64MAGIC=0x4B564442# "KVDB" in hexVERSION=1INITIAL_SIZE=1024*1024*100# 100MB 初始大小def__init__(self,filepath:str):self.filepath=filepath self.file=Noneself.mmap=Noneself._initialize()def_initialize(self):"""初始化或打开存储文件"""# 创建或打开文件is_new=notos.path.exists(self.filepath)self.file=open(self.filepath,'r+b'ifnotis_newelse'w+b')ifis_new:# 新文件:初始化 header 并预分配空间self.file.write(b'\x00'*self.INITIAL_SIZE)self.file.flush()# 写入 headerheader=struct.pack('<IIQQ40s',self.MAGIC,self.VERSION,self.HEADER_SIZE,# data_offset0,# data_sizeb'\x00'*40# reserved)self.file.seek(0)self.file.write(header)self.file.flush()# 创建内存映射self.mmap=mmap.mmap(self.file.fileno(),0)# 验证文件格式ifnotis_new:self._validate_header()def_validate_header(self):"""验证文件头"""self.mmap.seek(0)magic,version=struct.unpack('<II',self.mmap.read(8))ifmagic!=self.MAGIC:raiseValueError(f"无效的文件格式:magic ={magic:08x}")ifversion!=self.VERSION:raiseValueError(f"不支持的版本:{version}")def_get_data_size(self)->int:"""获取已使用的数据大小"""self.mmap.seek(16)returnstruct.unpack('<Q',self.mmap.read(8))[0]def_set_data_size(self,size:int):"""设置已使用的数据大小"""self.mmap.seek(16)self.mmap.write(struct.pack('<Q',size))defwrite(self,data:bytes)->int:""" 写入数据,返回偏移量 Args: data: 要写入的数据 Returns: 数据在文件中的偏移量 """data_size=self._get_data_size()offset=self.HEADER_SIZE+data_size# 检查是否需要扩展文件required_size=offset+4+len(data)current_size=self.mmap.size()ifrequired_size>current_size:self._expand(required_size)# 写入数据长度self.mmap.seek(offset)self.mmap.write(struct.pack('<I',len(data)))# 写入数据self.mmap.write(data)# 更新 data_sizeself._set_data_size(data_size+4+len(data))returnoffsetdefread(self,offset:int)->bytes:""" 从指定偏移量读取数据 Args: offset: 数据偏移量 Returns: 读取的数据 """self.mmap.seek(offset)# 读取长度length=struct.unpack('<I',self.mmap.read(4))[0]# 读取数据returnself.mmap.read(length)def_expand(self,new_size:int):"""扩展文件大小"""# 计算新的大小(按 2 倍增长)current_size=self.mmap.size()target_size=current_sizewhiletarget_size<new_size:target_size*=2print(f"扩展文件:{current_size/1024/1024:.2f}MB →{target_size/1024/1024:.2f}MB")# 关闭当前 mmapself.mmap.close()# 扩展文件self.file.seek(target_size-1)self.file.write(b'\x00')self.
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 22:51:27

Speech Seaco Paraformer ASR部署教程:阿里中文语音识别模型实战指南

Speech Seaco Paraformer ASR部署教程&#xff1a;阿里中文语音识别模型实战指南 1. 引言&#xff1a;为什么选择这款语音识别方案&#xff1f; 你有没有遇到过这样的情况&#xff1a;会议录音堆成山&#xff0c;逐字整理费时又费力&#xff1b;采访素材长达数小时&#xff0…

作者头像 李华
网站建设 2026/4/22 3:54:59

Qwen3-0.6B多轮对话实战:Session管理与状态保持教程

Qwen3-0.6B多轮对话实战&#xff1a;Session管理与状态保持教程 你是否在使用Qwen3-0.6B时&#xff0c;发现每次提问都像第一次对话&#xff1f;明明上一轮聊得好好的&#xff0c;模型却“转头就忘”&#xff1f;这其实是缺少会话状态管理的典型表现。别担心&#xff0c;本文将…

作者头像 李华
网站建设 2026/4/22 12:55:20

Z-Image-Turbo输出模糊?调整参数后效果立现

Z-Image-Turbo输出模糊&#xff1f;调整参数后效果立现 在实际使用 Z-Image-Turbo 进行文生图任务时&#xff0c;不少用户反馈&#xff1a;明明提示词写得清晰、显存充足、模型也加载成功&#xff0c;可生成的图片却常常偏灰、细节发虚、边缘糊成一片&#xff0c;甚至人物五官…

作者头像 李华
网站建设 2026/4/23 7:30:54

java_ssm68社区志愿者服务

目录具体实现截图Java SSM68 社区志愿者服务系统摘要系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 Java SSM68 社区志愿者服务系统摘要 社区志愿者服务系统基于Java SSM&#xff08;Spring S…

作者头像 李华
网站建设 2026/4/23 7:26:23

一键启动SenseVoiceSmall:Gradio WebUI免配置环境实战教程

一键启动SenseVoiceSmall&#xff1a;Gradio WebUI免配置环境实战教程 1. 学习目标与前置知识 你是否还在为语音识别工具操作复杂、依赖繁多而烦恼&#xff1f;今天要介绍的 SenseVoiceSmall 模型&#xff0c;不仅支持中、英、日、韩、粤五种语言的高精度转写&#xff0c;还能…

作者头像 李华
网站建设 2026/4/23 7:30:54

儿童AI伦理实践:Qwen萌宠生成器部署中的责任边界探讨

儿童AI伦理实践&#xff1a;Qwen萌宠生成器部署中的责任边界探讨 在人工智能技术快速渗透日常生活的今天&#xff0c;面向儿童的应用场景正变得越来越重要。而当AI开始参与儿童内容创作——比如生成他们喜爱的动物形象时&#xff0c;我们不仅要关注“能不能做”&#xff0c;更…

作者头像 李华