Подтвердить что ты не робот

Memcached, блокировка и условия гонки

Мы пытаемся обновить memcached объекты, когда мы пишем в базу данных, чтобы избежать необходимости читать их из базы данных после вставки/обновления.

Для нашего сайта post post мы имеем поле ViewCount, содержащее количество просмотров сообщения.

Мы боимся, что мы вводим условие гонки, обновляя memcached-объект, поскольку одно и то же сообщение можно просматривать одновременно на другом сервере фермы.

Любая идея, как справиться с такими проблемами - казалось бы, нужна какая-то блокировка, но как сделать это надежно на серверах в ферме?

4b9b3361

Ответ 1

Если вы имеете дело с данными, которые необязательно нужно обновлять в реальном времени, и для меня один из них - это количество просмотров, то вы можете добавить поле expires к объектам, которые хранятся в memcache.

Как только это истечение произойдет, оно вернется к базе данных и прочитает новое значение, но до тех пор оно останется в покое.

Конечно, для новых сообщений вы можете захотеть это обновление чаще, но вы можете это кодировать.

Memcache сохраняет только одну копию вашего объекта в одном из своих экземпляров, а не во многих из них, поэтому я не буду беспокоиться о блокировке объекта или чем-либо еще. То есть для обработки базы данных, а не для вашего кеша.

Edit:

Memcache не гарантирует, что при получении и настройке с разных серверов ваши данные не будут сбиты.

Из документов memcache:

  • Ряд команд не является атомарным. Если вы выдаете "get" против элемента, работайте с данными, а затем хотите "установить" его обратно в memcached, вы не гарантируете, что это единственный процесс, работающий над этим значением. Параллельно вы можете переписать значение, установленное чем-то другим.

Условия гонки и устаревшие данные

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

Скажите, что вы кешируете последние пять комментариев для отображения на боковой панели вашего приложения. Вы решаете, что данные нужно обновлять только один раз в минуту. Однако вы пренебрегаете тем, что этот экран боковой панели отображается 50 раз в секунду! Таким образом, как только 60 секунд катится и кэш истекает, неожиданно 10+ процессов запускают один и тот же SQL-запрос для повторного заполнения этого кеша. Каждый раз, когда истекает срок действия кэша, возникает внезапный всплеск трафика SQL.

Хуже того, у вас есть несколько процессов, обновляющих одни и те же данные, а неправильный - встречающий кеш. Затем у вас есть устаревшие, устаревшие данные, плавающие.

Следует помнить о возможных проблемах при заполнении или повторном заполнении нашего кеша. Помните, что процесс проверки memcached, выборки SQL и хранения в memcached не является атомарным вообще!

Ответ 2

Я думаю - могло бы быть решение, чтобы отдельно хранить viewcount из объекта Post, а затем делать INCR на нем. Конечно, это потребовало бы считывания 2 отдельных значений из memcached при отображении информации.

Ответ 3

Операции memcached являются атомарными. серверный процесс будет ставить в очередь запросы и обслуживать каждый полностью, прежде чем перейти к следующему, поэтому нет необходимости блокировать.

edit: memcached имеет команду increment, которая является атомарной. Вам просто нужно сохранить счетчик как отдельное значение в кеше.

Ответ 4

Мы столкнулись с этим в нашей системе. Мы изменили так, чтобы

  • Если значение не установлено, оно устанавливает его с флагом ('g') и [8] вторым TTL и возвращает false, чтобы вызывающая функция его генерировала.
  • Если значение не помечено (! == 'g'), то unserialize и вернуть его.
  • Если значение отмечено (=== '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;
}