ayuda a suavizar Golang servicio HTTP actualización

Hace algún tiempo, con Golang haciendo una interfaz HTTP, debido a las características de los lenguajes compilados, modificar las necesidades de código para volver a compilar el archivo ejecutable, cerca de los viejos programas que se están ejecutando, e iniciar el nuevo programa. El acceso a una gran cantidad de productos orientados al usuario, cerrado el caso no se puede acceder en el proceso de reiniciar obligado a aparecer, lo que afecta a la experiencia del usuario.

Golang sistema mediante el desarrollo conjunto de servicios HTTP, no es capaz de soportar una actualización sin problemas (agraciadas Reiniciar), este artículo va a explorar cómo resolver el problema.

Una actualización sin problemas (agraciadas Reiniciar) de la idea general

En general, para lograr una actualización sin problemas requiere los siguientes pasos:

  1. Reemplazando el antiguo con el nuevo archivo ejecutable archivo ejecutable (como el reinicio simplemente elegante, se puede omitir este paso)
  2. Enviar una señal específica (matar -SIGUSR2 $ PID) para el viejo proceso en ejecución por pid
  3. procesos en ejecución de edad, después de recibir la señal especificada a hijo procesos manera de empezar el nuevo archivo ejecutable y comenzar a procesar nuevas solicitudes
  4. El proceso de edad ya no acepta nuevas solicitudes en espera para el servicio de proceso no se completa se ha completado, entonces el final de la normalidad
  5. Los nuevos procesos cuando el proceso sale de los padres, sería adoptar el proceso init, y seguir proporcionando servicios

Dos, la programación de red Golang Socket

Socket es un nivel programador capa de transporte de protocolo TCP encapsulación y aplicaciones / IP. Función y estructura se define en Golang zócalo correspondiente en el paquete de red, empezamos con un ejemplo sencillo de aprender acerca de la programación de la red Golang zócalo, instrucciones clave escritas directamente en los comentarios.

1, el programa servidor server.go

package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

func main() {
	// 监听8086端口
	listener, err := net.Listen("tcp", ":8086")
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()

	for {
		// 循环接收客户端的连接,没有连接时会阻塞,出错则跳出循环
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println(err)
			break
		}

		fmt.Println("[server] accept new connection.")

		// 启动一个goroutine 处理连接
		go handler(conn)
	}
}

func handler(conn net.Conn) {
	defer conn.Close()

	for {
		// 循环从连接中 读取请求内容,没有请求时会阻塞,出错则跳出循环
		request := make([]byte, 128)
		readLength, err := conn.Read(request)

		if err != nil {
			fmt.Println(err)
			break
		}

		if readLength == 0 {
			fmt.Println(err)
			break
		}

		// 控制台输出读取到的请求内容,并在请求内容前加上hello和时间后向客户端输出
		fmt.Println("[server] request from ", string(request))
		conn.Write([]byte("hello " + string(request) + ", time: " + time.Now().Format("2006-01-02 15:04:05")))
	}
}

2, el programa cliente client.go

package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"time"
)

func main() {

	// 从命令行中读取第二个参数作为名字,如果不存在第二个参数则报错退出
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s name ", os.Args[0])
		os.Exit(1)
	}
	name := os.Args[1]

	// 连接到服务端的8086端口
	conn, err := net.Dial("tcp", "127.0.0.1:8086")
	checkError(err)

	for {
		// 循环往连接中 写入名字
		_, err = conn.Write([]byte(name))
		checkError(err)

		// 循环从连接中 读取响应内容,没有响应时会阻塞
		response := make([]byte, 256)
		readLength, err := conn.Read(response)
		checkError(err)

		// 将读取响应内容输出到控制台,并sleep一秒
		if readLength > 0 {
			fmt.Println("[client] server response:", string(response))
			time.Sleep(1 * time.Second)
		}
	}
}

func checkError(err error) {
	if err != nil {
		log.Fatal("fatal error: " + err.Error())
	}
}

3, ejecutar el ejemplo

# 运行服务端程序
go run server.go

# 在另一个命令行窗口运行客户端程序
go run client.go "tabalt"

Tres, Golang HTTP programación

protocolo de capa de transporte HTTP se basa en el protocolo de capa de aplicación TCP / IP. Golang implementado en la red HTTP relacionada / http paquete, y se usó directamente como una función de estructura de red asociado paquete Socket.

Comencemos con un ejemplo sencillo de aprender sobre Golang HTTP programación, instrucciones escritas clave directamente en los comentarios.

1, http programa de servicio http.go

package main

import (
	"log"
	"net/http"
	"os"
)

// 定义http请求的处理方法
func handlerHello(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("http hello on golang\n"))
}

func main() {

	// 注册http请求的处理方法
	http.HandleFunc("/hello", handlerHello)

	// 在8086端口启动http服务,会一直阻塞执行
	err := http.ListenAndServe("localhost:8086", nil)
	if err != nil {
		log.Println(err)
	}

	// http服务因故停止后 才会输出如下内容
	log.Println("Server on 8086 stopped")
	os.Exit(0)
}

2, la ejecución del programa de ejemplo

# 运行HTTP服务程序
go run http.go

# 在另一个命令行窗口curl请求测试页面
curl http://localhost:8086/hello/

# 输出如下内容:
http hello on golang

La consecución de la red Golang / http paquetes zócalo operación

Desde el sencillo ejemplo anterior, vemos http Golang Para iniciar un servicio, sólo necesita un simple de tres pasos:

  1. método de petición de procesamiento de Http define
  2. El tratamiento de la solicitud HTTP registrada
  3. En un servicio HTTP inicio puerto

El servicio de inicio http fundamental más, llamando a la función http.ListenAndServe () para lograr. Aquí nos encontramos con la implementación de la función:

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

Aquí se crea un servidor de objetos y llamarlo método ListenAndServe (), el método de la estructura del servidor que encontraremos ListenAndServe realize ():

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

Ver desde el código, aquí está escuchando el puerto TCP y el oyente empaquetados en una estructura tcpKeepAliveListener, a continuación, llamar al método srv.Serve (); seguimos para rastrear la realización Servir () Método:

func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	var tempDelay time.Duration // how long to sleep on accept failure
	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, err := srv.newConn(rw)
		if err != nil {
			continue
		}
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve()
	}
}

Se puede ver frente a nosotros, y código de ejemplo de programación del zócalo, como el ciclismo aceptar la conexión desde el puerto de escuchar, y si se devuelve un error net.Error y esto es temporal, se va a dormir un momento para continuar. Si se devolverán otros errores para terminar el ciclo. Después de un exitoso aceptar una conexión, llame al método srv.newConn () para hacer que la capa de conexión de los envases, y, finalmente, iniciar un proceso goroutine http petición.

Cinco, Golang lograr una actualización sin problemas (agraciadas Reiniciar) en los servicios HTTP

He creado un nuevo paquete gracehttp para lograr un soporte de actualización sin problemas (agraciadas Reiniciar) HTTP servicio, con el fin de escribir menos código y reducir el costo, el nuevo paquete lo más posible el uso de net/httpdispositivos de aplicación y y net/httppaquetes de manera consistente para mantener extranjera . Ahora nos centraremos en gracehttplos soportes del paquete una actualización sin problemas (agraciadas Reiniciar) Golang HTTP servicios implicados en los detalles de la forma de lograr.

1, la señal de Golang procesado

Golang de os/signalprocesamiento de encapsulación de paquetes en la señal. Consideremos un ejemplo sencillo de uso:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {

	signalChan := make(chan os.Signal)

	// 监听指定信号
	signal.Notify(
		signalChan,
		syscall.SIGHUP,
		syscall.SIGUSR2,
	)

	// 输出当前进程的pid
	fmt.Println("pid is: ", os.Getpid())

	// 处理信号
	for {
		sig := <-signalChan
		fmt.Println("get signal: ", sig)
	}
}

2, el proceso hijo para poner en marcha un nuevo programa, el mismo puerto de escucha

Podemos ver en el código que el método de la cuarta parte de los implementos ListenAndServe (), el net/httppaquete utilizando una net.Listenfunción de escucha para un determinado puerto, pero si un programa en ejecución ha sido la escucha en un puerto, otros programas no pueden ir a escuchar a este puerto. La solución es la manera de utilizar el proceso hijo comenzado, y pasa el descriptor de fichero puerto de escucha al proceso hijo, el proceso hijo para lograr en la escucha de este puerto desde el descriptor de archivo.

Necesitar la ayuda de una variable de entorno de aplicación específica para distinguir entre el proceso normal se inicia, o se ponen en marcha los procesos secundarios, el fragmento de código correspondiente a continuación:

// 启动子进程执行新程序
func (this *Server) startNewProcess() error {

	listenerFd, err := this.listener.(*Listener).GetFd()
	if err != nil {
		return fmt.Errorf("failed to get socket file descriptor: %v", err)
	}

	path := os.Args[0]

	// 设置标识优雅重启的环境变量
	environList := []string{}
	for _, value := range os.Environ() {
		if value != GRACEFUL_ENVIRON_STRING {
			environList = append(environList, value)
		}
	}
	environList = append(environList, GRACEFUL_ENVIRON_STRING)

	execSpec := &syscall.ProcAttr{
		Env:   environList,
		Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
	}

	fork, err := syscall.ForkExec(path, os.Args, execSpec)
	if err != nil {
		return fmt.Errorf("failed to forkexec: %v", err)
	}

	this.logf("start new process success, pid %d.", fork)

	return nil
}

func (this *Server) getNetTCPListener(addr string) (*net.TCPListener, error) {

	var ln net.Listener
	var err error

	if this.isGraceful {
		file := os.NewFile(3, "")
		ln, err = net.FileListener(file)
		if err != nil {
			err = fmt.Errorf("net.FileListener error: %v", err)
			return nil, err
		}
	} else {
		ln, err = net.Listen("tcp", addr)
		if err != nil {
			err = fmt.Errorf("net.Listen error: %v", err)
			return nil, err
		}
	}
	return ln.(*net.TCPListener), nil
}

3, que espera el proceso padre para el procesamiento de solicitud de conexión se ha completado sin terminar

Éste es el más complejo; en primer lugar necesitamos un contador al éxito Aceptar un contador de conexión por un cargo por 1 cuando se cierra la conexión, el contador es 0 matriz normal puede dejar de fumar. Bolsa WaitGroup sincronización de Golang bien puede lograr esta función.

Para establecer el control y luego cerrar la conexión, tenemos que profundizar en net/httpSirva estructura del paquete de servidor () método. Lograr revivir la Parte IV Servir método (), se encuentra si desea reescribir un Serve () método es casi imposible, ya que este método en una serie buena de llamadas a métodos internos no se puede exportar, reescribir Servir () es casi reescribir todo el net/httppaquete.

Afortunadamente, también se encontró que el método de transferencia ListenAndServe () en un oyente para servir método (), y, finalmente, llamar al método accept () del oyente, que devuelve un ejemplo de un Conn de, con el tiempo desconectado cuando se llama al método de Conn Close (), las estructuras y los métodos son exportables!

Podemos definir su propia estructura y la estructura Conn oyente, la combinación net/httpestructura del paquete correspondiente, y reescribir el accept () y Cerrar () método, implementado recuento, el extracto de código relevante conectados de la siguiente manera:

type Listener struct {
	*net.TCPListener

	waitGroup *sync.WaitGroup
}

func (this *Listener) Accept() (net.Conn, error) {

	tc, err := this.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)

	this.waitGroup.Add(1)

	conn := &Connection{
		Conn:     tc,
		listener: this,
	}
	return conn, nil
}

func (this *Listener) Wait() {
	this.waitGroup.Wait()
}

type Connection struct {
	net.Conn
	listener *Listener

	closed bool
}

func (this *Connection) Close() error {

	if !this.closed {
		this.closed = true
		this.listener.waitGroup.Done()
	}

	return this.Conn.Close()
}

Uso 4, paquete de gracehttp

gracehttp paquete se ha aplicado a cientos de millones de personas cada proyecto fotovoltaico día, también está abierto a la GitHub: github.com/tabalt/gracehttp , muy sencillo de utilizar.

El siguiente código de ejemplo, después de la introducción del paquete puede modificar una palabra clave, el http.ListenAndServe gracehttp.ListenAndServe se puede cambiar.

package main

import (
    "fmt"
    "net/http"

    "github.com/tabalt/gracehttp"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello world")
    })

    err := gracehttp.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println(err)
    }
}

Prueba suave de actualización (Graceful Restart) efecto puede ser descrito con referencia a la página siguiente:
https://github.com/tabalt/gracehttp#demo

Publicados 158 artículos originales · ganado elogios 119 · vistas 810 000 +

Supongo que te gusta

Origin blog.csdn.net/u013474436/article/details/104761967
Recomendado
Clasificación