Кэширование памяти, блокировка и условия гонки
-
05-07-2019 - |
Вопрос
Мы пытаемся обновить объекты memcached при записи в базу данных, чтобы избежать необходимости считывать их из базы данных после вставок / обновлений.
Для нашего объекта записи на форуме у нас есть поле viewCount, содержащее количество просмотров записи.
Мы опасаемся, что вводим условие гонки, обновляя объект memcached, поскольку одно и то же сообщение может быть просмотрено в одно и то же время на другом сервере фермы.
Есть какие-нибудь идеи, как справиться с такого рода проблемами - казалось бы, необходима какая-то блокировка, но как сделать это надежно на всех серверах фермы?
Решение
Если вы имеете дело с данными, это не обязательно потребность чтобы обновляться в режиме реального времени, и для меня количество просмотров является одним из них, вы могли бы добавить поле expires к объектам, которые хранятся в memcache.
Как только истечет срок действия, он вернется в базу данных и прочитает новое значение, но до тех пор оставит его в покое.
Конечно, для новых сообщений вы можете захотеть, чтобы это обновлялось чаще, но вы можете написать код для этого.
Memcache хранит только одну копию вашего объекта в одном из его экземпляров, а не во многих из них, поэтому я бы не стал беспокоиться о блокировке объекта или о чем-то еще.Это предназначено для обработки базы данных, а не вашего кэша.
Редактировать:
Memcache не дает гарантии, что при получении и настройке с разных серверов ваши данные не будут сбиты с толку.
Из документов memcache:
- Последовательность команд не является атомарной.Если вы выдаете "get" для элемента, работаете с данными, а затем хотите "установить" их обратно в memcached, вы не гарантированно будете единственным процессом, работающим с этим значением.Параллельно вы могли бы в конечном итоге перезаписать значение, установленное чем-то другим.
Условия гонки и устаревшие данные
Одна вещь, которую следует иметь в виду при разработке приложения для кэширования данных, - это то, как справляться с условиями гонки и случайными устаревшими данными.
Допустим, вы кэшируете последние пять комментариев для отображения на боковой панели вашего приложения.Вы решаете, что данные необходимо обновлять только один раз в минуту.Однако вы забываете помнить, что отображение боковой панели отображается 50 раз в секунду!Таким образом, как только проходит 60 секунд и срок действия кэша истекает, внезапно более 10 процессов запускают один и тот же SQL-запрос для повторного заполнения этого кэша.Каждый раз, когда срок действия кэша истекает, возникает внезапный всплеск SQL-трафика.
Хуже того, у вас есть несколько процессов, обновляющих одни и те же данные, и неправильный из них в конечном итоге датирует кэш.Тогда у вас будут всплывать устаревшие данные.
Следует помнить о возможных проблемах при заполнении или повторном заполнении нашего кэша.Помните, что процесс проверки memcached, извлечения SQL и сохранения в memcached вообще не является атомарным!
Другие советы
Я думаю - может ли решение заключаться в том, чтобы хранить viewcount отдельно от объекта Post, а затем делать для него INCR. Конечно, это потребует чтения 2 отдельных значений из memcached при отображении информации.
Мы столкнулись с этим в нашей системе.Мы могли бы получить так
- Если значение не задано, оно устанавливается с помощью флага ('g') и [8] второго TTL и возвращает false, чтобы вызывающая функция сгенерировала его.
- Если значение не помечено (!== 'g'), то отмените сериализацию и верните его.
- Если значение помечено (==='g'), подождите 1 секунду и повторите попытку, пока оно не будет помечено.В конечном итоге он будет установлен другим процессом или истечет по TTL.
Когда мы это внедрили, нагрузка на нашу базу данных снизилась в 100 раз.
function get($key) {
$value=$m->get($key);
if ($value===false) $m->set($key, 'g', $ttl=8);
else while ($value==='g') {
sleep(1);
$value=$m->get($key);
}
return $value;
}
Операции memcached являются атомарными. серверный процесс будет ставить запросы в очередь и полностью обслуживать каждый из них, прежде чем перейти к следующему, поэтому блокировка не требуется.
edit: memcached имеет команду увеличения, которая является атомарной. Вы просто должны хранить счетчик как отдельное значение в кэше.