1、指令回顾
JSON.NUMMULTBY <key> <path> <factor>
- key:目标 Redis 键
- path:JSONPath,默认为
$
- factor:乘数(整数或浮点)
- 返回值:按路径顺序返回新数字;若匹配到非数字 →
null
典型示例
JSON.SET doc $ '{"b":[{"a":2},{"a":5},{"a":"c"}]}'
JSON.NUMMULTBY doc $..a 2
# => ["null", 4, 10, "null"]
2、为何被官方弃用?
原因 | 说明 |
---|---|
与 RFC 7396 不契合 | 乘法并非常见“增量补丁”语义,难以与 MERGE 等指令保持一致 |
场景稀缺 | 实际生产中,自乘需求远少于自增/自减 |
易引入浮点误差 | Redis 内部采用 double ,多次乘法累积误差难以避免 |
维护成本高 | 需要单独分支处理整型 / 浮点、溢出、NaN 等特殊情况 |
因此,在 RedisJSON 2.x 之后,官方鼓励开发者通过 Lua 脚本 或 先 NUMINCRBY
再 SET 的方式实现乘法逻辑。
3、迁移 & 替代方案
方案一:Lua 原子运算(推荐)
local v = redis.call('JSON.GET', KEYS[1], ARGV[1])
if not v then return nil end
local num = cjson.decode(v)[1] -- JSON.GET 单路径返回数组
if type(num) ~= 'number' then return nil end
local res = num * tonumber(ARGV[2])
redis.call('JSON.SET', KEYS[1], ARGV[1], res)
return res
调用:
EVALSHA <sha> 1 doc '$.b[0].a' 2
优点:
- 单条脚本 原子 完成读、算、写
- 可自行处理整型/浮点边界
方案二:客户端乐观锁
JSON.GET
读取旧值- 计算新值 = 旧值 × factor
WATCH key
+JSON.SET
提交- 若失败重试
缺点:存在 RTT 及重试开销,不适合高并发热点键。
方案三:离线批量乘法
对于大规模历史数据,可通过 SCAN → JSON.GET → 计算 → JSON.SET 异步跑批;期间使用双写或版本号字段保持一致性。
4、性能与精度注意事项
关注点 | 建议 |
---|---|
浮点误差 | 金融/积分场景使用 整型×固定倍数 → 再除回;或使用 DECIMAL 字符串 |
并发冲突 | 多客户端同时乘法 → 使用 Lua;避免读改写不一致 |
热点键放大 | 若操作同一数组大量元素,考虑拆分键或分片 |
溢出 | Lua 中自行检测 num * factor 是否超出 64 位整型/双精度范围 |
5、示例:用 Lua 实现 2× 积分翻倍活动
-- script: double_points.lua
local path = '$.stats.points'
local val = redis.call('JSON.GET', KEYS[1], path)
if not val then return nil end
local old = cjson.decode(val)[1]
if type(old) ~= 'number' then return nil end
local new = old * 2
redis.call('JSON.SET', KEYS[1], path, new)
return new
部署 & 调用(Python 片段):
import redis, json
r = redis.Redis()
with open('double_points.lua','r') as f:
sha = r.script_load(f.read())
new_score = r.evalsha(sha, 1, 'user:{42}',)
print("新积分:", new_score)
6、总结
JSON.NUMMULTBY
已被弃用,在 2.x 以后不应再依赖。- 官方推荐用 Lua 或 应用层逻辑 实现乘法原子更新。
- 迁移时重点关注 精度、并发写一致性 与 集群哈希槽。
- 对于频繁数值操作,仍首选
JSON.NUMINCRBY
;乘法仅在少数场景需要,自行封装即可。
一行结论:当你看到
NUMMULTBY
,请立刻想到——“用 Lua 取代它”,让你的代码在未来的 RedisJSON 版本里依然稳如磐石。🚀