2008-09-29 4 views

ответ

5

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 URLs и т.д. Основная идея заключается в том, чтобы поддерживать список обратных вызовов и аргументы в кэше и каждый раз вызывайте обратный вызов с аргументом.

 
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. Поэтому будьте осторожны, чтобы не достичь этого предела.

+0

потрясающий! Мне нравится – fuentesjr 2008-10-01 04:47:50

+0

Я не вижу, как это может работать. Разве вы не превысите 10-секундную квоту на 6-й рекурсивной выборке? – Constantin 2008-10-03 20:43:44

+1

Исправьте меня, если я ошибаюсь, нет ли политики AppEngine относительно взаимодействия между размещенными приложениями? – zotherstupidguy 2009-05-04 21:12:01

2

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

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

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

4

Вы можете найти больше о работе cron в Python App Engine here.

1

Использование Deferred Python Library это самый простой способ сделать фоновую задачу на Appengine с помощью Python, который построен на вершине TaskQueue API.

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) 
Смежные вопросы