2011-11-16 9 views
8

He leído toneladas de material en la web sobre la seguridad y el rendimiento de los hilos en diferentes versiones de ruby ​​y rieles, y creo que entiendo muy bien estas cosas en este momento.¿Cómo implementar una aplicación Rails asíncrona segura para hilos?

Lo que parece extrañamente falta en las discusiones es cómo implementar realmente una aplicación de Rails asíncrona. Cuando se habla de roscas y sincronicidad en una aplicación, hay dos cosas que la gente quiere optimizar:

  1. utilizando todos los núcleos de la CPU con un uso mínimo de memoria RAM
  2. ser capaz de servir a las nuevas peticiones mientras que las solicitudes anteriores están a la espera de IO

El punto 1 es donde la gente se emociona (correctamente) con JRuby. Para esta pregunta sólo estoy tratando de optimizar el punto 2.

decir que este es el único controlador en mi aplicación:

TheController < ActionController::Base 
    def fast 
    render :text => "hello" 
    end 

    def slow 
    render :text => User.count.to_s 
    end 
end 

fast no tiene IO y se puede servir a cientos o miles de peticiones por segundo, y slow tiene que enviar una solicitud a través de la red, esperar a que se realice el trabajo, luego recibir la respuesta a través de la red y, por lo tanto, es mucho más lenta que fast.

Así que una implementación ideal permitiría que se cumplan cientos de solicitudes a fast mientras una solicitud a slow está esperando en IO.

Lo que parece faltar en las discusiones en la web es qué capa de la pila es responsable de habilitar esta concurrencia. thin tiene un indicador --threaded, que "Llamará a la aplicación Rack en hilos [experimentales]" - ¿eso inicia un nuevo hilo para cada solicitud entrante? ¿Enrollar las instancias de la aplicación en rack en los hilos que persisten y esperar las solicitudes entrantes?

¿Es delgada la única manera o hay otras? ¿Importa el tiempo de ejecución de ruby ​​para optimizar el punto 2?

+0

Dudo que tu versión de rubí tenga mucho que decir con respecto al punto 2. Esto dependería más de la implementación de simultaneidad del servidor y de cómo se ensamblan los rieles. – providence

Respuesta

6

El enfoque correcto para usted depende en gran medida de lo que esté haciendo su método slow.

En un mundo perfecto, puede utilizar algo como la gema sinatra-synchrony para manejar cada solicitud en una fibra. Solo estarás limitado por la cantidad máxima de fibras. Desafortunadamente, el tamaño de la pila en las fibras es hardcoded, y es fácil de desbordar en una aplicación de Rails. Además, he leído algunas historias de terror sobre las dificultades de depurar fibras, debido a la cedencia automática después de que se haya iniciado la asincronía IO. Las condiciones de carrera todavía son posibles cuando se usan fibras, también. En la actualidad, la fibra de Ruby es un poco un gueto, al menos en el front-end de una aplicación web.

Una solución más pragmática que no requiere cambios de código es utilizar un servidor Rack que tenga un conjunto de hilos de trabajo como Rainbows. o Puma. Creo que la bandera --threaded de Thin maneja cada solicitud en un nuevo hilo, pero hacer girar un hilo del sistema operativo nativo no es barato. Es mejor utilizar un grupo de subprocesos con el tamaño de grupo establecido suficientemente alto. En Rails, no olvide configurar config.threadsafe! en producción.

Si está de acuerdo con el cambio de código, puede consultar el excelente talk on real-time Rack de Konstantin Haase. Analiza el uso de la clase EventMachine::Deferrable para producir una respuesta fuera del ciclo de solicitud/respuesta tradicional en el que se basa Rack. Esto parece realmente limpio, pero tienes que volver a escribir el código en un estilo asíncrono.

También eche un vistazo a Cramp y Goliath. Estos le permiten implementar su método slow en una aplicación Rack separada que se aloja junto con su aplicación Rails, pero probablemente también tendrá que volver a escribir su código para trabajar en los controladores Cramp/Goliath.

En cuanto a su pregunta sobre el tiempo de ejecución de Ruby, también depende del trabajo que slow está haciendo. Si realiza un cálculo pesado de la CPU, corre el riesgo de que el GIL le dé problemas. Si está haciendo IO, entonces el GIL no debería entrar en su camino con. (Digo que no debería porque creo que he leído sobre problemas con la antigua gema mysql que bloquea el GIL).

Personalmente, he tenido éxito al usar sinatra-synchrony para un servicio web back-end y mashup. Puedo emitir varias solicitudes a servicios web externos en paralelo y esperar a que todos vuelvan. Mientras tanto, el servidor frontend Rails usa un grupo de subprocesos y realiza solicitudes directamente al servidor. No es perfecto, pero funciona bastante bien en este momento.

Cuestiones relacionadas