دوبارہ کوشش کرنے کا پیٹرن: لچکدار مائیکرو سروسز بنانا
مائیکرو سروسز فن تعمیر میں، سروسز ان میموری کالز کے بجائے نیٹ ورک پر بات چیت کرتی ہیں۔ اگرچہ یہ ڈیکپلنگ بڑے پیمانے پر افقی اسکیلنگ اور آزاد تعیناتیوں کو قابل بناتا ہے، یہ ایک بڑی کمزوری کو بھی متعارف کراتا ہے: نیٹ ورک ناقابل اعتبار ہے۔
کسی بھی لمحے، ڈاؤن اسٹریم سروس کو نیٹ ورک کی ایک مختصر خرابی، عارضی سی پی یو اسپائک، فوری ڈیٹا بیس لاک تنازعہ، یا رولنگ اپ ڈیٹ دوبارہ شروع ہو سکتا ہے۔ ان عارضی ناکامیوں کو عارضی فالٹس کے نام سے جانا جاتا ہے۔
اگر آپ کی سروس فوری طور پر ایک غلطی پھینک دیتی ہے اور اس درخواست کو ناکام بنا دیتی ہے جب ڈاؤن اسٹریم کال ناکام ہوجاتی ہے، تو آپ صارف کا ایک نازک تجربہ بناتے ہیں۔ اس کے بجائے، ان میں سے بہت سی عارضی غلطیوں کو ایک لمحہ انتظار کرنے اور دوبارہ کوشش کر کے خود بخود حل کیا جا سکتا ہے۔ یہ وہ جگہ ہے جہاں دوبارہ کوشش کرنے کا پیٹرن آتا ہے۔
اس گائیڈ میں، ہم دوبارہ کوشش کرنے کے پیٹرن کو دریافت کریں گے، یہ کیسے کام کرتا ہے، سادہ نفاذ کے خطرات، اور جاوا (Resilience4j) اور Go میں اسے صحیح طریقے سے کیسے نافذ کیا جائے۔
حقیقی دنیا کی تشبیہ: ایک مصروف لائن کو دوبارہ ڈائل کرنا
تصور کریں کہ آپ کسی دوست کو فون کرنے کی کوشش کر رہے ہیں۔ آپ ان کا نمبر ڈائل کرتے ہیں، لیکن آپ کو مصروف سگنل ملتا ہے کیونکہ وہ فی الحال دوسری کال پر ہیں۔
کیا آپ فوراً دستبردار ہو جاتے ہیں، ان کا رابطہ حذف کر دیتے ہیں، اور فرض کر لیتے ہیں کہ آپ ان سے دوبارہ کبھی بات نہیں کر سکتے؟ ہرگز نہیں۔ آپ ہینگ اپ کریں، ایک منٹ انتظار کریں، اور ان کا نمبر دوبارہ ڈائل کریں۔ اگر وہ ابھی بھی مصروف ہیں، تو آپ دوبارہ کوشش کرنے سے پہلے پانچ منٹ انتظار کر سکتے ہیں۔
بالآخر، ان کی کال ختم ہو جاتی ہے، اور آپ کی دوبارہ کوشش کامیاب ہو جاتی ہے۔
مائیکرو سروسز میں: دی کال ڈاؤن اسٹریم سروس کے لیے ایک API کی درخواست ہے۔
- مصروف سگنل ایک عارضی نیٹ ورک کی خرابی یا
503 Service Unavailableجواب ہے۔ ریڈیل دوبارہ کوشش کرنے کی کوشش ہے۔ انتظار کا وقت بیک آف کا دورانیہ ہے۔
خطرہ: بولی دوبارہ کوششیں اور “دوبارہ کوشش کریں طوفان”
دوبارہ کوشش کرنے کے طریقہ کار کو لاگو کرنا پہلی نظر میں معمولی لگتا ہے: بس اپنی HTTP کال کو for لوپ میں لپیٹیں اور اس کے کامیاب ہونے تک کوشش کرتے رہیں۔ تاہم، ایک سادہ سے دوبارہ کوشش کا نفاذ آسانی سے ایک معمولی ہچکی کو تباہ کن نظام کی بندش میں بدل سکتا ہے۔
ایک ڈاون اسٹریم سروس کا تصور کریں جو ٹریفک کے اچانک اضافے کے تحت جدوجہد کر رہی ہے۔ اس کا ڈیٹا بیس 99% CPU استعمال پر چل رہا ہے، اور درخواستوں کا وقت ختم ہونا شروع ہو رہا ہے۔
اگر 100 کلائنٹ کی تمام سروسز ٹائم آؤٹ کا پتہ لگاتی ہیں اور بغیر انتظار کیے فوری طور پر 3 بار دوبارہ کوشش کرتی ہیں، تو وہ پہلے سے گلا گھونٹنے والی بہاو سروس کو ٹکرانے والے ٹریفک کے حجم کو اچانک تین گنا کر دیں گی۔ ٹریفک کے اس اچانک اضافہ کو دوبارہ کوشش کریں طوفان (یا تھنڈرنگ ہارڈ مسئلہ) کے نام سے جانا جاتا ہے۔
ڈاؤن اسٹریم سروس کی بحالی میں مدد کرنے کے بجائے، آپ کی دوبارہ کوششیں اسے نیچے کی طرف دھکیلتی رہیں گی، اور اسے کبھی پکڑنے سے روکیں گی۔
حل: بیک آف اور جٹر
دوبارہ کوشش کرنے والے طوفانوں کو روکنے کے لیے، ایک لچکدار نظام کو دو ضروری حکمت عملیوں کا استعمال کرنا چاہیے: بیک آف اور جیٹر۔
1. بیک آف حکمت عملی
بیک آف یہ بتاتا ہے کہ ایک کلائنٹ کو بعد میں دوبارہ کوشش کرنے سے پہلے کتنا انتظار کرنا چاہیے۔
- فکسڈ بیک آف: کلائنٹ کوششوں کے درمیان مستقل وقت (مثلاً 200ms) کا انتظار کرتا ہے۔ سادہ ہونے کے باوجود، یہ اب بھی مطابقت پذیر ٹریفک اسپائکس کا خطرہ چلاتا ہے۔
- ایکسپونینشل بیک آف: ہر ناکام کوشش کے ساتھ انتظار کا وقت تیزی سے بڑھتا ہے (مثلاً، 100ms، 200ms، 400ms، 800ms)۔ یہ ڈاون اسٹریم سروس کو بتدریج بحالی کے لیے مزید وقت دیتا ہے کیونکہ ناکامی برقرار رہتی ہے۔
2. ہلچل (بے ترتیب پن)
یہاں تک کہ ایکسپونینشل بیک آف کے باوجود، اگر نیٹ ورک بلپ کی وجہ سے 1,000 درخواستیں بالکل اسی وقت ناکام ہوجاتی ہیں، تمام 1,000 کلائنٹس بالکل اسی بیک آف تاخیر کا حساب لگائیں گے۔ نتیجتاً، وہ سبھی لہروں میں بیک وقت دوبارہ کوشش کریں گے، مطابقت پذیر اسپائکس کے ساتھ ڈاؤن اسٹریم سروس کو مارتے ہوئے۔
Jitter بیک آف تاخیر میں بے ترتیب تغیرات شامل کرکے اسے حل کرتا ہے۔
Without Jitter (Synchronized Waves):
Time: 0ms -> [1000 requests fail]
Time: 100ms -> [1000 retries hit simultaneously]
Time: 200ms -> [1000 retries hit simultaneously]
With Jitter (Distributed Traffic):
Time: 0ms -> [1000 requests fail]
Time: 92ms -> [85 retries]
Time: 105ms -> [120 retries]
Time: 118ms -> [95 retries]
... (Traffic is smoothed out over time)
ایک بے ترتیب وقفہ پر دوبارہ کوششوں کو پھیلانے سے، ڈاؤن اسٹریم سروس پر بوجھ کو ہموار کیا جاتا ہے، جس سے اسے خوبصورتی سے بحال کیا جا سکتا ہے۔
دوبارہ کوششوں کا سنہری اصول: Idempotency
اس سے پہلے کہ آپ کسی بھی API کے اختتامی نقطہ پر دوبارہ کوششیں لاگو کریں، آپ کو ایک اہم سوال ضرور پوچھنا چاہیے: کیا یہ آپریشن غیرمعمولی ہے؟
ایک آئیڈیمپوٹینٹ آپریشن وہ ہوتا ہے جہاں ایک سے زیادہ ایک جیسی درخواستیں کرنے کا اثر ایک ہی درخواست کرنے جیسا ہوتا ہے۔
- Idempotent: صارف پروفائل (
GET /users/123) کو پڑھنا، پورے ای میل فیلڈ (PUT /users/123/email) کو اپ ڈیٹ کرنا، یا ایک آئٹم (DELETE /items/456) کو حذف کرنا۔ - نان ڈیمپوٹینٹ: ایک نیا آرڈر بنانا (
POST /orders)، یا کریڈٹ کارڈ چارج (POST /payments) پر کارروائی کرنا۔
فرض کریں کہ آپ ایک گاہک سے $50 چارج کرنے کے لیے POST /payments کو کال کرتے ہیں۔ ڈاؤن اسٹریم پیمنٹ گیٹ وے درخواست وصول کرتا ہے، کریڈٹ کارڈ سے کامیابی سے چارج کرتا ہے، لیکن پھر نیٹ ورک بلپ ہوتا ہے اس سے پہلے کہ وہ آپ کو 200 OK جواب بھیج سکے۔
آپ کی سروس ٹائم آؤٹ کو رجسٹر کرتی ہے، فرض کرتی ہے کہ کال ناکام ہوگئی، اور خود بخود دوبارہ کوشش کرتی ہے۔ اگر ادائیگی کا گیٹ وے ڈپلیکیٹ درخواستوں کو ہینڈل کرنے کے لیے ڈیزائن نہیں کیا گیا ہے، تو کسٹمر سے دو بار چارج کیا جائے گا۔
[!انتباہ] کبھی بھی غیر قابل عمل آپریشنز کی دوبارہ کوشش نہ کریں جب تک کہ ڈاؤن اسٹریم سروس Idempotency Keys کو سپورٹ نہ کرے (منفرد درخواست شناخت کنندگان جو ڈپلیکیٹ ٹرانزیکشنز کا پتہ لگانے اور اسے مسترد کرنے کے لیے استعمال ہوتے ہیں)۔
نفاذ کی مثالیں۔
آئیے دیکھتے ہیں کہ جاوا اور گو میں دوبارہ کوشش کرنے کے پیٹرن کو کیسے نافذ کیا جائے۔
1. Java (Resilience4j اور Spring Boot)
Resilience4j ایک مضبوط، انتہائی قابل ترتیب دوبارہ کوشش کرنے والا ماڈیول فراہم کرتا ہے۔ بیرونی بلنگ سروس کے لیے اسے کنفیگر کرنے کا طریقہ یہاں ہے۔
کنفیگریشن (application.yml)
resilience4j.retry:
instances:
billingService:
maxAttempts: 3
waitDuration: 100ms
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2.0
enableRandomizedWait: true
randomizedWaitFactor: 0.5
retryExceptions:
- org.springframework.web.client.ResourceAccessException
- io.netty.channel.ConnectTimeoutException
ignoreExceptions:
- org.springframework.web.client.HttpClientErrorException # E.g., 400 Bad Request shouldn't be retried
کوڈ کا نفاذ
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class PaymentProcessor {
private final RestTemplate restTemplate;
public PaymentProcessor(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
// Apply the configured retry policy with a fallback method
@Retry(name = "billingService", fallbackMethod = "billingFallback")
public String chargeUser(BillingRequest request) {
return restTemplate.postForObject("http://billing-service/charge", request, String.class);
}
// Executed when all retry attempts fail
public String billingFallback(BillingRequest request, Throwable throwable) {
return "Billing service is currently unavailable. Your transaction will be queued.";
}
}
2. جاؤ (گولانگ)
گو میں، ہم معیاری لائبریری ٹائمرز کا استعمال کرتے ہوئے ایک خوبصورت دوبارہ کوشش کرنے والا رنر لکھ سکتے ہیں جس میں ایکسپونینشل بیک آف اور رینڈمائزڈ جٹر شامل ہوں۔
package main
import (
"context"
"errors"
"fmt"
"math/rand"
"time"
)
// RetryConfig holds the policies for our retry attempts
type RetryConfig struct {
MaxAttempts int
MinBackoff time.Duration
MaxBackoff time.Duration
}
// Execute runs the operation using exponential backoff with full jitter
func Execute(ctx context.Context, config RetryConfig, operation func() error) error {
var err error
for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
err = operation()
if err == nil {
return nil // Success!
}
if attempt == config.MaxAttempts {
break
}
// Calculate exponential backoff
// wait = min(MaxBackoff, MinBackoff * 2^(attempt-1))
backoff := config.MinBackoff * (1 << (attempt - 1))
if backoff > config.MaxBackoff || backoff <= 0 {
backoff = config.MaxBackoff
}
// Apply Full Jitter: wait randomly between 0 and backoff
jitter := time.Duration(rand.Int63n(int64(backoff)))
fmt.Printf("[Attempt %d/%d] Failed: %v. Retrying in %v...\n", attempt, config.MaxAttempts, err, jitter)
select {
case <-time.After(jitter):
case <-ctx.Done():
return ctx.Err()
}
}
return fmt.Errorf("operation failed after %d attempts: %w", config.MaxAttempts, err)
}
func main() {
config := RetryConfig{
MaxAttempts: 4,
MinBackoff: 100 * time.Millisecond,
MaxBackoff: 2000 * time.Millisecond,
}
// Mock function that fails 3 times and succeeds on the 4th
attempts := 0
mockAPI := func() error {
attempts++
if attempts < 4 {
return errors.New("network timeout (503)")
}
return nil
}
ctx := context.Background()
err := Execute(ctx, config, mockAPI)
if err != nil {
fmt.Printf("Final Outcome: %v\n", err)
} else {
fmt.Println("Final Outcome: Successfully connected on attempt", attempts)
}
}
دوبارہ کوشش کریں بمقابلہ سرکٹ بریکر: کون سا کب استعمال کریں؟
ڈویلپر اکثر دوبارہ کوشش کرنے کے پیٹرن کو سرکٹ بریکر پیٹرن کے ساتھ الجھاتے ہیں۔ اگرچہ دونوں کا مقصد نظام کی لچک کو بہتر بنانا ہے، وہ بنیادی طور پر مختلف قسم کی ناکامیوں کو سنبھالتے ہیں:
| میٹرک | پیٹرن کی دوبارہ کوشش کریں | سرکٹ بریکر پیٹرن |
|---|---|---|
| بنیادی مقصد | عارضی (قلیل المدتی) ناکامیوں سے خود بخود بازیافت ہو جاتی ہے۔ | مسلسل (طویل المدت) ناکامیوں کو کالر کو نیچے اتارنے سے روکتا ہے۔ |
| حکمت عملی | فوری طور پر دوبارہ کوشش کریں یا مختصر بیک آف انتظار کے بعد۔ | ٹارگٹ سروس کو طلب کیے بغیر فوری طور پر فیل ہو جائیں۔ |
| ڈاؤن اسٹریم اثر | ڈاؤن اسٹریم سروس پر عارضی طور پر بوجھ بڑھاتا ہے۔ | ڈاؤن اسٹریم سروس کو ٹریفک سے بچاتا ہے، اسے بحال کرنے کی اجازت دیتا ہے۔ |
| عام محرکات | مختصر نیٹ ورک منقطع، ڈیٹا بیس ٹائم آؤٹ، ساکٹ ڈراپ۔ | ڈاؤن اسٹریم سروس مکمل طور پر بند ہے، مسلسل 500s یا ٹائم آؤٹ واپس آ رہی ہے۔ |
پاور کپل: دونوں کو ملانا
پیداواری نظاموں میں، یہ دونوں نمونے ایک ساتھ استعمال کرنے کے لیے بنائے گئے ہیں۔
جب آپ ڈاؤن اسٹریم کی درخواست کرتے ہیں، تو اسے پہلے سرکٹ بریکر اور پھر دوبارہ کوشش ریپر سے گزرنا چاہیے۔
اگر کوئی عارضی خرابی واقع ہوتی ہے تو، دوبارہ کوشش کرنے کا طریقہ کار اسے روکتا ہے اور دوبارہ کوشش کرتا ہے۔ تاہم، اگر ڈاؤن اسٹریم سروس بند ہے، تو ناکامیوں کا ڈھیر لگ جائے گا۔ سرکٹ بریکر لگاتار ناکامیوں کا پتہ لگاتا ہے، “ٹرپس” کھل جاتا ہے، اور مستقبل کی تمام کالوں کو روکتا ہے۔
اب، جب آپ سروس کو کال کرنے کی کوشش کرتے ہیں، سرکٹ بریکر فوری طور پر ناکام ہوجاتا ہے، دوبارہ کوشش کرنے والے لوپ کو مکمل طور پر نظرانداز کرتے ہوئے اور آپ کے کمپیوٹنگ وسائل کو بچاتے ہیں۔
دوبارہ کوشش کرنے کے پیٹرن کے لیے بہترین طریقے
- صرف عارضی غلطیوں کی دوبارہ کوشش کریں: HTTP اسٹیٹس کوڈز کا معائنہ کریں۔
503 Service Unavailable،429 Too Many Requests(اگر موجود ہو توRetry-Afterہیڈرز کا احترام کرتے ہوئے)، اور نیٹ ورک کا ٹائم آؤٹ دوبارہ کوشش کریں۔ نہیں دوبارہ کوشش کریں400 Bad Request،401 Unauthorized، یا404 Not Found—یہ دوبارہ کوشش کرنے پر کبھی کامیاب نہیں ہوں گے۔ - Jitter کا اطلاق کریں: کبھی بھی رینڈم جٹر متعارف کرائے بغیر دوبارہ کوشش کرنے کا ایک مقررہ ٹائمر استعمال نہ کریں۔
- زیادہ سے زیادہ کوششوں کو محدود کریں: ایک معقول حد کے بعد دوبارہ کوشش کرنا بند کریں (عام طور پر 3 سے 5 کوششیں)۔ لامحدود دوبارہ کوششیں وسائل استعمال کرتی ہیں اور تاخیر کو نیچے کھینچتی ہیں۔
- کیسکیڈنگ کی دوبارہ کوششوں سے محتاط رہیں: اگر سروس A سروس B کو کال کرتی ہے (جو 3 بار دوبارہ کوشش کرتی ہے) اور سروس B سروس C کو کال کرتی ہے (جو 3 بار دوبارہ کوشش کرتی ہے)، C میں ناکامی کے نتیجے میں $3 \times 3 = 9$ کیسکیڈنگ کالز ہو سکتی ہیں۔ صرف ان حدود پر دوبارہ کوششیں لاگو کریں جہاں وہ سب سے زیادہ معنی خیز ہوں۔
- ہمیشہ سخت ٹائم آؤٹ سیٹ کریں: یقینی بنائیں کہ آپ کے نیٹ ورک کا ٹائم آؤٹ آپ کے بیک آف پیریڈز سے کم ہے، اس لیے تھریڈز کو غیر معینہ مدت تک روکا نہیں جاتا۔
نتیجہ
دوبارہ کوشش کرنے کا پیٹرن مائیکرو سروس آرکیٹیکچرز میں نیٹ ورک کی ناقابل اعتباریت کے خلاف دفاع کی ایک طاقتور پہلی لائن ہے۔ جب Exponential Backoff، Jitter، اور Circuit Breaker کے ساتھ جوڑا بنایا جائے تو، آپ اپنے سسٹمز کو جھڑپوں سے بچا سکتے ہیں اور اپنے صارفین کے لیے ایک ہموار، خود کو ٹھیک کرنے کا تجربہ بنا سکتے ہیں۔