Фоновые задачи в App Engine - PullRequest
6 голосов
/ 29 сентября 2008

Как я могу запускать фоновые задачи в App Engine?

Ответы [ 8 ]

12 голосов
/ 23 июня 2009

Вы можете использовать Python API очереди задач .

5 голосов
/ 29 сентября 2008

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

Как запустить фоновое задание?
В GAE код выполняется только при наличии HTTP-запроса. Существует строгий лимит времени (я думаю, 10 сек), сколько времени может занять код. Так что если нет запросов, то код не выполняется. Одним из предложенных способов решения этой проблемы было использование внешнего окна для непрерывной отправки запросов, что создавало фоновую задачу. Но для этого нам нужен внешний бокс, и теперь мы зависим от еще одного элемента. Другой альтернативой была отправка 302 ответа о перенаправлении, чтобы клиент повторно отправлял запрос, что также делает нас зависимыми от внешнего элемента, который является клиентом. Что если эта внешняя коробка - сама GAE? Каждый, кто использовал функциональный язык, который не поддерживает циклическую конструкцию в языке, знает об альтернативе, то есть рекурсия является заменой цикла. Так что, если мы завершим часть вычисления и сделаем HTTP GET по тому же URL с очень коротким временем ожидания, скажем, 1 секунда? Это создает цикл (рекурсию) для PHP-кода, работающего на Apache.

<?php
$i = 0;
if(isset($_REQUEST["i"])){
        $i= $_REQUEST["i"];
    sleep(1);
}
$ch = curl_init("http://localhost".$_SERVER["PHP_SELF"]."?i=".($i+1));
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_exec($ch);
print "hello world\n";
?>

Некоторые, как это не работает на GAE. Так что, если мы сделаем HTTP GET для другого URL, скажем, url2, который выполняет HTTP GET для первого URL? Это похоже на работу в GAE. Код для этого выглядит следующим образом.

class FirstUrl(webapp.RequestHandler):
    def get(self):
        self.response.out.write("ok")
        time.sleep(2)
        urlfetch.fetch("http://"+self.request.headers["HOST"]+'/url2')

class SecondUrl(webapp.RequestHandler):
    def get(self):
        self.response.out.write("ok")
        time.sleep(2)
        urlfetch.fetch("http://"+self.request.headers["HOST"]+'/url1')

application = webapp.WSGIApplication([('/url1', FirstUrl), ('/url2', SecondUrl)])
def main():
    run_wsgi_app(application)
if __name__ == "__main__":
    main()

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

Таймер
Теперь таймер строительства прямо вперед. Основная идея состоит в том, чтобы иметь список таймеров и интервал, с которым каждый из них должен быть вызван. Как только мы достигнем этого интервала, вызовем функцию обратного вызова. Мы будем использовать memcache для поддержки списка таймеров. Чтобы узнать, когда вызывать обратный вызов, мы сохраним ключ в memcache с интервалом как время истечения. Мы периодически (скажем, 5 секунд) проверяем, присутствует ли этот ключ, если он отсутствует, затем вызываем обратный вызов и снова устанавливаем этот ключ с интервалом.

def timer(func, interval):
    timerlist = memcache.get('timer')
    if(None == timerlist):
        timerlist = []
    timerlist.append({'func':func, 'interval':interval})
    memcache.set('timer-'+func, '1', interval)
    memcache.set('timer', timerlist)

def checktimers():
    timerlist = memcache.get('timer')
    if(None == timerlist):
        return False
    for current in timerlist:
        if(None == memcache.get('timer-'+current['func'])):
            #reset interval
            memcache.set('timer-'+current['func'], '1', current['interval'])
            #invoke callback function
            try:
                eval(current['func']+'()')
            except:
                pass
            return True
    return False

Foreach
Это необходимо, когда мы хотим долго вычислять, скажем, выполнить какую-то операцию с 1000 строками базы данных или получить 1000 URL-адресов и т. Д. Основная идея состоит в том, чтобы поддерживать список обратных вызовов и аргументов в memcache и каждый раз вызывать обратный вызов с аргументом.

def foreach(func, args):
    looplist = memcache.get('foreach')
    if(None == looplist):
        looplist = []
    looplist.append({'func':func, 'args':args})
    memcache.set('foreach', looplist)

def checkloops():
    looplist = memcache.get('foreach')
    if(None == looplist):
        return False
    if((len(looplist) > 0) and (len(looplist[0]['args']) > 0)):
        arg = looplist[0]['args'].pop(0)
        func = looplist[0]['func']
        if(len(looplist[0]['args']) == 0):
            looplist.pop(0)
        if((len(looplist) > 0) and (len(looplist[0]['args']) > 0)):
            memcache.set('foreach', looplist)
        else:
            memcache.delete('foreach')
        try:
            eval(func+'('+repr(arg)+')')
        except:
            pass
        return True
    else:
        return False

# instead of
# foreach index in range(0, 1000):
#   someoperaton(index)
# we will say
# foreach('someoperaton', range(0, 1000))

Теперь просто построить программу, которая выбирает список URL-адресов каждый час. Вот код.

def getone(url):
    try:
        result = urlfetch.fetch(url)
        if(result.status_code == 200):
            memcache.set(url, '1', 60*60)
            #process result.content
    except :
        pass

def getallurl():
    #list of urls to be fetched
    urllist = ['http://www.google.com/', 'http://www.cnn.com/', 'http://www.yahoo.com', 'http://news.google.com']
    fetchlist = []
    for url in urllist:
        if (memcache.get(url) is None):
            fetchlist.append(url)
    #this is equivalent to
    #for url in fetchlist: getone(url)
    if(len(fetchlist) > 0):
        foreach('getone', fetchlist)

#register the timer callback
timer('getallurl', 3*60)

полный код здесь http://groups.google.com/group/httpmr-discuss/t/1648611a54c01aa Я запускаю этот код в appengine в течение нескольких дней без особых проблем.

Предупреждение: мы интенсивно используем urlfetch. Ограничение на количество urlfetch в день составляет 160000. Поэтому будьте осторожны, чтобы не достичь этого предела.

4 голосов
/ 01 ноября 2012

Более подробную информацию о заданиях cron вы можете найти в Python App Engine здесь .

2 голосов
/ 06 февраля 2009

В новой версии среды выполнения будет какой-то механизм периодического выполнения a'la cron. См. это сообщение в группе AppEngine.

Итак, все части SDK работают, но мое тестирование показывает, что это еще не работает на производственных серверах - я установил cron "каждые 1 минуту", который регистрирует, когда он работает, и он не работает был назван еще

Трудно сказать, когда это будет доступно, хотя ...

1 голос
/ 28 января 2016

Использование отложенной библиотеки Python - это самый простой способ выполнения фоновой задачи в Appengine с использованием Python, построенного на основе API-интерфейса TaskQueue.

from google.appengine.ext import deferred

def do_something_expensive(a, b, c=None):
    logging.info("Doing something expensive!")
    # Do your work here

# Somewhere else
deferred.defer(do_something_expensive, "Hello, world!", 42, c=True)
0 голосов
/ 14 августа 2013

В приложение встроен механизм cron.

Пожалуйста, обратитесь к: https://developers.google.com/appengine/docs/python/config/cron?hl=en

0 голосов
/ 06 марта 2012

Если вы хотите запускать фоновые периодические задачи, см. этот вопрос (AppEngine cron)

Если ваши задачи не являются периодическими, см. API Python для очереди задач или Java API для очереди задач

0 голосов
/ 07 декабря 2011

Использовать очередь задач - http://code.google.com/appengine/docs/java/taskqueue/overview.html

...