#kvrocks # redis数据结构编码 ## String - 存储在 metadata cf。 ### Key | ns size | ns | slot | user key | | ------- | ------- | ------ | -------- | | 1 byte | N bytes | 2 byte | N bytes | - **ns size**: namespace 长度 - **ns**: namespace 数据 - **slot** 仅在 cluster 特性启用后编码 - 示例:`SET foo bar` - key编码后为 `\v__namespacefoo - `\v`:11 的 ASCII 码,表示 ns 长度为 11 - `__namespace`:默认 ns - `foo`:user key ### Value | flag | expire | user value | | ------ | ------ | ---------- | | 1 byte | 8 byte | N bytes | - **flag**: - 包含了 encoding version 和 redis type - encoding version:为 1 时表示新版本,expire 使用 8 字节(旧版本 4 字节) - redis type:数据结构的类型枚举,如 string/hash 等 | encoding version | reserved | redis type | | ---------------- | -------- | ---------- | | 1 bit | 3 bit | 4 bit | - **expire**: - 过期的绝对时间,单位 ms - 大端编码,固定 64 字节 - flag + expire 对应 MetaData ## Hash - hash 的编码分为两部分: - metadata:包含 field 数量、过期时间等元数据,一个 kv 存储在 metadata cf - fields:每个 field 对应一个 kv,存储在 default cf ### Metadata - **key**:hash key,编码方式同 String - **value**:hash metadata | flag | expire | version | size | | ------ | ------ | ------- | ------ | | 1 byte | 8 byte | 8 byte | 8 byte | - flag 和 expire 编码同 String 类型 - **version**:每次创建 hash 结构时生成新的版本 - 高 53 bits 取当前时间的毫秒数 - 低 11 bits 取自增 couter - counter 启动时设为一个随机值,减少时钟回退时可能引发的版本冲突 - **size**:fields 的数量 ### Field - field key: - 前半部分跟 metadata key 编码接近(除了加了 key size,因为后面还有其他数据) - version 用于关联所属的 metadata | ns size | ns data | slot | key size | key data | version | field key | | ------- | ------- | ------ | -------- | -------- | ------- | --------- | | 1 byte | N bytes | 2 byte | 4 byte | N bytes | 8 byte | N bytes | - field value:无编码,直接存入 > [!summary] version > 使用 version 表示不同生命期的 hash,用于关联同一个生命期 hash 的 metadata 和 fields。 > 用于实现快速删除: > - 删除 hash 时只删除 metadata key,fields 通过 compaction filter 删除; > - compaction filter 中发现跟 field 相同 version 的 metadata 过期或者不存在时则可以回收; > ## Set - 整体与 hash 相同,除了 field value 为空。 ## List - 类似 hash,区分 metadata 和 elements(存放在 default cf)。 - 使用数组模拟 list,每个 node 分配一个整型 index 编号,相邻 node 之间 index 连续。 ### metadata - metadata key 跟其他结构相同。 - metadata value:增加了 list 专属的 head 和 tail 元数据 - head 和 tail 为整型,元素节点的编号(index),链表数据不包含 tail。 - 节点编号的分配: - list 上的节点编码代表了前后关系,且是连续的 - head 和 tail 初始值为 UINT64_MAX / 2 - lpush 时新节点编号为 head - 1,同时更新 head - rpush 时新节点编号为 tail,同时 tail 自增 | flag | expire | version | size | head | tail | | ------ | ------ | ------- | ------ | ------ | ------ | | 1 byte | 8 byte | 8 byte | 8 byte | 8 byte | 8 byte | ### nodes - 与 hash field 相同,使用节点编号作为 field key - node key: | ns size | ns data | slot | key size | key data | version | index | | ------- | ------- | ------ | -------- | -------- | ------- | ------ | | 1 byte | N bytes | 2 byte | 4 byte | N bytes | 8 byte | 8 byte | > [!summary] node index > node index 的大小代表了在链表上的前后位置,且连续 > - 优势: > - 遍历如 LRANGE 性能较好,顺序扫描 > - 按 index 读写单节点性能最优 > - LINDEX 可实现为 Get 操作,Get(head + index) > - LSET 可实现为一个 Put 操作 > - 每个 node 上不用额外存储前驱和后驱,空间较优 > - 劣势: > - 修改如增删中间节点时需要维护连续性,可能重写前后所有节点, > - 例如 LREM/LINSERT 操作可能涉及移动、重写大量节点 > [!question] > 三者性能的均衡:LRANGE,LINDEX,LREM 以及空间 > 是否可以牺牲 index 的连续性(会牺牲 LINDEX 性能),只保证顺序性,来减少修改时的维护代价 ## Zset - 分为三部分: 1. metadata: 编码与 hash 相同,存放在 metadata cf。 2. member:每元素一个 kv,存放在 default cf 3. score:每元素一个 kv,存放在 zset_score cf ### member - member key :同 hash field 的编码相同 - member value:存放 score - double 先转 uint64 再存储 ### score - score key:类似 hash field,相当于把 field key 换成 score + member_key | ns size | ns data | slot | key size | key data | version | score | member | | ------- | ------- | ------ | -------- | -------- | ------- | ------ | ------- | | 1 byte | N bytes | 2 byte | 4 byte | N bytes | 8 byte | 8 byte | N bytes | - score value:空 --- # 总结 > [!summary] Metadata > 每种数据结构都有一条 metadata KV entry: > - 所有数据结构 metadata key 编码都一样; > - metadata value 统一编码了 flags 和 expire 信息; > - String 类型直接将 value 编码在 metadata value 中; > - 集合类的 metadata value 中还编码了 version、size; > - list 额外编码了 head 和 tail; > > metadata 和集合数据分离到不同 cf: > - 访问 metadata 的效率变高(数据量小),许多命令只需要访问 metadata > --- # TODO - [ ] hyperloglog - [ ] bitmap