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

Как реализовать "автоинкремент" в Google AppEngine

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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)

Ответ 4

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

Ответ 5

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

В принципе, у вас есть одна сущность, которая является счетчиком "master". Затем у вас есть несколько объектов (на основе нагрузки, которую нужно обрабатывать), которые имеют свои собственные счетчики. Эти осколки оставляют куски идентификаторов от мастера и служат из их диапазона до тех пор, пока не исчерпываются значения.

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

  • Вам нужно получить идентификатор.
  • Случайно выберите осколок.
  • Если начало осколка меньше его конца, запустите его и увеличьте.
  • Если начало осколка равно (или больше о-о) его концу, перейдите к мастеру, возьмите это значение и добавьте к нему сумму n. Установите осколки, начиная с полученного значения плюс один, и заканчивайте на извлеченный плюс n.

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

Изменить

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

Ответ 6

Помните: Sharding увеличивает вероятность того, что вы получите уникальное значение auto-increment, но не гарантирует его. Пожалуйста, примите рекомендации Ника, если вы ДОЛЖНЫ иметь уникальное автоматическое вмешательство.

Ответ 7

В моем блоге я реализовал что-то очень упрощенное, которое увеличивает IntegerProperty, iden, а не идентификатор ключа.

Я определяю 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() теперь увеличился, прежде чем перейти к следующему счету.

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

Ответ 8

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

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

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

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

Ответ 9

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

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