【问题标题】:An alternative to redis transactions for atomicity in node节点中原子性的 redis 事务的替代方案
【发布时间】:2020-01-05 14:09:13
【问题描述】:

我有一个快速的网络服务器,它在 Redis 中执行一些操作,这些操作需要是原子的,它的值是一个列表。更具体地说,这或多或少是我的代码结构

redisclient.lrange(key, 0, -1 (error, items) => {
    .
    .
    .
    //some slightly complex code that updates the list of strings
    //obtain newItems from items
    .
    .
    .
    redisclient.del(key);
    //push the newly updated list
    for (let item of newItems){
        redisclient.rpush(key,item);
    }
});

这里的问题是,据我了解,为了使这些操作具有原子性,我需要使用 Lua 脚本。不过我对 Lua 一无所知,在 Lua 脚本中转换我的 JS 逻辑并非易事。

Node 是单线程的,但在这样的代码中是否有任何替代方法可以避免不同客户端之间的竞争条件?

【问题讨论】:

    标签: node.js redis atomic node-redis


    【解决方案1】:

    您可以使用事务来自动执行 DEL 和 RPUSH 命令。见CLIENT.MULTI([COMMANDS])

    如果您在处理列表时修改了列表,如果您希望事务不执行,您可以为您的密钥添加一个 WATCH。见OPTIMISTIC LOCKS。但在这里,您需要恢复/重试逻辑以防万一失败。

    要使用WATCH,您首先开始观看,然后使用LRANGE 阅读列表,进行操作,然后进行MULTIDELRPUSHEXEC。如果在WATCHEXEC 之间修改列表,EXEC 将失败。

    client.watch(key, function( err ){
        if(err) throw err;
    
        client.lrange(key, 0, -1, function(err, result) {
            if(err) throw err;
    
            // Process the result
    
            client.multi()
                .del(key)
                .rpush(key, newItems)
                .exec(function(err, results) {
    
                    /**
                     * If err is null, it means Redis successfully attempted 
                     * the operation.
                     */ 
                    if(err) throw err;
    
                    /**
                     * If results === null, it means that a concurrent client
                     * changed the key while we were processing it and thus 
                     * the execution of the MULTI command was not performed.
                     * 
                     * NOTICE: Failing an execution of MULTI is not considered
                     * an error. So you will have err === null and results === null
                     */
                });
        });
    });
    

    Redis 中的RPUSH 一次支持多个元素。考虑通过直接使用[send_command][3]ES6 spread syntax 或直接传递数组来一次推送多个元素(取决于您运行的版本)。

    也就是说,考虑使用 Lua 脚本。这里有点推动你开始:

    EVAL "local list1 = redis.call('LRANGE', KEYS[1], 0, -1) for ix,elm in ipairs(list1) do list1[ix] = string.gsub(elm, 'node', 'nodejs') end redis.call('DEL', KEYS[1]) redis.call('RPUSH', KEYS[1], unpack(list1)) return list1" 1 myList
    

    这里是 Lua 脚本的友好视图:

    local list1 = redis.call('LRANGE', KEYS[1], 0, -1)
    for ix,elm in ipairs(list1) do
        list1[ix] = string.gsub(elm, 'node', 'nodejs')
    end
    redis.call('DEL', KEYS[1])
    redis.call('RPUSH', KEYS[1], unpack(list1))
    return list1
    

    这只是在列表的所有元素中将node 替换为nodejs。在String Library Tutorial 上查看有关字符串操作的更多信息。

    【讨论】:

    • 所以澄清一下,我应该让 lrange 保持原样,然后为密钥设置 watch 并为 del 和 rpush 使用事务?
    • @Plutone11011,我刚刚扩展了乐观锁定的答案。您首先开始观看,否则列表可能会在LRANGEWATCH 之间变化。我还为 Lua 脚本添加了一点邀请
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-18
    • 2018-01-30
    相关资源
    最近更新 更多