2011-08-15 9 views
35

Estoy intentando comprender completamente las opciones para el manejo concurrente de solicitudes en Rack. Utilicé async_sinatra para crear una aplicación de sondeo largo, y ahora estoy experimentando con un rack de metal desnudo usando throw :async y/o la bandera Thin's Thread. Me siento cómodo con el tema, pero hay algunas cosas que no puedo entender. (No, no estoy confundiendo la concurrencia para el paralelismo, y sí, entiendo las limitaciones impuestas por el GIL).Simultaneidad de rack: rack.multithread, async.callback o ambos?

Q1. Mis pruebas indican que thin --threaded (es decir, rack.multithread=true) ejecuta solicitudes simultáneamente en hilos separados (supongo que usando EM), lo que significa que la solicitud de larga ejecución A no bloqueará la solicitud B (IO a un lado). Esto significa que mi aplicación no requiere ninguna codificación especial (por ejemplo, devoluciones de llamada) para lograr la simultaneidad (una vez más, se ignoran las llamadas bloqueadas de DB, IO, etc.). Esto es lo que creo que he observado, ¿es correcto?

Q2. Hay otro medio más discutido para lograr la concurrencia, involucrando EventMachine.defer y throw :async. Estrictamente hablando, las solicitudes son no manejadas con hilos. Se tratan en serie, pero pasan su trabajo pesado y una devolución de llamada a EventMachine, que usa async.callback para enviar una respuesta en otro momento. Después de que la solicitud A haya descargado su trabajo a EM.defer, se inicia la solicitud B. ¿Es esto correcto?

Q3. Suponiendo que lo anterior sea más o menos correcto, ¿hay alguna ventaja particular en un método sobre el otro? Obviamente --threaded parece una bala mágica. ¿Hay algún inconveniente? Si no, ¿por qué todos hablan de async_sinatra/throw :async/async.callback? Quizás el primero es "Quiero hacer que mi aplicación Rails sea un poco más ágil bajo una gran carga" y esta última es más adecuada para aplicaciones con muchas solicitudes de larga ejecución. O tal vez la escala es un factor? Solo adivinando aquí.

Estoy ejecutando Thin 1.2.11 en MRI Ruby 1.9.2. (Para su información, tengo que usar la bandera --no-epoll, ya que hay a long-standing, supposedly-resolved-but-not-really problem con el uso de EventMachine de epoll y Ruby 1.9.2 Ese no es el punto, pero alguna idea es bienvenida..)

+0

El problema de epoll debe ser reparado como dice en ese ticket, esto es [el compromiso] (https://github.com/eventmachine/eventmachine/commit/d684cc3b77a6c401295a3086b5671fe4ec335a64) al que apuntan. – Bitterzoet

+0

Si elimino el distintivo --no-epoll mis solicitudes de subprocesos pasan de milisegundos a minutos. EM 0.12.10, Ruby 1.9.2-p180. Supongo que podría intentar compilar p290 ... – bioneuralnet

+0

Buena pregunta. He hecho una pregunta muy similar aquí: http://stackoverflow.com/questions/8146851/how-to-deploy-a-threadsafe-asynchronous-rails-app y he hecho algo de experimentación aquí: https: // github. com/jjb/threaded-rails-example (tenga en cuenta que aunque el subproceso enhebrado es asíncrono con éxito, es más lento) –

Respuesta

24

Nota: Yo uso delgada como sinónimo para todos servidores web que implementan la extensión de rack asíncrono (es decir, ¡Rainbows !, Ebb, versiones futuras de Puma, ...)

Q1. Correcto. Envolverá la generación de respuesta (también conocida como call) en EventMachine.defer { ... }, lo que causará que EventMachine la introduzca en su grupo de subprocesos integrado.

Q2. Usar async.callback en conjunto con EM.defer en realidad no tiene demasiado sentido, ya que básicamente también usaría el grupo de hilos, terminando con una construcción similar a la descrita en Q1. El uso de async.callback tiene sentido cuando solo se usan las librerías eventmachine para IO. Thin enviará la respuesta al cliente una vez que se llame al env['async.callback'] con una respuesta de Rack normal como argumento.

Si el cuerpo es EM::Deferrable, Thin no cerrará la conexión hasta que se produzca dicho aplazamiento. Un secreto bien guardado: si desea algo más que un sondeo largo (es decir, mantenga la conexión abierta después de enviar una respuesta parcial), también puede devolver un EM::Deferrable como objeto cuerpo directamente sin tener que usar throw :async o un código de estado de -1.

Q3. Estás adivinando correctamente. La porción roscada podría mejorar la carga en una aplicación Rack que de lo contrario no se modificará. Veo un 20% de mejora para las aplicaciones simples de Sinatra en mi máquina con Ruby 1.9.3, incluso más cuando se ejecuta en Rubinius o JRuby, donde se pueden utilizar todos los núcleos. El segundo enfoque es útil si escribe su aplicación de manera eficaz.

Puede lanzar un montón de magia y hacks sobre Rack para que una aplicación no vindicada haga uso de esos mecanismos (vea em-synchrony o sinatra-synchrony), pero eso lo dejará en la depuración y dependencia infernal .

El enfoque asincrónico tiene sentido real con aplicaciones que tienden a ser mejor resueltas con un enfoque renovado, como a web chat. Sin embargo, no recomendaría utilizar el método de subprocesamiento para implementar el sondeo largo, ya que cada conexión de sondeo bloqueará un hilo. Esto te dejará con una tonelada de hilos o conexiones con las que no podrás lidiar. El grupo de subprocesos de EM tiene un tamaño de 20 subprocesos por defecto, lo que lo limita a 20 conexiones en espera por proceso.

Puede usar un servidor que cree un nuevo hilo para cada conexión entrante, pero crear subprocesos es costoso (excepto en MacRuby, pero no usaría MacRuby en ninguna aplicación de producción). Los ejemplos son serv y net-http-server. Idealmente, lo que desea es un mapeo n: m de solicitudes e hilos. Pero no hay ningún servidor ofreciendo eso.

Si desea obtener más información sobre el tema: hice una presentación sobre esto en Rocky Mountain Ruby (y un montón de otras conferencias). Se puede encontrar una grabación de video on confreaks.

Cuestiones relacionadas