http go scale net-http

http - Gestión de procesos para el servidor web Go



scale net-http (2)

Ajustar / configurar el servidor HTTP

El tipo que implementa el servidor HTTP es http.Server . Si no crea un http.Server usted mismo, por ejemplo, porque llama a la función http.ListenAndServe() , eso crea un http.Server bajo el capó para usted:

func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }

Entonces, si desea modificar / personalizar el servidor HTTP, cree uno usted mismo y llame a su método Server.ListenAndServe() usted mismo. http.Server es una estructura, su valor cero es una configuración válida. Vea en su documento qué campos tiene y qué puede ajustar / configurar.

La "Gestión de procesos" del servidor HTTP se documenta en Server.Serve() :

Serve acepta conexiones entrantes en el Listener l, creando una nueva rutina de servicio para cada uno . El servicio goroutines lee las solicitudes y luego llama a srv.Handler para responderlas. Servir siempre devuelve un error no nulo.

Por lo tanto, cada solicitud HTTP entrante se maneja en su nueva rutina, lo que significa que se atienden simultáneamente. Desafortunadamente, la API no documenta ninguna forma de saltar y cambiar cómo funciona esto.

Y mirando la implementación actual (Ir 1.6.2), tampoco hay una forma indocumentada de hacerlo. server.go , actualmente línea # 2107-2139 :

2107 func (srv *Server) Serve(l net.Listener) error { 2108 defer l.Close() 2109 if fn := testHookServerServe; fn != nil { 2110 fn(srv, l) 2111 } 2112 var tempDelay time.Duration // how long to sleep on accept failure 2113 if err := srv.setupHTTP2(); err != nil { 2114 return err 2115 } 2116 for { 2117 rw, e := l.Accept() 2118 if e != nil { 2119 if ne, ok := e.(net.Error); ok && ne.Temporary() { 2120 if tempDelay == 0 { 2121 tempDelay = 5 * time.Millisecond 2122 } else { 2123 tempDelay *= 2 2124 } 2125 if max := 1 * time.Second; tempDelay > max { 2126 tempDelay = max 2127 } 2128 srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) 2129 time.Sleep(tempDelay) 2130 continue 2131 } 2132 return e 2133 } 2134 tempDelay = 0 2135 c := srv.newConn(rw) 2136 c.setState(c.rwc, StateNew) // before Serve can return 2137 go c.serve() 2138 } 2139 }

Como puede ver en la línea # 2137, la conexión se sirve incondicionalmente en una nueva rutina, por lo que no hay nada que pueda hacer al respecto.

Limitando las gorutinas "obreras"

Si desea limitar el número de goroutines que atienden solicitudes, aún puede hacerlo.

Puede limitarlos en múltiples niveles. Para limitar el nivel del oyente, vea la respuesta de Darigaaz. Para limitar el nivel del controlador, sigue leyendo.

Por ejemplo, podría insertar un código en cada una de sus funciones de controlador o manejador http.HandlerFunc ( http.HandlerFunc ) que solo continúa si el número de solicitudes concurrentes que atienden goroutines es inferior a un límite especificado.

Existen numerosas construcciones para dicho código de sincronización limitante. Un ejemplo podría ser: crear un canal protegido con capacidad, siendo su límite deseado. Cada controlador debe enviar primero un valor en este canal y luego hacer el trabajo. Cuando el controlador regresa, debe recibir un valor del canal: por lo tanto, es mejor hacerlo en una función diferida (sin olvidar "limpiarse").

Si el búfer está lleno, se bloqueará una nueva solicitud que intente enviar en el canal: espere hasta que una solicitud finalice su trabajo.

Tenga en cuenta que no tiene que inyectar este código de limitación a todos sus controladores, puede usar un patrón de "middleware", un nuevo tipo de controlador que envuelve sus controladores, realiza este trabajo de sincronización limitante y llama al controlador envuelto en el medio de eso.

La ventaja de limitar en el controlador (en lugar de limitar en los oyentes) es que en el controlador sabemos lo que hace el controlador, por lo que podemos hacer una limitación selectiva (por ejemplo, podemos elegir limitar ciertas solicitudes, como las operaciones de la base de datos, y no limitar otros como servir recursos estáticos) o podemos crear múltiples grupos de límites distintos arbitrariamente a nuestras necesidades (por ejemplo, limitar las solicitudes de base de datos concurrentes a 10 máx., limitar las solicitudes estáticas a 100 máx., limitar las solicitudes computacionales pesadas a 3 máx.) etc. También podemos advierta fácilmente limitaciones como ilimitado (o límite alto) para usuarios que inician sesión / que pagan, y límite bajo para usuarios anónimos / que no pagan.

También tenga en cuenta que incluso puede hacer la limitación de velocidad en un solo lugar, sin usar middlewares. Cree un "controlador principal" y páselo a http.ListenAndServe() (o Server.ListenAndServe() ). En este controlador principal, limite la velocidad (por ejemplo, utilizando un canal protegido como se mencionó anteriormente) y simplemente reenvíe la llamada al http.ServeMux que está utilizando.

Aquí hay un ejemplo simple que usa http.ListenAndServe() y el multiplexor predeterminado del paquete http ( http.DefaultServeMux ) para la demostración. Limita las solicitudes concurrentes a 2:

func fooHandler(w http.ResponseWriter, r *http.Request) { log.Println("Foo called...") time.Sleep(3 * time.Second) w.Write([]byte("I''m Foo")) log.Println("Foo ended.") } func barHandler(w http.ResponseWriter, r *http.Request) { log.Println("Bar called...") time.Sleep(3 * time.Second) w.Write([]byte("I''m Bar")) log.Println("Bar ended.") } var ch = make(chan struct{}, 2) // 2 concurrent requests func mainHandler(w http.ResponseWriter, r *http.Request) { ch <- struct{}{} defer func() { <-ch }() http.DefaultServeMux.ServeHTTP(w, r) } func main() { http.HandleFunc("/foo", fooHandler) http.HandleFunc("/bar", barHandler) panic(http.ListenAndServe(":8080", http.HandlerFunc(mainHandler))) }

Despliegue

Las aplicaciones web escritas en Go no requieren servidores externos para controlar los procesos, ya que el propio servidor web Go maneja las solicitudes simultáneamente.

Por lo tanto, puede iniciar su servidor web escrito en Go as-is: el servidor web Go está listo para la producción.

Por supuesto, puede usar otros servidores para tareas adicionales si lo desea (por ejemplo, manejo de HTTPS, autenticación / autorización, enrutamiento, equilibrio de carga entre varios servidores).

Soy un nuevo programador de Go, proveniente del mundo del desarrollo de aplicaciones y servicios web. Disculpas, esta es una pregunta herp de-derp, pero mi búsqueda en Google no ha encontrado nada. Además, este es un territorio límite de Fallas del servidor, pero como estoy más interesado en las API / interfaces programáticas, pregunto aquí.

He escrito un pequeño programa go usando el servidor web incorporado del paquete net/http . Me estoy preparando para implementar en producción, pero no estoy muy claro sobre el proceso del servidor web de model go Go y cómo debo implementarlo.

Específicamente: en los entornos a los que estoy acostumbrado (PHP, Ruby, Python) tenemos un servidor web (Apache, Nginx, etc.) ubicado frente a nuestra aplicación, y configuramos estos servidores web para usar un cierto número de trabajador procesa / subprocesos y configura cuántas conexiones HTTP (S) individuales debe procesar cada subproceso.

No he podido encontrar información sobre cómo el servidor web de Go maneja esto, o información práctica sobre cómo escalar / planificar para escalar un servidor web Go.

es decir, si tengo un programa simple ejecutándose, listo para manejar una solicitud HTTP

func main() { http.HandleFunc("/", processRequest) http.ListenAndServe(":8000", nil) }

¿Cuántas conexiones intentará manejar HandleFunc a la vez? ¿O comenzará a bloquearse cuando se abra una conexión y solo servirá la próxima conexión una vez que se cierre?

¿O simplemente no debería preocuparme por esto y meter todo en una rutina? Pero si hago eso, ¿cómo evito que el sistema se atasque con demasiados hilos de ejecución?

Básicamente estoy tratando de

  1. Comprender el modo de proceso del servidor web go
  2. Encuentre las características integradas de go para ajustar esto, y / o cualquier paquete estándar que la gente esté usando

Como dije, soy muy nuevo para ir, así que si me estoy perdiendo completamente la trama, ¡házmelo saber!


ListenAndServe inicia un servidor HTTP con una dirección y un controlador dados. El controlador suele ser nulo, lo que significa utilizar DefaultServeMux . Handle y HandleFunc agregan controladores a DefaultServeMux .

Mire http.Server , muchos campos son opcionales y funcionan bien con valores predeterminados.

Ahora veamos http.ListenAndServe , no es nada difícil

func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }

por lo que el servidor predeterminado es muy simple de crear.

func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) } func (srv *Server) Serve(l net.Listener) error { defer l.Close() if fn := testHookServerServe; fn != nil { fn(srv, l) } var tempDelay time.Duration // how long to sleep on accept failure if err := srv.setupHTTP2(); err != nil { return err } for { rw, e := l.Accept() if e != nil { if ne, ok := e.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } return e } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve() } }

Escucha en "addr" y acepta cada conexión, luego genera una rutina para manejar cada conexión de forma independiente. (HTTP / 2.0 es un poco diferente, pero es lo mismo en general).

Si desea controlar las conexiones, tiene 2 opciones:

  1. Cree un servidor personalizado (sus 3 líneas de código) con el servidor. ConnState callback y control de conexiones de clientes desde allí. (pero serán aceptados por el núcleo de todos modos)

  2. Cree un servidor personalizado con su propia implementación de net.Listener (como LimitedListener ) y controle las conexiones desde allí, de esta manera tendrá el máximo poder sobre las conexiones.

Como el http.Server predeterminado no tiene forma de detenerse, la segunda forma es la única manera de terminar con gracia el oyente. Puede combinar dos métodos para implementar diferentes estrategias, bueno, ya se ha hecho.