Проблемы Amazon SimpleDB:Реализация атрибутов счетчика

StackOverflow https://stackoverflow.com/questions/1496066

Вопрос

Короче говоря, я переписываю часть системы и ищу способ сохранить некоторые счетчики посещений в AWS SimpleDB.

Для тех из вас, кто не знаком с SimpleDB, (основная) проблема с хранением счетчиков заключается в том, что задержка распространения облака часто превышает секунду.В настоящее время наше приложение получает ~ 1500 просмотров в секунду.Не все эти обращения будут сопоставлены с одним и тем же ключом, но приблизительная цифра может составлять около 5-10 обновлений ключа каждую секунду.Это означает, что если бы мы использовали традиционный механизм обновления (чтение, увеличение, сохранение), мы бы в конечном итоге непреднамеренно отбросили значительное количество обращений.

Одним из потенциальных решений является сохранение счетчиков в memcache и использование задачи cron для передачи данных.Большая проблема заключается в том, что это не "правильный" способ сделать это.Memcache на самом деле не следует использовать для постоянного хранения...в конце концов, это уровень кэширования.Кроме того, в конечном итоге мы столкнемся с проблемами, когда будем выполнять push, убедившись, что удаляем правильные элементы, и надеясь, что при их удалении не возникнет разногласий (что весьма вероятно).

Другим потенциальным решением является сохранение локальной базы данных SQL и запись счетчиков туда, обновление нашего внешнего интерфейса SimpleDB при каждом большом количестве запросов или запуск cron-задачи для отправки данных.Это решает проблему синхронизации, поскольку мы можем включить временные метки, чтобы легко устанавливать границы для нажатий SimpleDB.Конечно, есть и другие проблемы, и хотя это может сработать при приличном количестве взломов, это не кажется самым элегантным решением.

Кто-нибудь сталкивался с подобной проблемой в своем опыте или у вас есть какие-то новые подходы?Будем признательны за любые советы или идеи, даже если они не полностью устранены.Я уже некоторое время думал об этом, и мне не помешали бы некоторые новые перспективы.

Это было полезно?

Решение

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

Работая строго внутри SimpleDB, есть два способа заставить его работать.Простой метод, для очистки которого требуется что-то вроде задания cron.Или гораздо более сложная техника, которая очищает по ходу дела.

Простой способ

Самый простой способ — создавать разные предметы для каждого «попадания».С единственным атрибутом, который является ключевым.Накачивайте домен(ы) счетчиками быстро и легко.Когда вам нужно получить количество (предположительно, гораздо реже), вам нужно выполнить запрос

SELECT count(*) FROM domain WHERE key='myKey'

Конечно, это приведет к неограниченному росту вашего домена (доменов), а выполнение запросов со временем будет занимать все больше и больше времени.Решением является сводная запись, в которой вы суммируете все собранные на данный момент данные по каждому ключу.Это просто элемент с атрибутами для ключа {summary='myKey'} и меткой времени «Последнее обновление» с точностью до миллисекунды.Для этого также необходимо добавить атрибут «метка времени» к элементам «хит».Сводные записи не обязательно должны находиться в одном домене.Фактически, в зависимости от вашей настройки, их лучше всего хранить в отдельном домене.В любом случае вы можете использовать ключ в качестве имени элемента и использовать GetAttributes вместо выполнения SELECT.

Теперь подсчет представляет собой двухэтапный процесс.Вам нужно извлечь сводную запись, а также запросить «Метка времени», строго превышающую время «Последнего обновления» в вашей сводной записи, и сложить два значения вместе.

SELECT count(*) FROM domain WHERE key='myKey' AND timestamp > '...'

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

Это решение работает в условиях одновременных обновлений, поскольку даже если одновременно записывается множество сводных записей, все они верны, и какая бы из них ни победила, она все равно будет правильной, поскольку счетчик и атрибут «Последнее обновление» будут согласовываться с каждым из них. другой.

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

Это хорошо работает с кэшированием.Если ваш кеш выйдет из строя, у вас есть надежная резервная копия.

Придет время, когда кто-то захочет вернуться и отредактировать/удалить/добавить запись со старым значением «Timestamp».Вам придется обновить свою сводную запись (для этого домена) в это время, иначе ваши подсчеты будут отключены, пока вы не пересчитаете эту сводку.

Это даст вам количество, синхронизированное с данными, которые в данный момент просматриваются в окне согласованности.Это не даст вам подсчета с точностью до миллисекунды.

Трудный путь

Другой способ — использовать обычный механизм чтения-инкремента-сохранения, но также записать составное значение, которое включает номер версии вместе с вашим значением.Если используемый вами номер версии на 1 больше номера версии обновляемого значения.

get(key) возвращает значение атрибута="Ver015 Count089"

Здесь вы получаете число 89, которое было сохранено как версия 15.Когда вы делаете обновление, вы пишете такое значение:

put(key, value="Ver016 Count090")

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

Это потребует от вас сделать несколько дополнительных вещей.

  1. способность выявлять и разрешать конфликты всякий раз, когда вы выполняете GET
  2. простой номер версии не подойдет, вам нужно будет включить временную метку с разрешением хотя бы до миллисекунды и, возможно, также идентификатор процесса.
  3. на практике вам нужно, чтобы ваше значение включало номер текущей версии и номер версии значения, на котором основано ваше обновление, чтобы упростить разрешение конфликтов.
  4. вы не можете вести бесконечный контрольный журнал в одном элементе, поэтому вам придется по ходу дела удалять старые значения.

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

Когда я говорю «разрешать конфликты во время GET», я имею в виду, что если вы читаете элемент, и его значение выглядит следующим образом:

      11 --- 12
     /
10 --- 11
     \
       11

Вы должны быть в состоянии понять, что реальное значение равно 14.Это можно сделать, если для каждого нового значения включить версию значения(й), которое вы обновляете.

Это не должно быть ракетостроение

Если все, что вам нужно, это простой счетчик: это уже перебор.Создание простого счетчика не должно быть сложной задачей.Вот почему SimpleDB, возможно, не лучший выбор для создания простых счетчиков.

Это не единственный способ, но большинство из этих вещей необходимо будет сделать, если вы реализуете решение SimpleDB вместо фактической блокировки.

Не поймите меня неправильно, мне действительно нравится этот метод именно потому, что в нем нет блокировки, а ограничение на количество процессов, которые могут использовать этот счетчик одновременно, составляет около 100.(из-за ограничения на количество атрибутов в элементе) И с некоторыми изменениями вы можете выйти за пределы 100.

Примечание

Но если бы все эти детали реализации были скрыты от вас и вам нужно было просто вызвать инкремент(ключ), это было бы совсем несложно.В SimpleDB клиентская библиотека является ключом к тому, чтобы сделать сложные вещи простыми.Но в настоящее время (насколько мне известно) не существует общедоступных библиотек, реализующих эту функциональность.

Другие советы

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

Теперь, чтобы реализовать счетчик, просто вызовите GetAttributes, увеличьте счетчик, а затем вызовите PutAttributes с правильно установленным ожидаемым значением.Если Amazon отвечает ошибкой ConditionalCheckFailed, затем повторите всю операцию.

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

псевдокод:

begin
  attributes = SimpleDB.GetAttributes
  initial_version = attributes[:version]
  attributes[:counter1] += 3
  attributes[:counter2] += 7
  attributes[:version] += 1
  SimpleDB.PutAttributes(attributes, :expected => {:version => initial_version})
rescue ConditionalCheckFailed
  retry
end

Я вижу, вы уже приняли ответ, но это можно считать новым подходом.

Если вы создаете веб-приложение, вы можете использовать продукт Google Analytics для отслеживания показов страниц (если сопоставление страницы с элементами домена подходит), а затем использовать Analytics API для периодической передачи этих данных в сами элементы.

Я не продумывал это подробно, поэтому могут быть дыры.На самом деле мне было бы очень интересно узнать ваше мнение об этом подходе, учитывая ваш опыт в этой области.

Спасибо, Скотт

Кому интересно, как я с этим справился...(немного специфично для Java)

В итоге я использовал EhCache для каждого экземпляра сервлета.Я использовал UUID в качестве ключа и Java AtomicInteger в качестве значения.Периодически поток выполняет итерацию по кешу и отправляет строки в домен временной статистики simpledb, а также записывает строку с ключом в домен недействительности (что происходит автоматически, если ключ уже существует).Поток также уменьшает счетчик на предыдущее значение, гарантируя, что мы не пропустим ни одного попадания во время его обновления.Отдельный поток проверяет домен инвалидации simpledb и собирает статистику во временных доменах (для каждого ключа есть несколько строк, поскольку мы используем экземпляры ec2), отправляя ее в реальный домен статистики.

Я провел небольшое нагрузочное тестирование, и, похоже, оно хорошо масштабируется.Локально я смог обработать около 500 обращений в секунду, прежде чем нагрузочный тестер сломался (не сервлеты - ха), поэтому, во всяком случае, я думаю, что работа на ec2 должна только улучшить производительность.

Ответ фейнмансубастарду:

Если вы хотите хранить огромное количество событий, я предлагаю вам использовать распределенные системы журналов фиксации, такие как Кафка или оу кинезис.Они позволяют дешево и просто потреблять поток событий (цена kinesis составляет 25 долларов в месяц за 1 тыс. событий в секунду) — вам просто нужно реализовать потребителя (на любом языке), который массово считывает все события из предыдущей контрольной точки, агрегирует счетчики в памяти. затем сбрасывает данные в постоянное хранилище (dynamodb или mysql) и фиксирует контрольную точку.

События можно просто регистрировать с помощью журнала nginx и передавать в kafka/kinesis с помощью свободно владеющий.Это очень дешевое, производительное и простое решение.

У него также были похожие потребности / проблемы.

Я посмотрел на использование Google Analytics и count.ly .последнее казалось слишком дорогим, чтобы оно того стоило (к тому же у них есть несколько запутанное определение сеансов).GA я бы с удовольствием использовал, но я потратил два дня, используя их библиотеки и некоторые сторонние (gadotnet и еще одну, возможно, из codeproject).к сожалению, я мог видеть сообщения о счетчиках только в разделе GA realtime, но никогда в обычных информационных панелях, даже когда api сообщал об успехе.вероятно, мы делали что-то не так, но мы превысили наш временной бюджет для ga.

У нас уже был существующий счетчик simpledb, который обновлялся с использованием условных обновлений, как упоминалось предыдущим комментатором.Это работает хорошо, но страдает при возникновении разногласий и неопределенности, при которых пропускаются подсчеты (например, наш самый обновленный счетчик потерял несколько миллионов подсчетов в течение 3 месяцев по сравнению с системой резервного копирования).

Мы внедрили более новое решение, которое в чем-то похоже на ответ на этот вопрос, за исключением того, что оно намного проще.

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

СЧЕТЧИК (с 5 осколками) создает :shard0 { numshards = 5 } (только для информации) shard1 { количество = 0, numshards = 5, временная метка = 0} сегмент2 { количество = 0, порядковые номера = 5, временная метка = 0} сегмент3 { количество = 0, порядковые номера = 5, временная метка = 0} shard4 { количество = 0, числовые метки = 5, временная метка = 0} shard5 { количество = 0, числовые метки = 5, временная метка = 0 }

Сегментированная запись Зная количество сегментов, просто случайным образом выберите сегмент и попробуйте записать в него условно.Если это не удается из-за конфликта, выберите другой сегмент и повторите попытку.Если вы не знаете количество сегментов, получите его из корневого сегмента, который присутствует независимо от того, сколько существует сегментов.Поскольку он поддерживает несколько операций записи на счетчик, это уменьшает проблему конкуренции в соответствии с вашими потребностями.

Сегментированные чтения если вы знаете количество сегментов, прочитайте каждый сегмент и просуммируйте их.Если вы не знаете количество сегментов, получите его из корневого сегмента, а затем прочитайте все и суммируйте.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top