введение
- Зачем перезапускать и останавливать изящно
- В чем разница между перезапуском и остановкой, и как это можно считать элегантным
- Что такое семафор и какие бывают
- Как добиться плавного перезапуска и остановки
- кодgithub
1. Почему?
На этапе разработки, когда мы модифицируем код или конфигурацию, мы сразу нажмем ctrl + c, а затем запустим службу.Если то же самое верно в производственной среде, какие проблемы это вызовет?
- запрос потерян
- Поведение пользователя прерывается
- ты можешь потерять работу
Чтобы избежать этой ситуации, мы надеемся, что при обновлении или выпуске приложения существующее приложение, обрабатывающее существующее соединение, не будет прервано, а существующее соединение должно быть обработано перед выходом. Недавно выпущенное приложение начинает получать и обрабатывать новые запросы после его развертывания, чтобы избежать проблемы прерывания исходного обрабатываемого соединения.
2. В чем разница между перезапуском и остановкой?
- Перезапуск означает, что при обновлении приложения не ожидается, что обрабатываемое соединение будет отключено, в то же время должен быть новый процесс, чтобы принять новое приложение и принять новые запросы.
- Остановить означает, что когда процесс приложения закрывается, существующее соединение может быть обработано и закрыто, и оно перестает принимать новые соединения.
- Так называемая элегантность, суть в том, что связь нельзя потерять, а связь, которая должна быть обработана, должна быть обработана.
3. Определение семафора
Сигналы — это ограниченное средство межпроцессного взаимодействия в Unix, Unix-подобных и других операционных системах, совместимых с POSIX.
- общий семафор
Заказ | Сигнал | описывать |
---|---|---|
ctrl + c | SIGINT | Принудительно завершить процесс |
ctrl + z | SIGTSTP | Задача прервана, процесс завис |
ctrl + \ | SIGQUIT | конец процесса и дамп ядра |
- все семафоры
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
4. Как реализовать
достигнутые цели
- Существующие соединения (работающие программы) не закрываются.
- Новый процесс запускается и заменяет старый.
- Новый процесс принимает новое соединение.
- Соединение всегда должно отвечать на запрос пользователя. Чтобы сохранить соединение, когда пользователь все еще запрашивает старый процесс, новый пользователь должен запросить новый процесс, и запрос не может быть отклонен.
Принцип реализации
- Слушайте сигнал SIGHUP;
- Передать файловый дескриптор, отслеживаемый службой, новому дочернему процессу после получения сигнала;
- В это время новый и старый процессы получают запросы одновременно;
- Родительский процесс перестает получать новые запросы и ждет завершения старых запросов (или истечения времени ожидания);
- Родительский процесс завершается.
Используйте сторонние компоненты "github.com/fvbock/endless"
import ("github.com/fvbock/endless")
r := InitRouter()
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", config.ServerSetting.HttpAddress, config.ServerSetting.HttpPort),
Handler: r,
ReadTimeout: config.ServerSetting.ReadTimeout, //请求响应的超市时间
WriteTimeout: config.ServerSetting.WriteTimeout, //返回响应的超时时间
//MaxHeaderBytes: 1 << 20,//默认的1MB
}
endless.ListenAndServe()
5. Компоненты горячего перезапуска собственной разработки
основной процесс
func serve() {
ListenAndServe() //监听并启动
核心运行函数
getNetListener() // 1. 获取监听 listener
Serve() // 2. 用获取到的 listener 开启 server 服务
handleSignals() // 3. 监听外部信号,用来控制程序 fork 还是 shutdown
}
package hotstart
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
)
const (
LISTENER_FD = 3
DEFAULT_READ_TIMEOUT = 60 * time.Second
DEFAULT_WRITE_TIMEOUT = DEFAULT_READ_TIMEOUT
)
var (
runMutex = sync.RWMutex{}
)
// HTTP server that supported hotstart shutdown or restart
type HotServer struct {
*http.Server
listener net.Listener
isChild bool
signalChan chan os.Signal
shutdownChan chan bool
BeforeBegin func(addr string)
}
func ListenAndServer(server *http.Server) error {
return NewHotServer(server).ListenAndServe()
}
func ListenAndServe(addr string, handler http.Handler) error {
return NewServer(addr, handler, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT).ListenAndServe()
}
/*
new HotServer
*/
func NewHotServer(server *http.Server) (srv *HotServer) {
runMutex.Lock()
defer runMutex.Unlock()
isChild := os.Getenv("HOT_CONTINUE") != ""
srv = &HotServer{
Server: server,
isChild: isChild,
signalChan: make(chan os.Signal),
shutdownChan: make(chan bool),
}
//服务启动之前钩子,命令行输出pid
srv.BeforeBegin = func(addr string) {
srv.logf(addr)
}
return
}
/*
new HotServer
*/
func NewServer(addr string, handler http.Handler, readTimeout, writeTimeout time.Duration) *HotServer {
Server := &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
}
return NewHotServer(Server)
}
/*
Listen http server
*/
func (srv *HotServer) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := srv.getNetListener(addr)
if err != nil {
return err
}
srv.listener = ln
if srv.isChild {
//通知父进程不接受请求
syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
}
srv.BeforeBegin(srv.Addr)
return srv.Serve()
}
/*
监听 https server
*/
func (srv *HotServer) ListenAndServeTLS(certFile, keyFile string) error {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
config := &tls.Config{}
if srv.TLSConfig != nil {
*config = *srv.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
ln, err := srv.getNetListener(addr)
if err != nil {
return err
}
srv.listener = tls.NewListener(ln, config)
if srv.isChild {
syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
}
srv.BeforeBegin(srv.Addr)
return srv.Serve()
}
/*
服务启动
*/
func (srv *HotServer) Serve() error {
//监听信号
go srv.handleSignals()
err := srv.Server.Serve(srv.listener)
srv.logf("waiting for connections closed.")
//阻塞等待关闭
<-srv.shutdownChan
srv.logf("all connections closed.")
return err
}
/*
get lister
*/
func (srv *HotServer) getNetListener(addr string) (ln net.Listener, err error) {
if srv.isChild {
file := os.NewFile(LISTENER_FD, "")
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, nil
}
/*
监听信号
*/
func (srv *HotServer) handleSignals() {
var sig os.Signal
signal.Notify(
srv.signalChan,
syscall.SIGTERM,
syscall.SIGUSR2,
)
for {
sig = <-srv.signalChan
switch sig {
case syscall.SIGTERM:
srv.logf("received SIGTERM, hotstart shutting down HTTP server.")
srv.shutdown()
case syscall.SIGUSR2:
srv.logf("received SIGUSR2, hotstart restarting HTTP server.")
if err := srv.fork(); err != nil {
log.Println("Fork err:", err)
}
default:
}
}
}
/*
优雅关闭后台
*/
func (srv *HotServer) shutdown() {
if err := srv.Shutdown(context.Background()); err != nil {
srv.logf("HTTP server shutdown error: %v", err)
} else {
srv.logf("HTTP server shutdown success.")
srv.shutdownChan <- true
}
}
// start new process to handle HTTP Connection
func (srv *HotServer) fork() (err error) {
listener, err := srv.getTCPListenerFile()
if err != nil {
return fmt.Errorf("failed to get socket file descriptor: %v", err)
}
// set hotstart restart env flag
env := append(
os.Environ(),
"HOT_CONTINUE=1",
)
execSpec := &syscall.ProcAttr{
Env: env,
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listener.Fd()},
}
_, err = syscall.ForkExec(os.Args[0], os.Args, execSpec)
if err != nil {
return fmt.Errorf("Restart: Failed to launch, error: %v", err)
}
return
}
/*
获取TCP监听文件
*/
func (srv *HotServer) getTCPListenerFile() (*os.File, error) {
file, err := srv.listener.(*net.TCPListener).File()
if err != nil {
return file, err
}
return file, nil
}
/*
格式化输出Log
*/
func (srv *HotServer) logf(format string, args ...interface{}) {
pids := strconv.Itoa(os.Getpid())
format = "[pid " + pids + "] " + format
log.Printf(format, args...)
}
demo адрес
func hello(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Second)
w.Write([]byte("hello world233333!!!!"))
}
func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test 22222"))
}
func main() {
http.HandleFunc("/hello", hello)
http.HandleFunc("/test", test)
pid := os.Getpid()
address := ":9999"
s := &http.Server{
Addr: address,
Handler: nil,
}
err := Hot.ListenAndServer(s)
log.Printf("process with pid %d stoped, error: %s.\n", pid, err)
}
6. Скрипт
перезапустить (согласно сценарию запуска проекта)
ps aux | grep "gintest" | grep -v grep | awk '{print $2}' | xargs -i kill -SIGUSR2 {}
stop
ps aux | grep "gintest" | grep -v grep | awk '{print $2}' | xargs -i kill -SIGTERM {}
перезапуск порта
ps aux | lsof -i:8080 | grep -v grep | awk '{print $1}' | xargs -i kill -1 {}
портовая остановка
ps aux | lsof -i:8080 | grep -v grep | awk '{print $1}' | xargs -i kill {}
7. Резюме
В ежедневном http-сервисе изящный перезапуск (горячее обновление) является очень важной частью.В настоящее время у Golang есть много решений, мы можем выбирать в зависимости от ситуации
8. Рекомендуемый случай
Другая моя собственная структура добавила горячий запуск для поддержки компиляции в нескольких средах,адрес
9. Цикл статей
- Сериализация среды golang для сборки
- Серийная вторая установка Джин
- Сериализация 3. Определите структуру каталогов
- Строительный кейс серии 4 API1
- Строительный кейс Series 5 API2
- Документация по интерфейсу Serial 6 Access swagger
- Серийный компонент семи бревен
- Изящный перезапуск и остановка Serial Eight
- Серийная сборка Fanwai Makefile
- Серийные запланированные задачи Fanwai Cron
- Serial Fanwai Создание инструмента командной строки
- Сериализация Fanwai 3 дня на создание эксклюзивного кэша (первый день)
- 3 дня сериализации для создания эксклюзивного кэша (второй день)
- 3 дня сериализации для создания эксклюзивного кэша (третий день)