Key 的命名方式影响数据的可维护性,Value 的大小和结构影响查询效率与内存利用率。本文档从 Key 设计原则、大 Key 规避策略和数据类型选择三个维度,定义具体的设计准则,用于在项目初期建立合理的数据模型,减少慢查询和内存膨胀问题。
一、Key 设计原则
Key 的命名直接影响数据的可维护性、运维效率和内存利用率。以下六条原则从简洁性、字符集、语义结构、类型标识、长度控制和特殊字符六个维度,定义 Key 命名的标准化规范。
1. 简洁性
在保证语义清晰的前提下,尽量缩短 Key 长度。当 Key 总量达到百万级甚至千万级时,Key 本身占用的内存空间不可忽略。每个 Key 缩短10个字节,百万 Key 即可节省约10MB内存。
分类 | 示例 | 说明 |
正确 | cx:orderdb:order:202401150001:hash | 简洁明确,总长度40字节。 |
错误 | cx:orderdb:orderdb_order_detail_info:202401150001 | 表名冗余重复库名"orderdb",增加18字节无效长度。 |
2. 命名字符
以英文字母开头,只能包含大小写字母、数字、竖线(
|)、下划线(_)、英文点号(.)和英文半角冒号(:)。统一字符集有助于避免编码歧义,同时确保 Key 在日志、监控和运维工具中能被正确解析。分类 | 示例 | 说明 |
正确 | cx:userdb:session:U100238:string | 仅使用字母、数字和冒号,符合字符集规范。 |
错误 | user 100238 | 包含空格, SCAN 命令无法正确匹配。 |
错误 | cx:orderdb:order:{202401150001} | 使用花括号,与 Hashtag 语法冲突。 |
3. 语义分割
使用英文半角冒号(
:)分隔不同业务含义层级(如 业务:模块:标识),同一层级内的单词之间使用英文半角点号(.)连接。清晰的分层结构可让运维人员在排查问题时快速定位 Key 所属的业务模块。分类 | 示例 | 说明 |
正确 | cx:userdb:profile:U100238:hash | 用冒号分层:业务 → 库 → 表 → ID → 类型。 |
正确 | cx:userdb:basic.info:U100238:string | 同层词汇"basic"和"info"用点号连接。 |
错误 | cx_userdb_profile_U100238 | 使用下划线分隔层级,与冒号分层规范不一致。 |
4. 类型后缀
在 Key 末尾附加 Value 类型标识。当团队成员看到 Key 名称时,无需执行
TYPE 命令即可判断对应的数据结构,降低沟通成本。分类 | 示例 | 说明 |
正确 | cx:orderdb:order:202401150001:hash | 末尾 :hash 表明 Value 为 Hash 类型。 |
正确 | cx:userdb:session:U100238:string | 末尾 :string 表明 Value 为 String 类型。 |
错误 | cx:orderdb:order:202401150001 | 缺少类型后缀,无法判断 Value 是 String 还是 Hash。 |
5. 控制 Key 大小
Key 名称建议控制在128字节以内。过长的 Key 占用额外内存,同时增加比较和哈希计算的开销,在高并发场景下对 CPU 产生可测量的影响。
分类 | 示例 | 说明 |
正确 | cx:userdb:session:U100238:string | 总长度36字节,远低于128字节上限。 |
错误 | cx:orderdb:orderdb_order_detail_info_full_record:202401150001:hash | 总长度65字节,包含冗余描述,应精简。 |
6. 禁止特殊字符
禁止包含
\\、*、?、{}、[]、()、空格、单双引号和转义字符。这些字符与 glob 通配符和 Shell 转义规则冲突,会导致 SCAN、KEYS 等模式匹配命令无法正确检索,同时在日志分析和脚本处理中引发解析异常。分类 | 示例 | 说明 |
正确 | cx:orderdb:order:202401150001:hash | 不含任何特殊字符。 |
错误 | cx:orderdb:order:{202401150001} | 花括号与 Hashtag 语法冲突。 |
错误 | cx:orderdb:order:2024*:hash | 星号为 glob 通配符, SCAN 无法精确匹配。 |
错误 | user "test" | 包含空格和双引号,Shell 和日志解析会出错。 |
二、Value 设计原则
Value 的体积和集合元素数量需严格控制,避免产生大 Key。当某个 Key 的 Value 体积过大或集合元素过多时,读写该 Key 会占用较长的 CPU 时间片,阻塞其他请求的处理,表现为慢查询增多;同时,大 Value 在网络传输时占用较多网卡带宽,少量大 Key 的并发读取即可造成网络拥塞,影响同节点上其他业务的响应时间。本节定义大 Key 的判定阈值、日常开发建议阈值及标准化拆分策略。
1. 大 Key 判定阈值
当某个 Key 达到下表条件时,应排查并进行拆分或重构:
数据类型 | 大 Key 判定条件 | 风险说明 |
String | Value 值超过1MB。 | 单次读写占用过长 CPU 时间片,阻塞同线程其他请求。 |
Set | 成员数量超过10000个。 | 全量遍历耗时线性增长, SMEMBERS 等命令可能触发慢查询。 |
List | 成员数量超过10000个。 | 同上, LRANGE 0 -1 全量读取将阻塞服务端线程。 |
Hash | 成员数量超过1000个,且所有字段的总 Value 大小超过 1000MB。 | HGETALL 返回的数据量过大,网络传输耗时显著增加。 |
Sorted Set | 成员数量超过10000个。 | ZRANGEBYSCORE 大范围查询耗时线性增长。 |
2. 日常开发建议阈值
为了在问题发生之前将风险控制在安全范围内,建议在日常开发中遵循更严格的阈值标准:
数据类型 | 建议阈值 | 规范建议 |
String | ≤ 10KB | 超过10KB时按业务维度拆分为多个子 Key。 |
Hash / List / Set / ZSet | 元素个数 ≤ 5000 | 超过5000时按字段范围、时间区间或业务分类拆分。 |
3. 大 Key 拆分策略
当 Value 超过上述阈值时,应根据数据类型选择对应的拆分方式。以下按数据类型分别说明拆分策略和代码实现规范。
String 类型拆分
当单个 String 的 Value 超过10KB时,按业务维度将数据拆分为多个子 Key。
反面示例(单 Key 超限):将整个订单的 JSON 序列化后存入一个 String,体积达到50KB。
# 潜在风险:50KB 的 Value 在高并发读取时占用大量网卡带宽SET cx:orderdb:order:202401150001:string '{"status":"paid","amount":12580,"items":[...大量商品明细...],"logistics":{...物流信息...}}'
正确示例(按业务维度拆分):将订单拆分为基础信息、商品明细、物流信息三个子 Key,每个 Key 的 Value 控制在10KB以内。
# 拆分后每个子 Key 的 Value 体积可控,支持按需读取SET cx:orderdb:order:202401150001:base:string '{"status":"paid","amount":12580}'SET cx:orderdb:order:202401150001:items:string '[...商品明细...]'SET cx:orderdb:order:202401150001:logistics:string '{...物流信息...}'
Hash 类型拆分
当单个 Hash 的字段数超过5000个时,按字段范围或业务含义拆分为多个 Hash Key。
反面示例(单 Hash 超限):将10000个用户属性全部存入一个 Hash。
# 潜在风险:HGETALL 返回 10000 个字段,网络传输和客户端解析耗时显著HSET cx:userdb:user.attrs:hash field_0001 value_0001 field_0002 value_0002 ... field_10000 value_10000
正确示例(按字段范围拆分):每个子 Hash 控制在5000个字段以内。
# 按字段范围拆分,HGETALL 单个子 Hash 的返回量可控HSET cx:userdb:user.attrs:part1:hash field_0001 value_0001 ... field_5000 value_5000HSET cx:userdb:user.attrs:part2:hash field_5001 value_5001 ... field_10000 value_10000
List / Set / Sorted Set 类型拆分
当集合元素超过5000个时,按时间范围、ID 区间或业务分类拆分为多个子 Key。
反面示例(单集合超限):将全年的用户操作日志存入一个 List,元素数达到数万。
# 潜在风险:LRANGE 0 -1 全量读取数万条记录,阻塞服务端线程数秒LPUSH cx:userdb:user.oplog:U100238:list "2024-01-01 login" "2024-01-01 query" ... "2024-12-31 logout"
正确示例(按月份拆分):每个 List 仅存储当月日志,单个 List 元素数可控。
# 按月份拆分后,单个 List 的元素数控制在合理范围内LPUSH cx:userdb:user.oplog:U100238:202401:list "2024-01-01 login" "2024-01-01 query" ...LPUSH cx:userdb:user.oplog:U100238:202402:list "2024-02-01 login" ...
三、数据类型选择指南
KeeWiDB 兼容 Redis 协议,提供五种基础数据类型。不同类型在内部编码和操作复杂度上存在差异,选择合适的类型可以在相同硬件条件下提升读写效率、降低内存占用。以下按数据类型分别说明适用场景、典型用法及代码示例。
1. String(字符串)
存储单一值,如状态标记、序列化对象、二进制数据等。适用于分布式锁、计数器、会话缓存、配置信息等场景。
# 会话缓存:存储用户登录态,设置 30 分钟过期SET cx:userdb:session:U100238:string '{"token":"abc123","role":"admin"}' EX 1800# 计数器:文章阅读量自增INCR cx:articledb:article:A20240001:views:string
2. Hash(哈希表)
存储具有多个字段的结构化数据,支持对单个字段的独立读写,无需整体序列化。适用于用户画像(姓名、年龄、地域)、商品属性、设备信息等场景。
# 用户画像:聚合存储多个属性HMSET cx:userdb:profile:U100238:hash name "张三" age 28 region "广州"# 按需读取单个字段HGET cx:userdb:profile:U100238:hash name# 批量读取部分字段HMGET cx:userdb:profile:U100238:hash name age
3. List(列表)
维护有序的元素序列,支持从两端执行 O(1) 复杂度的插入和弹出操作。适用于消息时间线、最近浏览记录、任务队列等场景。
# 最近浏览记录:从左侧插入最新记录,保留最近 50 条LPUSH cx:userdb:recent.views:U100238:list "商品A_2024-01-15"LTRIM cx:userdb:recent.views:U100238:list 0 49
4. Set(集合)
存储无序且不重复的元素集合,支持交集、并集、差集运算。适用于标签系统、共同好友计算、去重集合等场景。
# 标签系统:为用户添加兴趣标签SADD cx:userdb:tags:U100238:set "保险" "理财" "汽车"# 共同好友:计算两个用户的共同标签SINTER cx:userdb:tags:U100238:set cx:userdb:tags:U200456:set
5. Sorted Set(有序集合)
每个元素关联一个分值(score),按分值排序存储,支持范围查询和排名查询。适用于实时排行榜、延迟任务调度、带权重的投票系统等场景。
# 实时排行榜:以销售额作为分值ZADD cx:salesdb:rank:202401:zset 125800 "华南区" 98500 "华东区" 76200 "华北区"# 查询 Top 3ZREVRANGE cx:salesdb:rank:202401:zset 0 2 WITHSCORES
说明:
KeeWiDB 极速版在底层使用了 ziplist(压缩列表)等紧凑编码方式来优化小型数据结构的内存占用。ziplist 将元素存储在一段连续的内存块中,省去了指针开销,但由于缺少索引,查找、插入和删除操作均需线性扫描。因此,当元素数量较少(通常不超过128个)且单个元素体积较小时,ziplist 可以减少内存占用;当元素规模增长后,应让系统自动转换为标准数据结构以维持查询效率。
6. 多属性存储:用 Hash 替代多个 String Key
当一个实体拥有多个属性时(如用户的姓名、年龄、爱好),建议使用一个 Hash Key 来聚合存储,而非为每个属性创建独立的 String Key。这样做有三个好处:一是减少 Key 的总数量,降低数据库的键空间占用;二是可以通过
HGET、HMGET 按需读取部分字段,避免不必要的数据传输;三是在逻辑上保持数据的内聚性,便于后续维护。反面示例(多 String Key 分散存储):为订单的每个属性创建单独的 String Key,5个属性即产生5个 Key,百万订单将占用500万个键空间。
# 潜在风险:每个属性一个 Key,键空间膨胀 5 倍,内存开销显著增加SET cx:orderdb:order:202401150001:status paidSET cx:orderdb:order:202401150001:amount 12580SET cx:orderdb:order:202401150001:product car_insuranceSET cx:orderdb:order:202401150001:customer C100238SET cx:orderdb:order:202401150001:create_time 2024-01-15T10:30:00
正确示例(Hash 聚合存储):使用一个 Hash Key 聚合全部属性,百万订单仅占100万个键空间,且支持按需读取单个字段。
# 一个 Hash Key 聚合全部属性,减少键空间占用HMSET cx:orderdb:order:202401150001:hash status paid amount 12580 product car_insurance customer C100238 create_time 2024-01-15T10:30:00# 按需读取单个字段,无需加载整个订单HGET cx:orderdb:order:202401150001:hash status# 批量读取部分字段HMGET cx:orderdb:order:202401150001:hash status amount customer