news 2026/6/10 13:30:57

从零打造超快本地 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/6/3 16:12:55

YOLOv10官版镜像测评:轻量模型在Jetson上的表现

YOLOv10官版镜像测评&#xff1a;轻量模型在Jetson上的表现 当边缘设备需要在毫秒级响应中识别行人、车辆或工业零件时&#xff0c;模型不能只靠“参数少”来标榜轻量——它得真正在 Jetson Orin NX 这类 15W 功耗的嵌入式平台上跑得稳、看得清、判得准。YOLOv10 官版镜像正是…

作者头像 李华
网站建设 2026/6/4 14:17:24

批量处理100张截图?cv_resnet18_ocr-detection实测效率惊人

批量处理100张截图&#xff1f;cv_resnet18_ocr-detection实测效率惊人 你有没有过这样的经历&#xff1a;手头堆着几十甚至上百张手机/电脑截图&#xff0c;里面全是产品参数、聊天记录、订单信息、会议纪要——每一张都藏着关键文字&#xff0c;但手动一张张点开、放大、识别…

作者头像 李华
网站建设 2026/5/6 5:57:49

GPEN与Runway ML对比:轻量级图像修复工具成本效益评测

GPEN与Runway ML对比&#xff1a;轻量级图像修复工具成本效益评测 1. 为什么需要这场对比&#xff1f; 你是不是也遇到过这些情况&#xff1a; 手里有一张老照片&#xff0c;人脸模糊、噪点多&#xff0c;想修复却找不到趁手的工具&#xff1b;做电商运营&#xff0c;每天要…

作者头像 李华
网站建设 2026/5/30 16:13:15

前端小白别慌:30分钟搞懂CSS精灵+background属性实战技巧

前端小白别慌&#xff1a;30分钟搞懂CSS精灵background属性实战技巧 前端小白别慌&#xff1a;30分钟搞懂CSS精灵background属性实战技巧为啥你的网页图片加载慢得像蜗牛&#xff1f;CSS 精灵不是玄学&#xff0c;是老前端省流量的祖传手艺background 属性全家桶到底怎么用才不…

作者头像 李华
网站建设 2026/6/6 5:43:33

复杂背景也不怕,科哥模型精准识别发丝边缘

复杂背景也不怕&#xff0c;科哥模型精准识别发丝边缘 1. 引言&#xff1a;为什么传统抠图搞不定发丝&#xff1f; 你有没有遇到过这种情况&#xff1a;一张人像照片&#xff0c;头发飘逸&#xff0c;背景却乱七八糟——树影、栏杆、反光&#xff0c;甚至还有另一张人脸。想把…

作者头像 李华
网站建设 2026/6/2 13:56:35

java_ssm66电影评分推荐解说分析系统演gl4zm

目录具体实现截图系统概述技术架构核心功能数据处理特色创新应用价值系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 系统概述 Java_SSM66电影评分推荐解说分析系统是一个基于SSM&#xff08;Sp…

作者头像 李华