V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yanshenxian
V2EX  ›  程序员

奇怪 这段 redis lua 脚本为什么没有返回值 😭

  •  
  •   yanshenxian · 2020-06-28 22:23:23 +08:00 · 2367 次点击
    这是一个创建于 1614 天前的主题,其中的信息可能已经有所发展或是发生改变。

    script/decr.lua

    -- 如果不存在, 则设置值和过期时间 返回
    -- 如果已经存在, 直接自减, 过期时间保持不变
    local value = redis.call('EXISTS', KEYS[1])
    if value == 0 then
        redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2])
        return ARGV[1]
    else
        return redis.call('decr', KEYS[1])
    end
    

    java 代码如下

    decrLuaScript = new DefaultRedisScript<>();
    decrLuaScript.setResultType(Long.class);
    decrLuaScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/decr.lua")));
    
    Long result = atomicRedisTemplate.execute(decrLuaScript, ImmutableList.of(key), initValue, seconds);
    
    assert Objects.nonNull(result);
    

    测试发现如果是没有初始值, 结果返回的 null, 如果已有初始值可以正常返回 decr 后的结果

    更奇怪的是.. 在 redis-cli 里面手动执行是可以返回初始值的

    127.0.0.1:6379> eval "local value = redis.call('EXISTS', KEYS[1]) if value == 0 then redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) return ARGV[1] else return redis.call('decr', KEYS[1]) end" 1 xxx 100 300
    
    "100"
    
    第 1 条附言  ·  2020-06-29 01:08:21 +08:00
    补充 Redis server 版本
    v=6.0.4 sha=00000000:0 malloc=libc bits=64 build=8e6667c19bc7b263
    7 条回复    2020-06-29 19:00:08 +08:00
    echohw
        1
    echohw  
       2020-06-28 23:10:05 +08:00
    你试试用 Integer 类型接收,我又一次也这样,改成 Integer 类型就好了,不知道为什么┐( ̄ヮ ̄)┌
    yanshenxian
        2
    yanshenxian  
    OP
       2020-06-28 23:16:21 +08:00
    @echohw 我刚把返回改成 `return tonumber(ARGV[1])` 可以正常返回了

    但是这个 tonumber 只能处理 int 范围的, long 会溢出为负数 不知道咋整
    echohw
        3
    echohw  
       2020-06-29 00:17:19 +08:00
    刚 debug 了一下,发现 Lua 这边对类型比较严格,如果返回的是 string 类型,而使用 Long 接收的话,那么就为 null,而且我这边也没有出现你说的溢出的问题
    yanshenxian
        4
    yanshenxian  
    OP
       2020-06-29 00:55:17 +08:00
    @echohw 想问下怎么 debug 的 (哪个地方),我刚换成 tonumber 用 Long.MAX_VALUE 去测试溢出了

    > eval "local value = redis.call('EXISTS', KEYS[1]) if value == 0 then redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) return ARGV[1] else return redis.call('decr', KEYS[1]) end" 1 xx 9223372036854775707 3600
    "9223372036854775707"

    这个直接 return 执行是正常的,但是 redis 返回的好像确实是个 string

    > eval "local value = redis.call('EXISTS', KEYS[1]) if value == 0 then redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) return tonumber(ARGV[1]) else return redis.call('decr', KEYS[1]) end" 1 xx 9223372036854775707 3600
    (integer) -9223372036854775808

    这个 tonumber 返回 直接溢出
    yanshenxian
        5
    yanshenxian  
    OP
       2020-06-29 01:08:06 +08:00
    补充 Redis server 版本 v=6.0.4 sha=00000000:0 malloc=libc bits=64 build=8e6667c19bc7b263
    echohw
        6
    echohw  
       2020-06-29 12:31:54 +08:00
    @yanshenxian 这个是 Lua 大数运算的锅,你可以在 Lua 中返回 string 类型,然后通过序列化器来进行反序列化

    ![image.png]( https://i.loli.net/2020/06/29/tYLWuNVaTym5i1p.png)
    yanshenxian
        7
    yanshenxian  
    OP
       2020-06-29 19:00:08 +08:00
    @echohw 好像无解, tonumber 最大支持 52 bit 的 integer, 也就是 9007199254740991, 如果是用 tostring 也只能拿到一个科学技术法表示的字符串,string.format("%18.0f", xxx) 同样无法处理超出范围的大数
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1299 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 18:00 · PVG 02:00 · LAX 10:00 · JFK 13:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.