진 서버 종료 시도 중...(고루틴 및 채널 팁)

오늘 저는 응용 프로그램을 다시 실행하지 않고 내 진 서버를 정상적으로 종료하고 응용 프로그램 자체를 통해 다시 시작하려고 했습니다.
여러분과 나누고 싶은 소중한 것들을 배웠습니다.

그래서, 무엇이 문제입니까?


gin.Run() 또는 http.ListenAndServe() 를 사용하여 서버를 실행하면 메인 프로세스에 있는 경우 고루틴을 차단하므로 메인 고루틴을 차단합니다.

func main() {
  ginApp := gin.Default()
  ginApp.Run(":80") // This code is blocking
  // next lines will never run because of blocking code.
  FuncForStoppingGinServer() // This function can't be used.
}


다른 고루틴 사용



우리는 단순히 다른 고루틴을 차단하고 메인 고루틴이 자유롭게 유지되도록 도울 수 있습니다.

func main() {
  ginApp := gin.Default()
  go ginApp.Run(":80") // This code is not blocking anymore.
  FuncForStoppingGinServer() // This function will run now.
}


그런데, 안에서는 어떻게 해야 할까요FuncForStoppingGinServer() ??

아무것도 아님!
대답은 gin 자체에는 서버를 종료하는 기능이 없다는 것입니다!
그러나 그것에 들어가기 전에 다른 잘못된 방법을 시도해 봅시다!!

그냥 죽여!



고루틴과 채널을 사용하여 진 서버를 중지하는 것에 대해 생각하고 있습니까? (지금은 활성 연결 처리를 잊어 버리십시오)

var ginApp *gin.Engine

func main() {
  // Make an engine
  ginApp = gin.Default()

  // Make a stop channel
  stopChan := make(chan struct{})

  go func() {
    ginApp.Run(":80") // blocking process
    fmt.Println(">> 1 <<")
    for range stopChan {
      return
    }
  }()
  fmt.Println(">> 2 <<")
  // terminate gin server after 3 seconds
  time.Sleep(time.Second * 3)
  stopChan <- struct{}{}
  fmt.Println(">> 3 <<")
}


이것은 좋은 접근 방식으로 들리지만 여기서 무엇이 잘못되었다고 생각하십니까?

어떤 숫자가 인쇄됩니까? (1, 2, 3)?

앞에서 언급했듯이 ginApp.Run(":80")는 차단 프로세스이며 고 루틴을 차단하므로 채널 stopChan로 보냅니다.
하지만,
우리는 stopChan 를 사용하여 for range loop 에서 읽을 수 없을 것입니다. 이는 차단 프로세스 후에 나왔고 ">> 1 <<"가 인쇄되지 않기 때문입니다.

그리고 우리 채널이 버퍼링되지 않았기 때문에 메인 고루틴인 발신자도 ">> 3 <<"를 인쇄하기 바로 전에 누군가가 그것을 읽을 때까지 차단될 것이며 역시 인쇄되지 않을 것입니다.

">> 2 <<"는 어쨌든 인쇄됩니다 😄

추가 재미있는 사실: 채널을 버퍼링하면(이 경우에는 크기 1이면 충분함) 메인 고루틴이 여기로 전송하고 진행한 다음 메인 고루틴 작업이 완료될 때마다 작업을 완료하고 닫힙니다. 전체 프로그램이 존재합니다. ! 다른 goroutine은 그것과 함께 죽을 것입니다. 이렇게 변경하고 결과를 확인하십시오.

stopChan := make(chan struct{}, 1)


또한 chan struct{}는 채널 유형이고 빈 구조체struct{}{}는 메모리를 전혀 소비하지 않으며 신호 전용 채널로 알려져 있습니다.

나는 마침내 이런 식으로 이 문제를 해결할 방법이 없다는 것을 깨달았습니다.
Is there a way to stop a long blocking function?

이 질문에서 고루틴 중지에 대해 자세히 알아볼 수도 있습니다. How to stop a goroutine
그리고 나중에 더 많은 예를 다루겠습니다. (저를 팔로우하시면 주목받을 수 있습니다)

다시 문제로!



따라서 고루틴을 사용해도 문제가 해결되지 않았습니다.http.Server.Shutdown() 방법을 시도해보는 것은 어떨까요?

var ginApp *gin.Engine

func main() {
  // Make an engine
  ginApp = gin.Default()

  // Make a http server
  httpServer := &http.Server{
    Addr:    ":80",
    Handler: ginApp,
  }

  // Launch http server in a separate goroutine
  go httpServer.ListenAndServe()

  // Stop the server
  time.Sleep(time.Second * 5)
  fmt.Println("We're going to stop gin server")
  httpServer.Shutdown(context.Background())
}


이 질문을 읽는 것이 도움이 될 수 있습니다: Graceful stop of gin server

작동하지만 여전히 잘못된 점은 무엇입니까?



이제 프로그램 내에서 서버를 종료하고 있지만 어떻게 다시 시작할 수 있습니까?ListenAndServe()를 다시 실행하면 될까요?

내가 이 질문에서 물은 것처럼: How to stop and start gin server multiple times in one run
이 세미 코드와 같은 것을 구현하려고 합니다.

srv := NewServer()
srv.Start()
srv.Stop()
srv.Start()
srv.Stop()
srv.Start()


약간의 리팩토링으로 해봅시다.

type Server struct {
  httpServer *http.Server
}

func main() {
  srv := NewHTTPServer()
  srv.Start()
  time.Sleep(time.Second * 2)
  srv.Stop()
  time.Sleep(time.Second * 2)
  srv.Start()

  select {} // Simulate other processes
}

func NewHTTPServer() *Server {
  return &Server{
    httpServer: &http.Server{
      Addr:    ":80",
      Handler: gin.Default(),
    },
  }
}

func (s *Server) Start() {
  go func() {
    if err := s.httpServer.ListenAndServe(); err != nil {
      fmt.Println(">>>", err)
    }
  }()
}

func (s *Server) Stop() {
  s.httpServer.Shutdown(context.Background())
}


이 코드를 실행하고 무슨 일이 일어나는지 보십시오!
산출:

...Gin logs...
>>> http: Server closed
>>> http: Server closed


뭐? 우리는 srv.Stop()을 한 번만 호출했지만 서버가 두 번 닫혔습니다!
왜요?
.Shutdown() 함수 문서에 따르면:

Once Shutdown has been called on a server, it may not be reused; future calls to methods such as Serve will return ErrServerClosed.



따라서 공식적으로 작동하지 않습니다!

그러나 여전히 한 가지 작은 변화가 있습니다. 서버를 시작할 때마다 서버 구조를 처음부터 다시 만들 수 있습니다.

마지막 코드에 서브 기능을 추가하고 메인 기능을 변경하면 다른 코드는 동일합니다.

func main() {
  srv := serve()
  time.Sleep(time.Second * 2)
  srv.Stop()
  time.Sleep(time.Second * 2)
  srv = serve()
  srv.Start()

  select {} // Simulate other processes
}

func serve() *Server {
  srv := NewHTTPServer()
  // Register handlers or other stuff
  srv.Start()
  return srv
}


이것은 우리가 원하는 것이지만 코드를 더 깔끔하게 만들 수 있습니다.

기타 솔루션



내 경우에는 Fiber를 대신 사용할 수 있었고 내 프로젝트에 훨씬 더 잘 맞지만 원하는 솔루션이 아닐 수도 있습니다.
또한 이 페이지를 볼 수 있습니다: Graceful restart or stop

최종 견적



이 기사는 나 자신에 대해 다른 느낌을 가졌습니다. 몇 가지 팁과 작동하지 않는 접근 방식이 있는 위아래 경로였습니다. 여러분도 같은 느낌이기를 바라며 여러분의 생각을 듣게 되어 기쁩니다.

좋은 웹페이지 즐겨찾기