| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- Vue
- method
- localStorage
- CDN
- Server
- TODO
- emit
- URL
- go
- channel
- graceful shutdown
- toggle
- golang
- Dictionary
- map
- goroutines
- PROPS
- cli
- todo-list
- 행렬
- websocket
- reactivity
- component
- Vue.js
- SFC
- goroutine
- container
- App.vue
- Matrix
- Refactoring
Archives
- Today
- Total
ksundev 님의 블로그
Go 서버에서 Graceful Shutdown 구현하기 본문
기본 구현 방법
먼저 코드부터 살펴보겠습니다:
func main() {
app := fiber.New()
// 라우트 설정
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
// 서버를 goroutine으로 실행
go func() {
if err := app.Listen(":3000"); err != nil {
log.Panic(err)
}
}()
// OS 신호 대기
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c // 종료 신호 받을 때까지 대기
log.Println("Gracefully shutting down...")
app.Shutdown()
log.Println("Server shut down successfully")
}
핵심은 서버를 goroutine 안에서 실행하는 것입니다.
왜 Goroutine이 필요한가?
핵심 이유
app.Listen()은 blocking 함수입니다. 서버가 종료될 때까지 다음 코드로 진행하지 않기 때문에:
- ❌ goroutine 없이 실행 → signal을 받을 코드에 도달하지 못함
- ✅ goroutine으로 실행 → 메인 스레드가 signal을 기다리다가 종료 신호를 받으면 graceful shutdown 수행 가능
전체 코드
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
// 라우트 설정
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
// 서버를 goroutine으로 실행
go func() {
if err := app.Listen(":3000"); err != nil {
log.Panic(err)
}
}()
// OS 신호 대기
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c // 종료 신호 받을 때까지 대기
log.Println("Gracefully shutting down...")
// Fiber 서버 종료
if err := app.Shutdown(); err != nil {
log.Fatal(err)
}
log.Println("Server shut down successfully")
}
동작 원리
Goroutine 없이 실행할 경우
func main() {
app := fiber.New()
app.Listen(":3000") // 여기서 계속 blocking
// 아래 코드는 절대 실행되지 않음
signal.Notify(...)
<-c
}
서버가 계속 실행되면서 blocking되기 때문에 signal을 받을 코드에 도달할 수 없습니다.
Goroutine 사용할 경우
func main() {
app := fiber.New()
// 1. 이 goroutine은 백그라운드에서 계속 실행됨
go func() {
app.Listen(":3000") // 여기서 blocking되지만 goroutine 안이라 괜찮음
}()
// 2. main 함수는 여기로 바로 넘어옴
// 3. 여기서 blocking - 신호를 받을 때까지 대기
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c // 이 코드가 없으면 main이 바로 끝나버려서 goroutine도 같이 종료됨
// 4. 신호를 받으면 여기부터 실행
app.Shutdown()
// 5. main 함수 종료 → 프로그램 종료
}
중요: <-c 같은 blocking 코드가 없으면 main 함수가 바로 끝나버려서 goroutine도 같이 강제 종료됩니다. 그래서 채널로 신호를 기다리면서 main을 살려두는 것입니다.
Hexagonal Architecture에서의 구현
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/gofiber/fiber/v2"
"yourapp/internal/adapter/handler"
"yourapp/internal/adapter/repository"
"yourapp/internal/application/service"
)
func main() {
// 의존성 주입
db := initDB()
defer db.Close()
// Repository 계층
chatRepo := repository.NewChatRepository(db)
// Service 계층
chatService := service.NewChatService(chatRepo)
// Handler 계층
chatHandler := handler.NewChatHandler(chatService)
// Fiber 앱 생성 및 라우트 등록
app := fiber.New()
chatHandler.RegisterRoutes(app)
// 서버 시작 (goroutine)
go func() {
if err := app.Listen(":3000"); err != nil {
log.Panic(err)
}
}()
// Graceful shutdown 대기
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// 서버 종료
if err := app.Shutdown(); err != nil {
log.Fatal(err)
}
// 추가 정리 작업 (DB 연결 등)
// defer로 이미 처리되지만, 명시적으로 추가 가능
log.Println("Server exited properly")
}
func initDB() *sql.DB {
// MySQL 연결 초기화
// ...
return db
}
테스트 방법
- 서버 실행:
go run main.go - 종료 신호 전송:
Ctrl + C또는kill -SIGTERM <PID> - 로그 확인: "Gracefully shutting down..." → "Server shut down successfully"
마무리
원래 서버는 반복문처럼 계속 실행되다가 종료되는 구조였다면, graceful shutdown에서는:
- 서버를 goroutine으로 백그라운드 실행
- main 함수는 신호를 기다림
- 종료 신호를 받으면 정리 작업 후 종료
이렇게 명시적인 종료 프로세스를 통해 안전하고 깔끔한 서버 종료가 가능합니다.
'[개발] Go' 카테고리의 다른 글
| GO vs JS, Python, Java 비교 (0) | 2025.05.31 |
|---|