Месяц назад-
Весь купонный центр разделен на внешний и внутренний. Сяохуэй отвечает за разработку внутреннего RPC-интерфейса. Интерфейс включает в себя два метода "проверить купоны" и "получить купоны".Общая структура проекта выглядит следующим образом:
Две недели спустя -
Сяо Хуэй: Смотри, это эффект функции запроса купона!
Сяо Хуэй: Смотри, это эффект от функции сбора купонов!
Три дня спустя-
Оригинальный интерфейс запроса купона Xiaohui реализован следующим образом:
Список купонов хранится в виде List в Redis, и логика при запросе очень проста:
1. Запросить кеш, если кеш есть, вернуть результат
2. Кэш не существует, запросите базу данных
3. Зациклить результаты запроса базы данных в кеше
Однако, когда кеш не существует в определенный момент времени и объем запроса велик, он появитсяпараллелизм кешаПроблема. То есть несколько потоков будут неоднократно запрашивать БД и неоднократно обновлять кеш. (Обратите внимание, что это неразбивка кеша, многие путают эти два понятия. )
Среди них повторные запросы к БД — второстепенная проблема, а постоянное обновление кеша — главная проблема. Если два потока одновременно входят в указанный выше третий этап и выполняют операцию rpush соответственно, то в конечном итоге в кэш списка купонов будут вставлены два набора одинаковых данных.
Как это решить? Используя механизм блокировки Java? Очевидно, что нет, потому что онлайн-среды обычно представляют собой кластеры из нескольких серверов. Итак, Сяо Хуэй подумал об использованииРаспределенная блокировка.
Существует множество так называемых распределенных блокировок, которые можно реализовать с помощью ZooKeeper, MemCache и Redis. Среди них относительно прост метод Redis, который представляет собой не что иное, как использование общего ключа между серверами и командой Setnx.
Когда первый поток выполняет Setnx, соответствующее значение ключа сохраняется, что эквивалентно успешному получению блокировки. Когда позже другой поток выполнит инструкцию Setnx для того же ключа, он вернет null, что эквивалентно невозможности захвата блокировки. В то же время, чтобы поток не удерживал блокировку в течение длительного времени из-за непредвиденных ситуаций, программа устанавливает время истечения 1 секунду для ключа.
Подытожим измененную логику:
1. Запросить кеш, если кеш есть, вернуть результат
2. Кэш не существует, запросите базу данных
3. Конкурировать за распределенные блокировки
4. Блокировка успешно получена, и результат запроса к БД помещается в кеш в цикле
5. Снимите распределенные блокировки
Три дня спустя-
Странная ошибка снова появилась, потому что в последнем изменении Xiaohui все еще есть фатальная лазейка. Здесь мы предполагаем, что кеша не существует, просто два потока A и B входят в блок кода один за другим.
На первом этапе поток A только начал запрашивать кеш купонов, а поток B пытается получить распределенную блокировку:
На втором этапе, поскольку кеша не существует, поток A начинает запрашивать базу данных, поток B успешно получает блокировку и начинает обновлять кеш:
На третьем этапе поток A пытается получить распределенную блокировку, а поток B снял распределенную блокировку:
На четвертом этапе поток A получает блокировку,сноваОбновите кеш, и поток B успешно вернулся:
Таким образом, кэш обновляется дважды, поэтому ошибка дублирования данных возникает снова.
Как решить эту ситуацию? На самом деле это несложно, просто после того, как поток успешно получит блокировку, еще раз оцените наличие кеша купонов:
Подытожим измененную логику:
1. Запросить кеш, если кеш есть, вернуть результат
2. Кэш не существует, запросите базу данных
3. Конкурировать за распределенные блокировки
4. Успешно получите блокировку и снова оцените наличие кеша.
5. Если кеш по-прежнему не существует, зациклить результаты запроса базы данных в кеш.
6. Снимите распределенную блокировку
Этот вторичный механизм для определения существования специального имени под названиемДвойное обнаружение. Этот метод также часто используется в потокобезопасном одноэлементном шаблоне.
Воспоминания Сяо Хуэя подошли к концу——
Несколько дополнений:
1. Распределенная блокировка, используемая в этой статье, на самом деле не является «аутентичной» распределенной блокировкой.Когда поток не может конкурировать за блокировку, он будет напрямую возвращать результат запроса к БД, а не полагаться на него.вращениемеханизм ожидания блокировки.
2. Почему информация о списке купонов должна храниться в кеше с использованием типа List, а не хранить весь список в виде длинной строки Json? Это связано с потребностями бизнеса.В некоторых случаях удобнее использовать List для изменения информации об одном купоне (инструкция LSET).
3. Почему информация о списке купонов не сохраняется в типе данных Redis Set или Hash для автоматической дедупликации? Для типа Set необходимо сравнить, идентична ли вся строка перед дедупликацией, а каждый купон представляет собой длинную строку Json, и эффективность сравнения будет относительно низкой. Использование Hash позволяет добиться эффективной дедупликации, но принципиально не решает проблему повторных обновлений.
----КОНЕЦ----
Друзья, которым понравилась эта статья, можете долго нажимать на картинку, чтобы следить за номером подписки.программист маленький серый, смотрите больше захватывающего контента