Списки: очереди и стеки
Список Redis — это двусторонняя очередь. Из неё легко сделать очередь задач, стек или ленту последних событий.
Список Redis устроен как дек: добавлять и забирать элементы с обоих концов за O(1). Этого достаточно, чтобы построить очередь задач промышленного уровня.
Список (List) хранит упорядоченную последовательность строк. Главная особенность — быстрые операции с обоих концов. Это делает список идеальным для очередей, стеков и лент новостей.
Основные команды
127.0.0.1:6379> RPUSH tasks "task1"
(integer) 1
127.0.0.1:6379> RPUSH tasks "task2" "task3"
(integer) 3
127.0.0.1:6379> LRANGE tasks 0 -1
1) "task1"
2) "task2"
3) "task3"
127.0.0.1:6379> LPOP tasks
"task1"
127.0.0.1:6379> LLEN tasks
(integer) 2
RPUSH — добавить справа (в хвост), LPUSH — слева (в голову). LPOP/RPOP — забрать с соответствующего конца. LRANGE 0 -1 — показать весь список. LLEN — длина.
Очередь vs стек
Очередь (FIFO): RPUSH добавляет, LPOP забирает вход -> [a][b][c] -> выход RPUSH d: [a][b][c][d] LPOP: забрали a (первый вошёл - первый вышел) Стек (LIFO): RPUSH добавляет, RPOP забирает RPUSH d: [a][b][c][d] RPOP: забрали d (последний вошёл - первый вышел)
Блокирующие операции
BLPOP/BRPOP — блокирующие версии. Если список пуст, клиент ждёт, пока кто-то не добавит элемент (или не истечёт таймаут). Это превращает список в простую очередь задач: воркеры висят на BLPOP и просыпаются, когда появляется работа.
BRPOP tasks 5 # ждать до 5 секунд появления элемента
Демонстрация: очередь задач на Python deque
Список Redis по поведению — это collections.deque. Смоделируем очередь задач с воркером:
from collections import deque
# Очередь задач — аналог списка Redis
queue = deque()
# Продюсеры добавляют задачи (как RPUSH)
for i in range(1, 6):
queue.append(f"job-{i}")
print(f"RPUSH job-{i} -> длина очереди: {len(queue)}")
print("\n--- Воркер разбирает очередь (как LPOP) ---")
# Воркер забирает задачи слева в порядке FIFO
while queue:
task = queue.popleft() # аналог LPOP
print(f"LPOP -> обрабатываю {task}, осталось: {len(queue)}")
print("\nОчередь пуста. В Redis воркер ждал бы на BLPOP.")
print("FIFO: задачи обработаны в том же порядке, что добавлены.")
Тот же принцип FIFO лежит в основе очередей задач на Redis: один процесс кладёт работу через RPUSH, другой забирает через LPOP или BLPOP.
Как работает под капотом
Современный Redis хранит списки как quicklist — двусвязный список, где каждый узел представляет собой компактный listpack (раньше — ziplist). Маленькие списки целиком умещаются в один listpack, экономя память. По мере роста добавляются узлы. Благодаря двусвязной структуре операции с обоих концов — O(1), а доступ к середине через LINDEX — O(n).
Частые ошибки
- Использовать список как массив с произвольным доступом.
LINDEXпо середине — O(n), это медленно для длинных списков. - Не ограничивать рост списка. Лента «последних событий» без
LTRIMразрастётся бесконечно. - Поллинг вместо BLPOP. Крутить цикл с проверкой LLEN — расточительно; блокирующие операции эффективнее.
Best practices
- Для очередей задач используйте
RPUSH+BLPOP. - Ограничивайте длину лент через
LTRIM list 0 99(оставить последние 100). - Для надёжных очередей с подтверждениями рассмотрите Redis Streams (раздел про очереди).
Итог: Список — двусторонняя очередь с O(1) на концах. RPUSH/LPOP дают FIFO-очередь, RPUSH/RPOP — стек. BLPOP делает из списка очередь задач. Внутри — эффективный quicklist из listpack-узлов.
Управление длиной и доступ к элементам
На практике списки почти всегда нужно ограничивать по длине, иначе лента событий или лог разрастаются бесконечно. Для этого есть LTRIM, обрезающий список до диапазона:
LPUSH feed:user42 "новое событие"
LTRIM feed:user42 0 99 # оставить только 100 последних
LRANGE feed:user42 0 9 # показать 10 самых свежих
LINDEX feed:user42 0 # элемент по индексу (O(n) для середины!)
LSET feed:user42 0 "правка" # заменить элемент по индексу
Связка LPUSH + LTRIM — это классический рецепт «ленты последних N событий»: добавляем в голову и тут же отсекаем хвост. Память под контролем, а свежие элементы всегда сверху.
Важно держать в голове сложность операций. Добавление и извлечение с концов — O(1). А вот LINDEX по середине, LINSERT и LREM — O(n), потому что списку приходится идти по цепочке узлов. Если вам постоянно нужен произвольный доступ по индексу — список не та структура; присмотритесь к sorted set или хешу. Список силён именно как очередь и стек, где работа идёт с концами.