2017-02-23 3 views
0

У меня есть служба, которую я разработал на GAE. Приложение должно «галочку» каждые 3 секунды, чтобы выполнить кучу вычислений. Это симуляторная игра.Избегайте дублирования заданий (или связанных с ними) в очереди задач в Google App Engine

У меня есть вручную масштабируется экземпляр, который я начала, который использует deferred API и задачи очереди, как так (некоторые обработки ошибок и т.д., удален для ясности):

@app.route('/_ah/start') 
def start(): 
    log.info('Ticker instance started') 
    return tick() 

@app.route('/tick') 
def tick(): 
    _do_tick() 
    deferred.defer(tick, _countdown=3) 
return 'Tick!', 200 

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

Любые идеи, как лучше всего справиться с этим?

Насколько я вижу, вы не можете задать очередь «Есть ли задачи X уже там?» или «сколько элементов в очереди на данный момент?».

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

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

Я мог бы просто переместить все в обработчик загрузки, например:

@app.route('/_ah/start') 
def start(): 
    log.info('Ticker instance started') 
while True: 
     _do_tick() 
     sleep(3) 

return 200 

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

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

Или есть лучший способ справиться с этим на GAE?

+0

Выполнение чего-либо каждые 3 секунды кажется очень сложным. Не могли бы вы сделать все расчеты по требованию, когда это необходимо? Например, когда вы получаете запрос от пользователя, выполните все необходимые вычисления на основе текущего времени и представите результаты пользователю? –

+0

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

ответ

0

Вы можете установить значение task_retry_limit в необязательном аргументе _retry_options, как указано в https://stackoverflow.com/a/36621588/4495081.

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

+0

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

+0

Если вам это нужно - подумайте о том, как использовать memcache (бесплатно) вместо хранилища данных, просто будьте готовы к восстановлению, если данные memcache исчезнут –

+0

Я уже использую memcache. Процесс «тика» выполняет кучу запросов и вычислений базы данных, а результаты кэшируются в memcache. Затем клиенты, обращающиеся к службе, получают данные из memcache (hit) или пересчитываются из db (miss) –

1

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

TICKINTERVAL = 3 

@app.route('/_ah/start') 
def scheduler(): 
    log.info('Ticker instance started') 
    while True: 
     if game.is_running(): 
      task = taskqueue.add(
       url='/v1/game/tick', 
       queue_name='tickqueue', 
       method='PUT', 
       target='tickworker', 
       ) 
     else: 
      log.info('Tick skipped as game stopped') 
     db_session.rollback() 
     sleep(TICKINTERVAL) 

Я определил свою собственную очередь, tickqueue в queue.yaml

queue: 
- name: tickqueue 
    rate: 5/s 
    max_concurrent_requests: 1 
    retry_parameters: 
    task_retry_limit: 0 
    task_age_limit: 1m 

Очередь Безразлично 't повторить задачи и любые задачи, оставшиеся там, более чем на минуту, будут отменены.Я установил максимальный параллелизм в 1, так что это только попытки обработать один элемент за раз.

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

Это дает то преимущество, что я получаю запись в журнале для каждого тика (и это называется /v1/game/tick, поэтому легко заметить, в отличие от /_ah/deferred). Недостатком является то, что мне нужно использовать один экземпляр для планировщика и один для рабочего, так как вы не можете обрабатывать запросы экземпляра планировщика, поскольку он не будет выполнять до тех пор, пока не завершится /_ah/start, чего он никогда здесь не делает.

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