خاموشی ایمن در زبان Go (بخش اول): چطور سرویس‌های آماده تولید بسازیم که درخواست‌ها رو از دست ندن


۰


خاموش‌سازی کنترل‌شده در Go (قسمت اول): ساخت سرویس‌های آماده برای تولید بدون از دست دادن درخواست‌ها

باید سیستم‌های خود را طوری طراحی کنید که به صورت مسالمت‌آمیز از کار بیفتند.
— مارتین فاولر

مقدمه

سرویس Go شما در حال پردازش هزاران درخواست در دقیقه است. سپس یک بروزرسانی روی سیستم اجرا می‌شود. سیگنال SIGTERM صادر می‌شود. چه اتفاقی می‌افتد؟

اگر پاسخ شما این است که «فقط سرویس خاموش می‌شود...» — در این صورت ریسک از دست رفتن کارها، داده‌های ازدست‌رفته و کاربران ناراضی را دارید.

خاموش‌سازی کنترل‌شده تضمین می‌کند که سرویس شما به شکل ایمن خارج شود — درخواست‌های فعال را کامل کند، منابع را آزاد کند و از آسیب به داده‌ها جلوگیری کند.

در این راهنمای کوتاه، یاد می‌گیرید چطور:

  • سیگنال‌های خاموشی را مدیریت کنید
  • اجازه دهید درخواست‌های در حال اجرا کامل شوند
  • کارگران پس‌زمینه را متوقف کنید
  • پایگاه داده و Redis را به صورت تمیز ببندید

تا پایان، سرویس شما فقط خاموش نمی‌شود —
بلکه مثل یک حرفه‌ای خاموش خواهد شد.

خاموش‌سازی کنترل‌شده چیست؟

یک خاموش‌سازی کنترل‌شده توقف تمیز و کنترل شده سرویس شما است.

مثال: یک کاربر در حال ارسال پرداخت است. در همین لحظه، اپ شما سیگنال خاموشی دریافت می‌کند. اگر به درستی مدیریت نشود، ممکن است تراکنش از دست برود.

بدون خاموش‌سازی کنترل‌شده:

  • درخواست‌های HTTP وسط پاسخ قطع می‌شوند
  • کارها نصفه رها می‌شوند
  • اتصال‌های پایگاه داده و Redis باز می‌مانند

با خاموش‌سازی کنترل‌شده:

  • درخواست‌ها کامل می‌شوند
  • کارگران وظایف خود را تمام می‌کنند
  • اتصال‌ها بسته می‌شوند

روش نادرست

1func main() {  
2    http.ListenAndServe(":8080", handler) // بلاک می‌شود برای همیشه  
3}

بسیاری از برنامه‌ها هنگام دریافت سیگنال خاموشی کاری انجام نمی‌دهند. در نتیجه فرایند بلافاصله خارج می‌شود، بدون هیچ تمیزکاری یا بازیابی.

روش صحیح

1ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)  
2defer stop()

سپس با استفاده از server.Shutdown(ctx) اجازه می‌دهید درخواست‌ها تکمیل شوند، پایگاه داده بسته شود و کارگران متوقف گردند.

به این شکل فکر کنید

بسته شدن یک رستوران:

  • جلوی ورود مشتریان جدید را بگیرید
  • به مشتریانی که داخل هستند سرویس دهید
  • آشپزخانه را تمیز کنید
  • در را قفل کنید و جای خود را ترک کنید

سرویس‌ها هم همین‌طورند: خراب نشوید — درست بسته شوید.

پیاده‌سازی خاموش‌سازی کنترل‌شده در Go (گام به گام)

۱. گوش دادن به سیگنال‌های خاموشی

1ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)  
2defer stop()

این یک context.Context برمی‌گرداند که هنگام دریافت SIGINT یا SIGTERM کنسل می‌شود.

۲. اجرای سرور در یک گوروتین (goroutine)

1server := &http.Server{Addr: ":8080", Handler: yourHandler}
2
3
4go func() {  
5    if err := server.ListenAndServe(); err != http.ErrServerClosed {  
6        log.Fatalf("Server error: %v", err)  
7    }  
8}()

به این ترتیب گوروتین اصلی آزاد می‌ماند تا سیگنال خاموشی را مدیریت کند.

۳. انتظار برای سیگنال خاموشی

1<-ctx.Done()  
2log.Println("Shutdown signal received")

این خط برنامه را بلاک می‌کند تا وقتی سیگنال خاموشی دریافت شود.

۴. انجام پاکسازی با محدودیت زمانی

1shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)  
2defer cancel()
3
4
5if err := server.Shutdown(shutdownCtx); err != nil {  
6    log.Printf("Graceful shutdown failed: %v", err)  
7} else {  
8    log.Println("Server shut down cleanly")  
9}

این اطمینان می‌دهد همه درخواست‌ها یا تکمیل شوند یا پس از ۱۰ ثانیه تایم‌اوت شود.


ادامه با خاموش‌کردن کارگران پس‌زمینه و کارهای طولانی‌مدت

خاموش‌کردن کارگران پس‌زمینه و تسک‌های طولانی

بسیاری از برنامه‌های Go از goroutineهای پس‌زمینه برای پردازش کارها، دریافت پیام‌ها یا انجام کارهای دوره‌ای استفاده می‌کنند. اگر این‌ها به درستی متوقف نشوند، ممکن است:

  • کارهایی که درحال انجام هستند از دست بروند
  • داده‌ها تکراری یا خراب شوند
  • سیستم‌های خارجی (مثل صف‌ها یا دیتابیس‌ها) در وضعیت ناسازگار باقی بمانند

خاموش‌کردن گام به گام برای کارگران

۱. استفاده از Context در کارگران

یک context.Context به هر کارگر پاس بدهید و منتظر لغو آن بمانید:

1func startWorker(ctx context.Context, jobs <-chan Job) {  
2    for {  
3        select {  
4        case <-ctx.Done():  
5            log.Println("Worker received shutdown signal")  
6            return  
7        case job := <-jobs:  
8            process(job)  
9        }  
10    }  
11}

۲. اجرای کارگران با WaitGroup

1var wg sync.WaitGroup  
2wg.Add(1)  
3go func() {  
4    defer wg.Done()  
5    startWorker(ctx, jobQueue)  
6}()

۳. صبر کردن پیش از خروج

بعد از فراخوانی cancel() روی context خاموشی، منتظر بمانید تا همه کارگران کارشان را تمام کنند:

1stop() // لغو signal.NotifyContext  
2wg.Wait() // انتظار برای اتمام همه کارگران

با این الگو:

  • کارگران دیگر کار جدید قبول نمی‌کنند
  • تسک‌های در حال انجام به صورت تمیز تمام می‌شوند
  • اپلیکیشن با اطمینان خارج می‌شود

خاموش‌سازی کنترل‌شده در Kubernetes: سیگنال‌های پایان، پروب‌ها و اشتباهات رایج

Kubernetes به سرعت پاد (Pod) شما را نمی‌کشد. فرصتی به برنامه می‌دهد تا بتواند خاموش شود، البته اگر بلد باشید چطور استفاده کنید.

مراحل در زمان پایان پاد:

۱. به کانتینر شما SIGTERM ارسال می‌شود
۲. پروب Readiness شکست می‌خورد و ترافیک ورودی قطع می‌شود
۳. شما مدت زمان terminationGracePeriodSeconds (به طور پیش‌فرض ۳۰ ثانیه) برای خاموش شدن دارید
۴. اگر به موقع خاموش نشدید، Kubernetes سیگنال SIGKILL ارسال می‌کند

چگونه خاموش‌سازی کنترل‌شده را با Kubernetes یکپارچه کنیم

  • از signal.NotifyContext استفاده کنید تا اپ به SIGTERM واکنش دهد
  • سریع پذیرش درخواست‌های جدید را متوقف کنید (مثلاً با شکستن پروب readiness)
  • کارهای در حال اجرا را در محدوده زمان grace period تمام کنید
  • timeout مناسبی برای server.Shutdown() تنظیم کنید

مثالی از پروب readiness که هنگام خاموش شدن با شکست مواجه می‌شود:

1var ready = true  
2  
3http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {  
4    if !ready {  
5        http.Error(w, "shutting down", http.StatusServiceUnavailable)  
6        return  
7    }  
8    w.Write([]byte("ok"))  
9})  
10  
11// هنگام دریافت SIGTERM:  
12ready = false // پروب readiness شروع به شکست خوردن می‌کند

اشتباهات رایج

  • زمان تایم‌اوت خیلی کوتاه: اپ قبل از اتمام پاکسازی، سیگنال SIGKILL دریافت می‌کند
  • فراموش کردن شکست پروب readiness: ترافیک جدید هنوز به پاد در حال خاموش‌شدن می‌رسد
  • فراخوانی مستقیم os.Exit: که باعث می‌شود پاکسازی انجام نشود

کubernetes ابزار لازم را در اختیار شما می‌گذارد، اما اپ شما باید به درستی واکنش نشان دهد.

جمع‌بندی

خاموش‌سازی کنترل‌شده فقط یک الگو نیست — یک انضباط است.
کد واکنشی را به سیستم‌های قابل اطمینان تبدیل می‌کند.

چه در Kubernetes اجرا کنید، چه روی سرور اختصاصی یا محیط‌های بدون سرور (serverless)، همیشه سرویس‌ها را طوری طراحی کنید که به شکل تمیز از صحنه خارج شوند.

چون وقتی به درستی انجام شود، هیچ کاربری متوجه نمی‌شود — و این نقطه اصلی موضوع است.

اچ تی ام ال

۰


نظرات


author
نویسنده مقاله: شادان ارشدی

کد با می متعهد است که بالاترین سطح کیفی آموزش را در اختیار شما بگذارد. هدف به اشتراک گذاشتن دانش فناوری اطلاعات و توسعه نرم افزار در بالاترین سطح ممکن برای درستیابی به جامعه ای توانمند و قدرتمند است. ما باور داریم هر کسی میتواند با استمرار در یادگیری برنامه نویسی چالش های خود و جهان پیرامون خود را بر طرف کند و به موفقیت های چشم گیر برسد. با ما در این مسیر همراه باشید. کد با می اجتماع حرفه ای برنامه نویسان ایرانی.

تمام حقوق این سایت متعلق به وبسایتcodebymeمیباشد.