Альтернатива повторным транзакциям для атомарности в узле - PullRequest
1 голос
/ 05 января 2020

У меня есть веб-сервер express, который выполняет в Redis некоторые операции, которые должны быть атомами c, над ключом, значением которого является список. Более конкретно, это более или менее структура моего кода

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);
    }
});

Проблема здесь в том, что, насколько я понимаю, для выполнения этих операций атомы c мне нужно будет использовать Lua скрипт. Я ничего не знаю о Lua, и преобразование моего JS logi c в сценарий Lua не будет тривиальным.

Узел однопоточный, но не Есть ли альтернатива, чтобы избежать гонки между разными клиентами в коде, подобном этому?

1 Ответ

1 голос
/ 05 января 2020

Вы можете использовать транзакцию, чтобы команды DEL и RPU SH выполнялись атомарно. См. CLIENT.MULTI ([COMMANDS]) .

. Вы можете добавить WATCH для своего ключа, если хотите, чтобы транзакция не выполнялась, если ваш список был изменен во время обработки. См. ОПТИМИСТИ C ЗАМКИ . Но в этом случае вам потребуется логика восстановления / повторных попыток c в случае сбоя.

Чтобы использовать WATCH, вы сначала начинаете просмотр, затем читаете список с помощью LRANGE, выполняете манипуляции, затем выполняете MULTI, DEL, RPUSH, EXEC. EXE C завершится ошибкой, если список был изменен между WATCH и 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
                 */
            });
    });
});

RPUSH в Redis поддерживает несколько элементов одновременно. Рассмотрите возможность одновременной загрузки нескольких элементов, используя [send_command][3], прямой синтаксис ES6 или прямую передачу массива (зависит от используемой версии).

Тем не менее, рассмотрите возможность использования Lua скрипты. Вот немного pu sh, чтобы вы начали:

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 во всех элементах списка. Подробнее о работе со строками читайте в Обучающем руководстве по библиотеке строк .

...