Как реализовать «автоинкремент» в Google AppEngine - PullRequest
31 голосов
/ 21 октября 2010

Я должен что-то пометить "с сильным монотонным увеличением".Будь то номера счетов-фактур, номера транспортных этикеток или т.).

Причудливый способ сказать: мне нужно сосчитать 1,2,3,4 ... Доступное мне пространство чисел, как правило, составляет 100 000 номеров, и мне нужно, возможно, 1000 в день.*

Я знаю, что это сложная проблема в распределенных системах, и часто нам гораздо лучше с GUID.Но в этом случае по юридическим причинам мне нужна «традиционная нумерация».

Может ли это быть реализовано в Google AppEngine (желательно на Python)?

Ответы [ 9 ]

25 голосов
/ 21 октября 2010

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

7 голосов
/ 14 ноября 2010

gaetk - Google AppEngine Toolkit теперь поставляется с простой библиотечной функцией для получения числа в последовательности. Он основан на транзакционном подходе Ника Джонсона и может довольно легко использоваться в качестве основы для шардингового подхода Мартина фон Лёвиса:

>>> from gaeth.sequences import * 
>>> init_sequence('invoce_number', start=1, end=0xffffffff)
>>> get_numbers('invoce_number', 2)
[1, 2]

Функциональность в основном реализована так:

def _get_numbers_helper(keys, needed):
  results = []

  for key in keys:
    seq = db.get(key)
    start = seq.current or seq.start
    end = seq.end
    avail = end - start
    consumed = needed
    if avail <= needed:
      seq.active = False
      consumed = avail
    seq.current = start + consumed
    seq.put()
    results += range(start, start + consumed)
    needed -= consumed
    if needed == 0:
      return results
  raise RuntimeError('Not enough sequence space to allocate %d numbers.' % needed)

def get_numbers(needed):
  query = gaetkSequence.all(keys_only=True).filter('active = ', True)
  return db.run_in_transaction(_get_numbers_helper, query.fetch(5), needed)
7 голосов
/ 30 октября 2010

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

Например, если у вас есть понятие «пользователи», вы можете выделить группу хранения для каждого пользователя (создавая некоторый глобальный объект для каждого пользователя). У каждого пользователя есть список зарезервированных идентификаторов. При выделении идентификатора для пользователя выберите зарезервированный (в транзакции). Если ни одного идентификатора не осталось, сделайте новую транзакцию, выделив, скажем, 100 идентификаторов из глобального пула, затем выполните новую транзакцию, чтобы добавить их пользователю и одновременно отозвать один. Предполагая, что каждый пользователь взаимодействует с приложением только последовательно, параллелизма в пользовательских объектах не будет.

5 голосов
/ 31 марта 2013

Если вы не слишком строги в последовательности, вы можете «осколить» свой инкрементатор.Это можно рассматривать как «в конечном итоге последовательный» счетчик.

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

Быстрый алгоритм:

  1. Вам необходимо получить идентификатор.
  2. Выберите шард наугад.
  3. Если начало осколка меньше его конца, возьмите его начало и увеличьте его.
  4. Если начало осколка равно (или болеео) его конец, иди к мастеру, возьми значение и добавь к нему сумму n.Задайте для начального значения извлечения шарды плюс единицу, а для окончательного извлечения - плюс n.

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

Редактировать

Мне это нужно длямое приложение (именно поэтому я искал вопрос: P), поэтому я реализовал свое решение.Он может захватывать отдельные идентификаторы, а также эффективно захватывать партии.Я проверил его в контролируемой среде (на appengine), и он работал очень хорошо.Вы можете найти код на github .

4 голосов
/ 21 октября 2010

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

1 голос
/ 27 июля 2014

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

first, last = MyModel.allocate_ids(1000000)
keys = [Key(MyModel, id) for id in range(first, last+1)]

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

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

0 голосов
/ 02 апреля 2015

Я думаю об использовании следующего решения: используйте CloudSQL (MySQL), чтобы вставить записи и назначить последовательный идентификатор (возможно, с помощью очереди задач), позже (используя задачу Cron) переместите записи из CloudSQL обратно вХранилище данных.

Объекты также могут иметь UUID, поэтому мы можем отобразить объекты из хранилища данных в CloudSQL, а также иметь последовательный идентификатор (по юридическим причинам).

0 голосов
/ 27 июля 2014

Я реализовал что-то очень простое для своего блога, который увеличивает IntegerProperty, iden, а не Key ID.

Я определяю max_iden(), чтобы найти максимальное iden целое число, используемое в настоящее время. Эта функция просматривает все существующие сообщения в блоге.

def max_iden():
    max_entity = Post.gql("order by iden desc").get()
    if max_entity:
        return max_entity.iden
    return 1000    # If this is the very first entry, start at number 1000

Затем, при создании нового сообщения в блоге, я присваиваю ему iden свойство max_iden() + 1

new_iden = max_iden() + 1
p = Post(parent=blog_key(), header=header, body=body, iden=new_iden)
p.put()

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

В целом: хрупкий, неэффективный код.

0 голосов
/ 31 марта 2013

Помните: Sharding увеличивает вероятность того, что вы получите уникальное значение автоинкремента, но не гарантирует его. Пожалуйста, примите совет Ника, если у вас ДОЛЖЕН быть уникальный автоинкремент.

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