Redis: сумма SCORES в отсортированном наборе

Как лучше всего получить сумму SCORES в отсортированном наборе Redis?


person ma11hew28    schedule 30.01.2011    source источник


Ответы (4)


Единственный вариант, который я думаю, - это повторение отсортированного набора и вычисление суммы на стороне клиента.

person antirez    schedule 31.01.2011
comment
Спасибо! Да, я думаю, это, вероятно, слишком специфичная функция для Redis, чтобы заморачиваться реализацией... особенно. так как я, возможно, даже не делаю что-то целесообразное. Я использую zset для хранения идентификаторов полученных пользователем подарков и идентификаторов отправляющих пользователей, а SCORE — это количество раз, когда один и тот же подарок был получен от одного и того же отправителя. Например, ZINCR 123:gifts 1 "3|345" отправляет подарок с идентификатором 3 от пользователя 345 пользователю 123. Итак, я хочу получить общее количество подарков, полученных пользователем. Видеть? Возможно, есть лучший способ реализовать это? Это похоже на Facebook Gift API. Редис рулит! :) Спасибо за все! - person ma11hew28; 02.02.2011

Доступная начиная с Redis v2.6, это самая замечательная возможность выполнять сценарии Lua на сервере Redis. Это делает задачу суммирования оценок отсортированного набора тривиальной:

local sum=0
local z=redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES')

for i=2, #z, 2 do 
    sum=sum+z[i]
end

return sum

Пример времени выполнения:

~$ redis-cli zadd z 1 a 2 b 3 c 4 d 5 e
(integer) 5
~$ redis-cli eval "local sum=0 local z=redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES') for i=2, #z, 2 do sum=sum+z[i] end return sum" 1 z
(integer) 15
person Itamar Haber    schedule 23.05.2015
comment
Важным примечанием является то, что lua-скрипты Redis на стороне сервера блокируют ВСЕ, что в большинстве случаев может нарушить сделку. Источник: stackoverflow.com/a/30896608/2440 - person Sire; 20.12.2016

Если наборы небольшие, и вам не нужна убийственная производительность, я бы просто перебрал (zrange/zrangebyscore) и суммировал значения на стороне клиента.

Если, с другой стороны, вы говорите о многих тысячах или миллионах элементов, вы всегда можете сохранить набор ссылок с промежуточными суммами для каждого пользователя и увеличивать/уменьшать их по мере отправки подарков.

Итак, когда вы выполняете ZINCR 123:gifts 1 "3|345", вы можете выполнить отдельную команду ZINCR, которая может выглядеть примерно так:

ZINCR received-gifts 1 <user_id>

Затем, чтобы получить количество подарков, полученных для данного пользователя, вам просто нужно запустить ZSCORE:

ZSCORE received-gifts <user_id>
person mkgrunder    schedule 19.02.2011
comment
Инкрементальный подсчет намного лучше решает онлайн-задачи. Сканирование следует использовать только в автономных алгоритмах, ИМХО, поскольку никто не может предсказать будущий рост набора данных. Этот ответ должен быть принятым. Да, и кстати: можно написать пользовательскую команду, которая делает и то, и другое (добавление к набору и увеличение счетчика, обновление значения и настройка счетчика, удаление из набора и...) - person Manuel Arwed Schmidt; 23.05.2015
comment
Очевидно, что этот ответ довольно старый, но зачем вообще использовать ZSET для отдельной команды? Из вашего ответа мне непонятно, насколько я могу судить, будет работать так же хорошо (и использовать меньше памяти), чтобы просто использовать INCR/GET. - person Madbreaks; 05.12.2018

Вот небольшой lua-скрипт, который поддерживает общее количество очков zset по ходу работы, в счетчике с ключевым постфиксом '.ss'. Вы можете использовать его вместо ZADD.

local delta = 0
for i=1,#ARGV,2 do
    local oldScore = redis.call('zscore', KEYS[1], ARGV[i+1])
    if oldScore == false then
        oldScore = 0
    end
    delta = delta - oldScore + ARGV[i]
end
local val = redis.call('zadd', KEYS[1], unpack(ARGV))
redis.call('INCRBY', KEYS[1]..'.ss', delta)
person Patrick    schedule 14.03.2016
comment
Этот сценарий не должен создавать ключ в lua (KEYS[1]..'.ss'), что нарушает семантику команды EVAL, поскольку все ключи, используемые сценарием, должны передаваться с использованием массива KEYS -- redis.io/commands/eval - person Jonathan; 09.02.2018
comment
@ Джонатан, ты прав, ключ для общего балла также должен быть передан в KEYS [] - person Patrick; 10.02.2018