KeeWiDB 兼容 Redis 协议,但在架构上属于磁盘型数据库,数据访问涉及磁盘 IO,其延迟特性与内存型 Redis 存在显著差异。由于磁盘操作耗时远高于内存,命令复杂度对响应时间的影响具有放大效应。为确保数据库响应时间及系统整体稳定性,本文档遵循"先规避风险、再规范用法"的原则,定义以下使用准则:
1. 禁用命令:明确禁止在生产环境执行的高危命令。
2. O(N) 命令管控:识别高复杂度命令的潜在风险,提供标准化替代方案。
3. 批量操作规范:定义单批次处理的数量阈值,防止磁盘 IO 累加导致请求超时。
4. 功能限制:界定存储架构层面的约束,规避非兼容特性调用。
5. Lua 脚本使用规范:约束脚本执行逻辑,确保其在分布式集群环境下的正确性。
6. Hashtag 使用规范:规范 Key 路由逻辑,防止数据分布倾斜。
一、禁用命令
受限于磁盘 IO 独占风险或数据安全考虑,以下命令不建议在生产环境的业务逻辑中直接调用。为确保系统吞吐量及响应延迟的稳定,本节提供了对应的标准化替代路径,建议在开发设计阶段优先采用。
1. 默认禁用命令清单
命令 | 禁用原因 | 替代方案 |
KEYS | 对整个键空间执行全量扫描,复杂度 O(N)。在百万级 Key 的实例上,单次执行可能阻塞数据库数秒,影响所有并发请求 | 使用 SCAN 命令分批遍历 |
FLUSHALL / FLUSHDB | 清空全部或当前库的数据,产生大量磁盘 IO 和 CPU 开销,且操作不可逆 | 通过控制台的清空实例功能执行,该操作具备二次确认和审计日志 |
CLIENT LIST | 当连接数较高时(如数千并发),遍历并序列化所有连接信息会增加 CPU 和内存开销 | 通过提交工单获取连接信息 |
2. 自定义禁用
如需根据业务需求禁用其他命令,可通过调整参数
disable-command-list 进行配置。二、O(N) 命令管控
KeeWiDB 的磁盘存储特性决定了 O(N) 命令的每个元素访问均可能涉及物理磁盘 IO。当 N 规模较大时,累计耗时将从毫秒级线性上升至秒级,不仅会导致当前请求超时,还会长时间占用后端执行线程,引发同节点其他请求的排队堆积。本节界定高危命令的识别标准,并提供标准化替代路径,用于指导开发阶段的方案选型。
1. 高危命令识别
以下命令在处理大规模数据集合时具有较高的执行风险,需严格评估其元素规模:
命令 | 风险说明 | 规范建议 |
HGETALL | 一次性返回 Hash 全部字段。字段数达到数千级时,单次调用涉及数 MB 数据的磁盘读取与网络传输。 | 字段数 > 500 时禁用,改用 HGET/HMGET/HSCAN。 |
LRANGE | 返回 List 指定范围元素。若执行 0 -1 等全量遍历操作,将引发持续的磁盘扫描。 | 禁止对未知长度的 List 执行全量查询,改为分段读取。 |
SMEMBERS | 返回 Set 全部成员,将引发持续的磁盘扫描。 | 建议替换为 SSCAN。 |
ZRANGE | 返回有序集合成员。耗时随范围扩大呈线性增长,且涉及较多磁盘读。 | 控制 Range 区间,或改用 ZSCAN。 |
SINTER/SUNION/ZINTERSTORE/ZINTERSTORE | 多集合交集或并集运算。复杂度取决于参与集合的成员总数,在集合规模不对称时性能波动剧烈。 | 在业务层执行逻辑交集计算,减轻服务端压力。 |
2. 标准替代方案:游标式扫描(SCAN 类命令)
为规避大规模数据访问带来的阻塞风险,应采用
HSCAN、SSCAN、ZSCAN 等游标式命令。通过分批迭代,将单次请求的 IO 耗时和传输数据量锁定在可控范围内。反面示例(全量读取):当集合包含万级元素时,下述操作将导致数秒的独占阻塞。
# 潜在风险:字段数达 5000+ 时,触发秒级延迟HGETALL cx:orderdb:order_index:hash# 潜在风险:成员数达 10000+ 时,导致磁盘 IO 饱和SMEMBERS cx:userdb:active_users:set
正确示例(分批迭代):通过设置合理的 COUNT 参数,平衡执行效率与系统稳定性。
# 分段获取,建议单次 COUNT 值在 100-500 之间HSCAN cx:orderdb:order_index:hash 0 COUNT 200# 配合模式匹配进行分批扫描(不指定COUNT值)SSCAN cx:userd b:active users:set 0 MATCH U10*
3. COUNT 参数使用建议
使用场景 | COUNT 参数建议 | 原因 |
未指定 MATCH 参数 | 指定合适的 COUNT 值(建议 ≤ 1000) | 控制每轮扫描返回的元素上限,避免单次返回数据量过大 |
已指定 MATCH 参数 | 使用数据库默认值,不额外指定 COUNT | MATCH 过滤在扫描之后执行。若 COUNT 过大,可能出现"扫描了大量数据但匹配结果极少"的低效情况 |
三、批量操作规范
KeeWiDB 的数据主要存储在磁盘上,磁盘寻址耗时与网络往返耗时处于同一量级(均为毫秒级)。将多个命令打包发送所节省的网络往返时间,会被磁盘 IO 的线性累加所抵消——当单批次包含的 Key 数量超过阈值时,整批请求的总耗时将突破 Proxy 超时上限,引发请求积压与客户端报错。本节定义单批次操作的数量阈值与拆分策略,用于指导批量接口的设计与调用。
1. 数量限制
操作方式 | 数量限制 | 风险说明 | 规范建议 |
MGET / MSET | 单次 ≤ 20个元素 | 每个 Key 的磁盘读取耗时线性累加,超限后整批耗时突破超时阈值。 | 超过20个 Key 时,拆分为多批顺序发送。 |
Pipeline | 单次 ≤ 20条命令 | 打包命令在服务端串行执行,命令数过多将导致后端线程长时间占用。 | 超过20条命令时,拆分为多个 Pipeline 分批执行。 |
2. 代码实现规范
反面示例(单批超限):单次批量100个 Key,磁盘 IO 累加导致整批超时。
# 潜在风险:100 个 Key 的磁盘随机读累加,耗时可达数秒MGET key1 key2 key3 ... key100
正确示例(分批发送):拆分为5批,每批20个,逐批发送。
# 第 1 批:20 个 Key,单批耗时可控MGET key1 key2 key3 ... key20# 第 2 批:20 个 KeyMGET key21 key22 key23 ... key40# ...依次类推,共 5 批完成
四、功能限制
以下限制源于 KeeWiDB 的磁盘存储引擎与集群路由架构,属于引擎层面的固有约束而非配置可调项。若在开发阶段未识别这些限制,将在运行时触发命令报错或数据不一致。本节逐项界定限制范围、根因说明及标准化替代路径,用于指导选型与方案设计阶段的兼容性评估。
1. 仅支持 DB 0
KeeWiDB 不支持多 DB 切换,
SELECT 命令将返回错误。隔离需求 | 限制说明 | 替代方案 |
业务级隔离 | 多 DB 切换在磁盘引擎下涉及文件句柄与索引切换,架构不予支持。 | 通过 Key 前缀命名区分不同业务(如 cx: 和 rx:)。 |
环境级隔离 | 同上。 | 部署独立实例(如生产实例和开发实例)。 |
2. 不支持事务
不支持事务相关命令:
MULTI、EXEC、WATCH、UNWATCH、DISCARD。原子性需求 | 限制说明 | 替代方案 |
单节点范围内的原子操作 | 集群架构下事务无法跨节点保证一致性,引擎层面不予支持。 | 使用 Lua 脚本实现(详见第五章),脚本内所有 Key 须路由到同一节点。 |
3. 禁止作为消息队列
KeeWiDB 的存储模型面向持久化键值访问,不具备消息队列所需的投递保障与消费确认机制。
机制 | 风险说明 | 规范建议 |
Pub/Sub | 消息不做持久化,客户端断连后消息丢失,无法满足可靠投递要求。 | 谨慎使用。 |
List 模拟队列 | 缺乏 ACK 确认机制,消费失败后消息不可回溯,且磁盘 IO 下吞吐量远低于专业消息中间件。 | 不建议在生产环境使用。 |
如有消息队列需求,请使用专业消息中间件(如 CKafka、TDMQ),获取可靠投递、消费确认和消息回溯能力。
五、Lua 脚本使用规范
Lua 脚本可在 KeeWiDB 服务端原子执行多条命令,适用于需要原子性保障的业务场景(如库存扣减、状态流转)。但在集群版架构下,请求由 Proxy 根据 Key 路由到不同后端节点,脚本的 Key 引用方式和路由逻辑需满足特定约束——违反这些约束将导致 Proxy 路由失败、跨节点执行异常或脚本加载性能劣化。本节定义三条核心规则,确保脚本在分布式集群环境下的正确性与执行效率。
规则1:Key 必须通过 KEYS 数组传递
在
redis.call / pcall 中引用的所有 Key,必须通过 KEYS 数组传递,禁止硬编码在脚本中。Proxy 依赖 KEYS 数组判断路由目标节点;硬编码 Key 将导致 Proxy 无法解析路由信息,请求直接失败。反面示例(Key 硬编码):Proxy 无法从脚本文本中提取路由信息,请求将返回路由错误。
# 潜在风险:Key 硬编码在脚本中,Proxy 无法识别路由目标EVAL "redis.call('SET', 'cx:orderdb:order:202401150001:hash', 'paid')" 0
正确示例(KEYS 数组传递):Proxy 从 KEYS 数组提取路由信息,正确分发到目标节点。
# Key 通过 KEYS[1] 传递,Proxy 可正确计算哈希槽并路由EVAL "redis.call('SET', KEYS[1], ARGV[1])" 1 cx:orderdb:order:202401150001:hash paid
规则2:单脚本所有 Key 必须位于同一节点
单个 Lua 脚本操作的所有 Key 必须路由到同一个节点。若 Key 分布在不同节点,脚本将执行失败。可通过 Hashtag(如
{order:202401150001})确保相关 Key 落在同一哈希槽。如下示例使用 Hashtag 将同一订单的多个属性 Key 绑定到同一哈希槽。# 两个 Key 共享 Hashtag {order:202401150001},确保路由到同一节点EVAL "redis.call('SET', KEYS[1], ARGV[1]); redis.call('SET', KEYS[2], ARGV[2])" 2 \\{order:202401150001}:status {order:202401150001}:amount paid 12580
规则3:使用 EVALSHA 替代 EVAL
首次通过
SCRIPT LOAD 加载脚本后,后续调用应使用 EVALSHA 传入脚本的 SHA1 摘要。每次使用 EVAL 都会传输完整脚本文本并触发重新编译,在高频调用场景下将产生显著的网络开销与 CPU 消耗。建议部署阶段使用 SCRIPT LOAD 将脚本文本上传至服务端并获取 SHA1 摘要;运行阶段使用 EVALSHA 仅传输20字节的摘要值即可调用已缓存的脚本,避免每次请求都传输完整脚本文本。# 部署阶段(执行一次):将脚本文本上传至服务端,服务端返回 SHA1 摘要SCRIPT LOAD "redis.call('SET', KEYS[1], ARGV[1])"# 返回:"a1b2c3d4e5f6..."# 运行阶段(反复调用):仅传输 SHA1 摘要,服务端直接执行已缓存的脚本EVALSHA a1b2c3d4e5f6... 1 cx:orderdb:order:202401150001:hash paid
六、Hashtag 使用规范
Hashtag 是集群版 KeeWiDB 提供的路由控制机制,通过在 Key 中使用花括号(如
{user}:name)指定哈希计算的子串,将多个 Key 强制分配到同一哈希槽。该机制在 Lua 脚本和多键命令中用于保证多 Key 路由到同一节点,但滥用将引发数据倾斜和请求倾斜,且倾斜一旦形成,无法通过集群扩容缓解。本节界定 Hashtag 的三大风险、使用原则及代码实现规范,用于指导架构设计阶段的路由策略评估。1. 三大风险
Hashtag 将多个 Key 绑定到同一哈希槽,可能引发以下风险:
风险类型 | 风险说明 | 规范建议 |
数据倾斜 | 大量 Key 共用同一 Hashtag 时,数据集中在同一节点,该节点内存使用率偏高,可能提前触发告警甚至写满。 | 禁止使用宽泛 Hashtag(如 {global}、{order}),按实体粒度拆分。 |
请求倾斜 | 与数据倾斜伴生——所有读写请求集中在同一节点,该节点延迟升高、CPU 使用率上升,拖累集群整体响应时间。 | 确保 Hashtag 值在整体上均匀分布。 |
不可扩容 | 哈希槽分配由 Hashtag 值固定决定,新增节点无法分散倾斜数据。区别于普通热点 Key 问题——扩容无法缓解。 | 设计阶段评估 Hashtag 基数,确保与节点数匹配。 |
2. 使用原则
原则 | 说明 | 规范建议 |
仅在必要时使用 | 仅在 Lua 脚本操作多 Key 或使用 MGET、SINTER 等多键命令时才引入 Hashtag。 | 不涉及跨 Key 操作的场景,保持自然哈希分布,禁止引入 Hashtag。 |
使用细粒度 Hashtag | 按具体实体拆分(如 {order:202401150001}),使不同实体的 Key 分散到不同节点。 | 禁止使用宽泛 Hashtag(如 {global}、{order})。 |
保证均匀分布 | 确保 Hashtag 值在整体上均匀分布。 | 若原始 ID 分布不均匀,可在 ID 后添加后缀(如 {user_id:1})进行人工分片。 |
3. 代码实现规范
反面示例(宽泛 Hashtag):全部订单共用
{order},百万订单 Key 集中在同一哈希槽,节点内存写满且扩容无法缓解。# 潜在风险:所有订单 Key 共用 {order},全部落在同一哈希槽{order}:202401150001:status{order}:202401150001:amount{order}:202401150002:status{order}:202401150002:amount# 百万订单集中在一个节点,扩容也无法分散
正确示例(细粒度 Hashtag):按订单号使用独立 Hashtag,仅同一订单的属性 Key 落在同一节点,不同订单自然分散。
# 订单 1 的属性 Key 共享 {order:202401150001},落在同一节点{order:202401150001}:status{order:202401150001}:amount# 订单 2 的属性 Key 共享 {order:202401150002},自然分散到其他节点{order:202401150002}:status{order:202401150002}:amount# 每个订单独立 Hashtag,百万订单均匀分布到不同节点