Вопрос:

Amazon SimpleDB Woes: реализация атрибутов счетчика

amazon-web-services amazon-simpledb

4709 просмотра

6 ответа

3943 Репутация автора

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

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

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

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

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

Автор: Justin Источник Размещён: 30.09.2009 04:04

Ответы (6)


20 плюса

5335 Репутация автора

Решение

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

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

Легкий путь

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

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

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

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

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

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

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

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

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

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

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

Трудный путь

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

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

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

put (ключ, значение = "Ver016 Count090")

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

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

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

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

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

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

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

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

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

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

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

Заметка

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

Автор: Mocky Размещён: 30.09.2009 03:04

2 плюса

7458 Репутация автора

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

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

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

Благодаря Скотту

Автор: Scott McKenzie Размещён: 14.10.2009 11:55

2 плюса

3943 Репутация автора

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

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

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

Автор: Justin Размещён: 15.10.2009 04:57

15 плюса

611 Репутация автора

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

Теперь, чтобы реализовать счетчик - просто вызовите 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
Автор: Stephen McCarthy Размещён: 02.03.2010 10:59

0 плюса

80 Репутация автора

Также были знакомые потребности / проблемы.

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

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

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

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

COUNTER (w / 5shards) создает: shard0 {numshards = 5} (только для информации) shard1 {count = 0, numshards = 5, timestamp = 0} shard2 {count = 0, numshards = 5, timestamp = 0} shard3 {count = 0, numshards = 5, timestamp = 0} shard4 {count = 0, numshards = 5, timestamp = 0} shard5 {count = 0, numshards = 5, timestamp = 0}

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

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

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

Автор: feynmansbastard Размещён: 18.07.2014 12:07

1 плюс

31 Репутация автора

Ответ на feynmansbastard:

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

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

Автор: Maxim Filippovich Размещён: 26.01.2015 09:15
Вопросы из категории :
32x32