news 2026/4/17 20:40:34

从零打造超快本地 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/17 7:45:56

新手必看!用科哥镜像快速搭建Emotion2Vec+语音情感系统

新手必看&#xff01;用科哥镜像快速搭建Emotion2Vec语音情感系统 1. 为什么你需要这个语音情感识别系统&#xff1f; 你有没有遇到过这些场景&#xff1a; 客服质检团队每天要听上百条通话录音&#xff0c;靠人工判断客户情绪是否满意&#xff0c;效率低、主观性强&#xf…

作者头像 李华
网站建设 2026/4/17 19:36:13

AI团队部署规范:DeepSeek-R1生产环境最佳实践

AI团队部署规范&#xff1a;DeepSeek-R1生产环境最佳实践 在AI工程落地过程中&#xff0c;模型部署不是“跑通就行”的一次性任务&#xff0c;而是需要兼顾稳定性、可维护性、资源效率与团队协作的一整套工程实践。尤其当团队开始将具备数学推理、代码生成和逻辑推演能力的轻量…

作者头像 李华
网站建设 2026/4/11 0:06:23

Qwen-Image-2512省钱部署方案:按需GPU计费成本省60%

Qwen-Image-2512省钱部署方案&#xff1a;按需GPU计费成本省60% 你是不是也遇到过这样的问题&#xff1a;想跑一个高质量图片生成模型&#xff0c;但一看到显卡租用价格就犹豫了&#xff1f;动辄每小时十几块的A100/H100费用&#xff0c;跑几个小时就上百&#xff1b;自己买卡…

作者头像 李华
网站建设 2026/4/16 9:05:07

Sambert语音合成可扩展性:多线程并发处理部署压力测试

Sambert语音合成可扩展性&#xff1a;多线程并发处理部署压力测试 1. 引言&#xff1a;为什么我们需要关注语音合成的并发能力&#xff1f; 你有没有遇到过这种情况&#xff1a;一个语音合成服务刚上线&#xff0c;用户不多时响应飞快&#xff0c;结果一到促销活动或者流量高…

作者头像 李华
网站建设 2026/4/17 19:46:11

学习笔记——时钟系统与定时器

时钟系统与定时器 一、基本概念定义 1. 核心术语解析 定时器 (Timer)&#xff1a;通过对已知频率的时钟信号进行计数&#xff0c;实现时间测量、延时控制或事件计数功能的硬件模块或软件机制。 时钟 (Clock)&#xff1a;在电子系统中产生稳定周期性振荡信号的电路或组件&…

作者头像 李华