انتشار بدون قطعی (Zero-Downtime) با ترکیب Nginx Retry و Graceful Shutdown داتنت
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۷ ۲۰:۲۵
آدرس: www.dntips.ir
[کاربران] ---> [Nginx Proxy (Retry Buffer)] ---> [.NET Kestrel (Graceful Shutdown)]
# /etc/nginx/conf.d/app.conf
upstream backend_dotnet {
server 127.0.0.1:5000 max_fails=0;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_dotnet;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ----------------------------------------------------
# هسته اصلی راهکار: مکانیزم Retry و Draining موقت
# ----------------------------------------------------
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
proxy_connect_timeout 2s;
# مهلت مجاز برای درخواستهای سنگین دیتابیسی
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}proxy_next_upstream error timeout http_502 http_503: به Nginx دستور میدهد اگر در حین اتصال با خطای قطع شبکه، تایماوت یا خطای ۵۰۲ مواجه شد، درخواست را لغو نکرده و دوباره تلاش کند.proxy_connect_timeout 2s: زمان انتظار برای اتصال اولیه به Kestrel. اگر Kestrel در حال ریاستارت باشد، حداکثر ۲ ثانیه صبر کرده و سپس تلاش بعدی را آغاز میکند.proxy_next_upstream_tries 3: تعداد دفعات مجاز برای تلاش مجدد قبل از برگرداندن خطا به کلاینت.max_fails=0: جلوگیری از اینکه Nginx سرور محلی را کلاً Unhealthy و از کار افتاده تلقی کند.Program.cs، مهلت پیشفرض توقف Kestrel را تنظیم کرده و CancellationToken را عبور میدهیم:// Program.cs
var builder = WebApplication.CreateBuilder(args);
// تنظیم زمان مهلت خاموشی Kestrel برای اتمام عملیات جاری
builder.Services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(30);
});
var app = builder.Build();
// نمونه اندپوینت CRUD دیتابیسی
app.MapPost("/api/orders", async (OrderDto dto, AppDbContext db, CancellationToken ct) =>
{
var order = new Order { Title = dto.Title, CreatedAt = DateTime.UtcNow };
db.Orders.Add(order);
// عبور دادن توکن لغو برای پشتیبانی از Rollback امن
await db.SaveChangesAsync(ct);
return Results.Created($"/api/orders/{order.Id}", order);
});
app.Run();SIGTERM، فایل سرویس لینوکس را بهصورت زیر تنظیم کنید:# /etc/systemd/system/myapp.service [Unit] Description=My .NET Production App After=network.target [Service] WorkingDirectory=/var/www/myapp ExecStart=/usr/bin/dotnet /var/www/myapp/MyApp.dll Restart=always RestartSec=5 KillSignal=SIGTERM TimeoutStopSec=35s User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production [Install] WantedBy=multi-user.target
/var/www/myapp کپی میشود.sudo systemctl restart myapp
SIGTERM به داتنت میفرستد.proxy_next_upstreamمنتظر میمانند.| معیار | روش عادی (Restart ساده) | راهکار ترکیبی (Nginx Retry + .NET) | Blue-Green کامل |
| هزینه و پیچیدگی زیرساخت | بسیار کم | بسیار کم (تکسرور) | متوسط تا زیاد (چند سرور/کانتینر) |
| قطعی سرویس (Downtime) | ۲ تا ۵ ثانیه خطای ۵۰۲ | صفر (تنها چند ثانیه تاخیر معلق) | صفر |
| امنیت تراکنشهای دیتابیس | خطر لغو ناقص تراکنش | ۱۰۰٪ تضمین شده با Graceful Shutdown | ۱۰۰٪ تضمین شده |
pingResponseTime تمام کند)، اما وقتی فرآیند (w3wp.exe) بسته شد، IIS هیچ بافری برای نگهداری درخواستهای جدیدی که در همان لحظه میرسند، ندارد. در واقع، اگر درخواست جدیدی در کسری از ثانیه بین بسته شدن فرآیند قدیم و بالا آمدن فرآیند جدید برسد، IIS فوراً خطای 503 (Service Unavailable) را به کاربر برمیگرداند، زیرا هیچ فرآیند فعالی برای تحویل درخواست وجود ندارد.proxy_next_upstream را که توضیح دادیم، روی آن اعمال میکنید. این ترکیب بسیار رایج است: Nginx درخواستهای ورودی را مدیریت و بافر میکند، و IIS اپلیکیشن داتنت را اجرا میکند.| ویژگی | Nginx (Single Server) | IIS (Single Server) | IIS + ARR (Two Instances) |
| هزینه زیرساخت | بسیار کم (یک سرور) | بسیار کم (یک سرور) | متوسط (نیاز به چند اینستنس) |
| رفتار با ریکوئستهای جدید هنگام Restart | معلق (با سوییچ شفاف) | ارسال خطای ۵۰۳ | انتقال شفاف به اینستنس دیگر |
| مدیریت خاموشی نرم داتنت | بله (SIGTERM) | بله (IIS Shutdown) | بله |