2014-09-22 2 views
0

мне нужно придумать стратегию для обработки клиентских-повторных попыток по созданию ввода данных-магазин:Идемпотентный клиент-retryable запись в AppEngine дается в конечном счете последовательные запросы и т.д.

  • Клиент посылает запрос на создание нового запись в базе данных
  • Сервер выполняет создание входа и готовит на успех Ответить
  • некоторых ошибки случаются, что делает клиент считает, что запрос не был обработан (потеря пакетов, ...)
  • клиента посылает такое же запрос на создание новая запись в базе данных снова
  • сервер обнаруживает попытку и воссоздает и отправляет оригинальный ответ без создания другой записи данных магазина
  • Клиент получает ответ
  • Все счастливы и только одна запись была создана в базе данных

У меня есть одно ограничение: Сервер БЕСПЛАТНО! У клиента нет никакой информации о сеансе.

Моя текущая идея заключается в следующем:

  • Tag каждый создать-запрос с гарантированным глобально уникальным идентификатором (вот как я создаю их, хотя они не слишком актуальны для вопроса):
    • Используя хранилище данных (и memcache), я назначаю уникальный, монотонно увеличивающийся идентификатор для каждого экземпляра сервера после его загрузки (назовем его SI)
    • Когда клиент запрашивает начальную страницу, экземпляр, который обслуживал запрос генерирует уникальную монотону nically увеличения загрузки страницы идентификатора (PL) и посылает SI.PL клиента вместе с содержимым страницы
    • Для каждого создания-запроса, клиент генерирует уникальный запрос монотонно возрастающий идентификатор (RI) и отправляет SI.PL.RI вместе с запросом создать самые
  • Для каждого создания-запроса, сервер сначала проверяет, знает ли она создать тег
  • Если нет, то он создает новую запись и каким-то образом сохраняет тег создания, по с ним
  • Если он знает тег, он использует его, чтобы найти первоначально созданную запись и воссоздает соответствующий ответ

Вот варианты осуществления, которые я имею в виду прямо сейчас, и их проблемы:

  1. Хранить создать тег как индексированные собственности внутри записи:
    • Когда сервер получает запрос, он должен использовать запрос, чтобы найти любую существующую запись
    • Проблемы: Поскольку запросы в AppEngine только в конечном счете, последовательным, он может пропустить запись
  2. Используйте создать тег как запись является ключевым:
    • Должно быть нормально, как это гарантированно будет уникальным, если цифры не завернуть (вряд ли с долгот)
    • Minor неудобством: Это увеличивает длину ключей записей, в любом впрок (ненужный накладные расходы)
    • Основная проблема: Это будет генерировать последовательные ключи входа в хранилище данных, которые следует избегать любой ценой, как это creates hot-spots in the stored data и, таким образом, может повлиять на производительность значительно

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

Или, может быть, есть лучший подход?

+2

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

+0

@Greg Я могу использовать данные, это правда. Но, к сожалению, это не решает проблему согласованных запросов: запрос может по-прежнему возвращать пустой набор результатов, даже если запись уже существует. –

+0

@Greg Возможно, я сделал звук более сложным, чем он есть. Достаточно тривиально делать id + = 1, чтобы генерировать эти «уникальные монотонно возрастающие» серверные, клиентские и, следовательно, запросы-идентификаторы. Это намного БОЛЕЕ быстрый и более переносимый, чем беспокоиться о том, какая часть данных мне нужна для хеша (то есть какая часть этого является уникальным идентификатором) и как обрабатывать потенциальные изменения в этой части данных другими клиентами до того, как будет получена повторная попытка. .. :) –

ответ

0

Хотя это можно сделать вариант осуществления (1) сверху работы, используя хитро структура данных, правильные транзакции и сбор мусора, будет довольно больно заставить ее работать надежно.

Таким образом, кажется, что правильное решение пойти с обобщенной версией варианты (2) вместо:

использовать некоторый уникальный идентификатор для созданного объекта в качестве ключа сущности.

Таким образом, если тот же объект снова создан в повторной попытке, это либо легко надежно найти существующую копию (как gets сильно соответствует), или слепо писать снова будет просто переписать первую версию.

@Greg предложил в комментариях использовать хэш над уникальными идентифицирующими данными объекта как ключ. Хотя это решает проблему наличия ключей, которые равномерно распределены по пространству параметров и, таким образом, приводит к эффективному распределению данных по физическим местам хранения, это создает новую проблему необходимости управления (или игнорирования) хеш-коллизий, особенно если кто-то пытается удержать ключи от очень долгого времени.

Есть способы справиться с этими столкновениями. Например: в случае столкновения сравните фактический контент, чтобы проверить, действительно ли это дубликат, а если нет, добавьте «1» к ключу. Затем посмотрите, существует ли этот ключ и если да, проверьте еще раз, если он имеет тот же контент. Если нет, добавьте вместо этого «2», еще раз проверьте наличие столкновения и т. Д. Хотя это работает, оно становится довольно грязным.

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

Но, к счастью, у меня уже есть уникальный глобальный уникальный идентификатор для данных: create-tag. И получается, два вопроса, которые я видел с помощью его оба легко исправить с помощью какого-то умного битного раскладу:

Используя те же идентификаторы, что и в первоначальном вопросе, мой создать тег-SI.PL.RI состоит от SI, который будет постоянно увеличиваться, PL, который сбрасывается до 0 каждый раз при создании нового экземпляра сервера и RI, который сбрасывается для каждого нового сеанса клиента. Таким образом, RI, вероятно, всегда крошечный, PL останется несколько небольшим, и SI будет медленно становиться огромным.

Учитывая, что я мог бы, например, создать ключ, как это (начиная с наиболее значимыми битами):

- Lowest 10 bits of PL 
- Lowest 4 bits of RI 
- Lowest 17 bits of SI 
- 1 bit indicating whether there are any further non-zero values 
- Next lowest 10 bits of PL 
- Next lowest 4 bits of RI 
- Next lowest 17 bits of SI 
- 1 bit indicating whether there are any further non-zero values 
- ... until ALL bits of RI, PL, and SI are used (eventually breaking 10-4-17 pattern) 

Таким образом, генерируемые ключи распространяются хорошо по параметру пространства, если отсортирован в лексикографическом порядке (как это делает AppEngine), и первые ключи всего лишь наполовину до тех пор, пока они не сгенерированы автоматически, и они только увеличиваются по мере необходимости.

Помимо 1:

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

И если нет экземпляра сервера никогда не является живым достаточно долго, чтобы служить более млн страничных нагрузок и ни один клиент никогда не создает более 256 новых объектов в одной сессии, и экземпляры серверов не порождали быстрее чем каждый раз второй в среднем, потребуется еще 500 лет, прежде чем ключи получат больше 8 байтов (и, следовательно, дольше, чем автогенерированные) в среднем. Должно быть хорошо ... :)

Помимо 2:

Если мне нужно использовать ключи индексировать Java HashMap вместо этого, hashCode() функции моего ключа-объект может вместо того, чтобы возвращать целое число построенный из первых 4 ключевых байтов в обратном порядке для распространения ключей по ковши.

1

Как вы назначаете ключ для нового объекта?

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

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

Это не фантазии, как «случайные перспективные последовательности», но это проще и надежнее :)

+0

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

+1

Тем не менее, это похоже на классический случай чрезмерной инженерии. Если 1 запрос из 100 000 не выполняется таким образом, просто перезагрузите данные пользователю. Когда время ответа истечет, состояние хранилища данных будет уже согласованным. –

+0

Согласен, я пытаюсь работать на 5-м 9 здесь, но поскольку это код, который войдет в библиотеку, которая, надеюсь, получит кучу использования, я бы предпочел потратить день или два на действительно решение проблемы, надеясь, что библиотека столкнется с таким количеством пользователей, что их совместное разочарование будет ниже моего ... :) –

Смежные вопросы