2008-09-29 14 views

Respuesta

5

GAE es una herramienta muy útil para crear aplicaciones web escalables. Algunas de las limitaciones señaladas por muchos no son compatibles con tareas en segundo plano, falta de tareas periódicas y límite estricto de cuánto tarda cada solicitud HTTP, si una solicitud excede ese límite de tiempo, la operación finaliza, lo que hace que las tareas que consumen tiempo sean imposibles. .

¿Cómo ejecutar la tarea en segundo plano?
En GAE, el código se ejecuta solo cuando hay una solicitud HTTP. Hay un límite de tiempo estricto (creo que 10 segundos) sobre cuánto tiempo puede tomar el código. Entonces, si no hay solicitudes, el código no se ejecuta. Uno de los trabajos sugeridos fue usar una caja externa para enviar solicitudes continuamente, creando así una tarea de fondo. Pero para esto necesitamos una caja externa y ahora dependemos de un elemento más. La otra alternativa era enviar 302 una respuesta de redireccionamiento para que el cliente reenviara la solicitud, esto también nos hace dependientes del elemento externo que es el cliente. ¿Qué pasa si esa caja externa es GAE en sí? Todos los que han utilizado un lenguaje funcional que no admite la construcción de bucles en el lenguaje conocen la alternativa, es decir, la recursividad es el bucle de sustitución. Entonces, ¿qué pasa si completamos parte de la computación y hacemos un HTTP GET en la misma url con muy poco tiempo de espera, por ejemplo, 1 segundo? Esto crea un bucle (recursión) en el código php que se ejecuta en 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"; 
?> 

De alguna manera esto no funciona en GAE. Entonces, ¿qué pasa si hacemos HTTP GET en alguna otra url, digamos url2, que hace HTTP GET en la primera url? Esto parece funcionar en GAE. El código para esto se parece a esto.

 
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() 

Como nos dimos cuenta de una forma de ejecutar tareas en segundo plano, permite construir abstracciones de tarea periódica (temporizador) y una estructura iterativa que se extiende a través de muchas peticiones HTTP (foreach).

Temporizador
Ahora el temporizador de construcción es sencillo. La idea básica es tener una lista de temporizadores y el intervalo al que se debe llamar cada uno. Una vez que alcanzamos ese intervalo llamamos a la función de devolución de llamada. Usaremos Memcache para mantener la lista de temporizadores. Para saber cuándo llamar a la devolución de llamada, almacenaremos una clave en Memcache con intervalo como tiempo de caducidad. Periódicamente (digamos 5 segundos) verificamos si esa clave está presente, si no está presente, llamamos a la devolución de llamada y nuevamente configuramos esa tecla con el intervalo.

 
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
Esto es necesario cuando queremos hacer larga la toma de cómputo decir hacer alguna operación en 1000 filas de bases de datos o ir a buscar 1000 URLs etc. idea básica es la lista de las devoluciones de llamada y argumentos en Memcache mantener y cada vez invocar devolución de llamada con el argumento.

 
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)) 

Ahora construir un programa que obtenga la lista de URL cada hora es sencillo. Aquí está el código.

 
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) 

código completo está aquí http://groups.google.com/group/httpmr-discuss/t/1648611a54c01aa He estado corriendo este código en appengine de unos días sin mucho problema.

Advertencia: Hacemos un uso intensivo de urlfetch. El límite en no de urlfetch por día es 160000. Por lo tanto, tenga cuidado de no alcanzar ese límite.

+0

¡increíble! me gusta – fuentesjr

+0

No veo cómo puede funcionar. ¿No superarás la cuota de 10 segundos en la sexta búsqueda recursiva? – Constantin

+1

Corrígeme si estoy equivocado, ¿no hay alguna política de AppEngine sobre la intercomunicación entre aplicaciones alojadas? – zotherstupidguy

2

La próxima versión de runtime tendrá algún tipo de motor de ejecución periódica a'la cron. Ver this message en el grupo de App Engine.

Entonces, todas las piezas del SDK parecen funcionar, pero mis pruebas indican que esto todavía no se está ejecutando en los servidores de producción: configuré un cron "cada 1 minutos" que se registra cuando se ejecuta y no ha sido llamado todavía

es difícil decir cuándo va a estar disponible, aunque ...

4

Puede Obtenga más información sobre trabajos cron en Python App Engine here.

1

utilizando los Deferred Python Library es la forma más fácil de hacer tarea en segundo plano en AppEngine usando Python que se construye en la parte superior de la 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

Existen ventajas y desventajas para usar la biblioteca diferida; consulte la sección "Cuándo usar ext.deferred" en el artículo de Nick Johnson: https://cloud.google.com/appengine/articles/deferred?hl=en –

Cuestiones relacionadas