Задайте кешированным ценам полный идентификатор и срок жизни

RedisBeginner
Практиковаться сейчас

Введение

Небольшой магазин хранит достоверные цены в подготовленной основной базе данных и использует Redis, чтобы запоминать цены, уже просмотренные покупателями. Повторные просмотры должны использовать это сохранённое значение. Но один SKU может иметь разные цены в разных магазинах или валютах, а срок хранения запомненной цены должен когда-нибудь истекать.

Вы подготовите короткий policy.py, который выбирает полный идентификатор кеша и задаёт конечный срок жизни каждой записи при заполнении. Нужны базовые навыки навигации в терминале и знание Redis GET/SET. Шаблон Python и каждое изменяемое выражение объясняются здесь; предыдущий курс Python не требуется. Основной источник — подготовленная база данных SQLite, а не задача по реализации второй базы данных. Это независимое занятие использует только Terminal и собственные новые данные.

Редактируйте только /home/labex/project/cache-desk/policy.py. Сохраните предоставленные инструменты, исходную таблицу, все исходные идентификаторы и цены, а также постороннюю заметку каталога. Используйте в трёх функциях только описанный API источника и кеша и обычные выражения политики; не добавляйте в политику импорты, выполнение команд оболочки и посторонние ресурсы. Не отключайте кеширование, не очищайте Redis целиком и не меняйте глобальную конфигурацию. Мы проверяем контролируемые последовательные запросы без гарантий задержки в рабочей системе или строгой согласованности. Оценка времени автором включает чтение и обычные ошибки; время прохождения новичками не измерялось.

Разделите цены, которые действительно запрашивают покупатели

На этом шаге вы увидите настоящее попадание в кеш, найдёте коллизию ключей и включите полный идентификатор каждой цены в её ключ.

Начните во вкладке Terminal, в приглашении оболочки. cd меняет текущий каталог; этот абсолютный путь работает из любого каталога. ./desk запускает подготовленную программу из этого каталога. Каждая команда ниже — отдельная команда оболочки, а не ввод для интерактивного приглашения Redis.

cd /home/labex/project/cache-desk
./desk status

Прочитайте порядок столбцов: shop / sku / currency / integer cents. North MUG USD стоит 1200 центов, South MUG USD — 1500, North MUG EUR — 1100, а North FREE USD — 0. Вывод состояния читает достоверные цены и никогда не заполняет кеш.

Промах кеша означает, что в Redis нет значения для запрошенного ключа. Попадание в кеш означает, что оно есть. Это подготовленное приложение использует cache-aside: сначала читает Redis, только при промахе читает основную базу, затем запоминает результат. Дважды выполните один обычный запрос:

./desk view north MUG USD
./desk view north MUG USD

Первый результат должен показывать "cents": 1200, "origin": "source", "primary_selects": 1; второй — "origin": "cache", "primary_selects": 0. Счётчик основан на штатной трассировке SQLite SELECT для этого запроса, а не на оценке ускорения. Вывод JSON использует имена полей в кавычках; эти три поля обозначают цену, происхождение и чтения основной базы.

Теперь запросите другой магазин и изучите настоящий ключ Redis:

./desk view south MUG USD
./desk inspect south MUG USD
./desk policy

Неполный ключ использует только MUG. Он может вернуть 1200 от North для South, хотя основная база всё ещё хранит 1500. inspect читает ключ и его фактическое значение, а не исправляет цену. Предоставленный префикс catalog ограничивает область ключей этого курса. Каждый ключ должен начинаться с предоставленного префикса, за которым следует двоеточие, например catalog:. Суффикс должен различать все три поля идентификатора, а не только SKU; эквивалентные кодировки суффикса принимаются.

Шаблон написан на Python. def определяет функцию; имена в скобках — её аргументы. Тело с отступом относится к этой функции. return возвращает её результат. identity — кортеж из трёх элементов в показанном порядке полей: identity[0] — магазин, [1] — SKU, [2] — валюта. Нумерация индексов начинается с нуля. f"...{name}..." вставляет выражение в строку; двоеточия здесь — обычные разделители. Идентификаторы курса содержат только буквы, цифры, дефисы и подчёркивания, поэтому не могут содержать этот разделитель. Такой формат ключа не является универсальным кодированием произвольного пользовательского текста.

В read_price знак = присваивает значение. cache.command("GET", key) отправляет настоящую команду Redis. None означает отсутствие значения в кеше; if value is not None: выбирает ветку только при наличии значения. Эта явная проверка сохраняет нулевую цену. Redis возвращает строковое значение, поэтому int(value) преобразует его в целое число центов. Запятая в return cents, "source" возвращает пару. source.read(identity) выполняет подготовленный SELECT основной базы и вызывает ошибку поиска для неизвестного идентификатора. Запасной эталонной цены нет.

Сохраните полный файл с полным ключом. cat > policy.py заменяет этот файл; маркер here-document 'POLICY' в кавычках заставляет оболочку буквально копировать каждую следующую строку до отдельной строки POLICY. Скопируйте весь блок, сохраняя отступ в четыре пробела. Повторённые функции сохраняют подготовленное поведение чтения; на этом шаге меняется только выбор ключа.

cat > policy.py <<'POLICY'
def cache_key(prefix, identity):
    return f"{prefix}:{identity[0]}:{identity[1]}:{identity[2]}"


def read_price(source, cache, prefix, identity):
    key = cache_key(prefix, identity)
    value = cache.command("GET", key)
    if value is not None:
        return int(value), "cache"
    cents = source.read(identity)
    cache.command("SET", key, cents)
    return cents, "source"


def change_price(source, cache, prefix, identity, cents):
    source.write(identity, cents)
POLICY

Просмотрите каждый идентификатор дважды:

./desk view north MUG USD
./desk view north MUG USD
./desk view south MUG USD
./desk view south MUG USD
./desk view north MUG EUR
./desk view north MUG EUR
./desk view north FREE USD
./desk view north FREE USD

Каждое первое чтение нового полного ключа приходит из источника, второе — из Redis. Цены соответственно 1200, 1500, 1100 и 0. Попадание в кеш с неправильной ценой — не успех. Старый одноразовый ключ catalog:MUG больше не используется; массовое удаление не нужно.

Если вы введёте ./deskk, оболочка сообщит об отсутствии файла. ls перечисляет настоящие инструменты; снова выполните ./desk с правильным написанием. Если Python сообщает об ошибке синтаксиса или отступов, выведите ./desk policy, затем снова скопируйте полный блок файла, сохранив пробелы в начале строк. Не сбрасывайте VM и не меняйте подготовленные инструменты. Проверка шага использует изменённые идентификаторы в одноразовом тестовом окружении и сохраняет живую основную базу; конкретное написание ключа не требуется.

Задайте срок жизни при каждом заполнении

На этом шаге вы добавите конечный срок жизни при попадании цены в Redis и не позволите попаданиям в кеш сдвигать этот срок.

Продолжайте во вкладке Terminal, в /home/labex/project/cache-desk. Изучите существующий полный ключ:

./desk inspect north MUG USD

pttl_ms — оставшийся срок жизни в миллисекундах. -1 означает, что срок не задан; -2 — что ключ отсутствует. Положительное значение уменьшается. Значение без срока истечения может пережить внешнее изменение цены и сохраняться неограниченно долго.

Команда Redis SET key value EX seconds сохраняет значение и срок истечения одной командой. Выберите 15 секунд для этого учебного магазина; при каждом заполнении допустим начальный срок жизни от 12 до 20 секунд. Короткий интервал делает естественное истечение наблюдаемым; это не рекомендация TTL для рабочей системы. Задавайте срок только при промахе. Продление при каждом попадании может сохранять популярную устаревшую цену бесконечно. Обычный SET, перезаписывающий ключ с истечением, снимает прежний срок, если его сохранение не было явно запрошено.

Замените файл, сохранив полный идентификатор и добавив SET со сроком. В Python отдельные аргументы "EX", 15 передаются Redis; 15 — целое число, а не идентификатор в кавычках.

cat > policy.py <<'POLICY'
def cache_key(prefix, identity):
    return f"{prefix}:{identity[0]}:{identity[1]}:{identity[2]}"


def read_price(source, cache, prefix, identity):
    key = cache_key(prefix, identity)
    value = cache.command("GET", key)
    if value is not None:
        return int(value), "cache"
    cents = source.read(identity)
    cache.command("SET", key, cents, "EX", 15)
    return cents, "source"


def change_price(source, cache, prefix, identity, cents):
    source.write(identity, cents)
POLICY

Значения с полными идентификаторами, записанные на предыдущем шаге, по-прежнему не имеют срока истечения: изменение кода не добавляет его задним числом. forget намеренно удаляет только текущий ключ кеша одного известного идентификатора, оставляя его основную цену и другие ключи нетронутыми. Удалите эти четыре явно одноразовые учебные записи по отдельности. Это явное действие учащегося, а не массовая очистка или скрытое исправление средством чтения. Все последующие заполнения будут использовать новый срок. Затем заполните цену North и изучите её:

./desk forget north MUG USD
./desk forget south MUG USD
./desk forget north MUG EUR
./desk forget north FREE USD
./desk view north MUG USD
./desk inspect north MUG USD
./desk view north MUG USD
./desk inspect north MUG USD

Первый просмотр — source/1, следующий — cache/0, если выполнен до истечения срока. Положительный PTTL уменьшается, а не начинается заново с 15000. Если чтение занимает больше 15 секунд, настоящий промах ожидаем: повторите команды заполнения и осмотра без задержки, не меняя политику.

Вы также можете читать штатные значения Redis через предоставленное соединение только для чтения. ./redis отправляет аргументы в отдельный Redis этого курса, а не в общую службу Redis:

./redis GET catalog:north:MUG:USD
./redis TTL catalog:north:MUG:USD

Обёртка печатает строку JSON, например "1200", штатный целочисленный TTL или null для отсутствующего значения. Она никогда не загружает отсутствующую цену. Теперь sleep 16 просит оболочку ждать 16 настоящих секунд. Это не имитация времени и не удаление ключа за вас:

sleep 16
./desk inspect north MUG USD
./desk view north MUG USD
./desk view north MUG USD

Перед повторным заполнением inspect показывает value: null и PTTL -2. Следующий просмотр снова читает источник, а следующий может попасть в Redis. В этом занятии все достоверные цены остаются неизменными. Проверка испытывает настоящее истечение на изолированных ключах; живые ключи могут естественно истечь, пока вы читаете, поэтому завершение не зависит от гонки с коротким обратным отсчётом.

Резюме

Вы разделили магазин, SKU и валюту в настоящих ключах Redis, сохранили нулевые цены, проверили попадания в кеш по чтениям основной базы и задали каждой записи при заполнении срок жизни без продления при чтении. Ваша политика пригодна для повторного использования; подготовленная основная база и посторонние данные каталога сохранены.