Εισαγωγή
Σε πολλές εφαρμογές, ιδιαίτερα σε Cloud-based Azure Functions, συχνά χρειάζεται να ελέγχουμε πόσες concurrent εργασίες μπορούν να έχουν πρόσβαση σε ένα κρίσιμο resource ή διαδικασία. Στην περίπτωση μας, το resource είναι η** λειτουργία ανανέωσης (refresh) της Azure App Configuration** μέσω του middleware AppConfigurationRefreshMiddleware.
Για αυτό χρησιμοποιούμε ένα SemaphoreSlim, που λειτουργεί σαν "έξυπνη πόρτα" για τον περιορισμό των ταυτόχρονων προσβάσεων.
Τι είναι ένα Semaphore;
- Semaphore: Ένα concurrency primitive που περιορίζει πόσα threads μπορούν να εισέλθουν σε μια κρίσιμη περιοχή ταυτόχρονα.
- Στην .NET υπάρχει το SemaphoreSlim, μια lightweight υλοποίηση για async/await σενάρια.
- Μπορεί να έχει:
- - InitialCount: Πόσα "slots" είναι διαθέσιμα για concurrent access.
- - MaxCount: Το ανώτατο όριο των ταυτόχρονων χρήσεων.
Στην περίπτωσή μας:
private static readonly SemaphoreSlim _refreshSemaphore = new(1, 1);
Μόνο 1 concurrent refresh μπορεί να εκτελεστεί.
Τα υπόλοιπα invocations παρακάμπτουν το refresh αν το semaphore είναι κλειστό.
Πώς λειτουργεί το Middleware
Βήμα-βήμα
Κάθε function invocation περνάει πρώτα από το middleware Invoke(FunctionContext context, FunctionExecutionDelegate next).
Ελέγχει το τελευταίο refresh:
if (now - _lastRefreshUtc > _minRefreshInterval)
- Αν έχει περάσει το _minRefreshInterval (π.χ. 30 δευτερόλεπτα) από το τελευταίο refresh, προχωρά για ανανέωση.
- Διαφορετικά, παρακάμπτει το refresh και καλεί αμέσως το next(context).
- Απόκτηση Semaphore
if (await _refreshSemaphore.WaitAsync(0))
- Αν το semaphore είναι ελεύθερο:
- Το invocation αποκτάει το "slot".
- Εκτελείται η μέθοδος refresher.TryRefreshAsync().
- Καταγράφεται η επιτυχία ή η αποτυχία στα logs.
- Τέλος, απελευθερώνεται το semaphore (Release()).
- Αν το semaphore είναι κατειλημμένο:
- Το invocation παρακάμπτει το refresh.
Δεν μπλοκάρει τη function.
Συνέχεια pipeline
await next(context);
Η actual function εκτελείται κανονικά.
Το αποτέλεσμα επιστρέφει στον Worker.
Τι συμβαίνει όταν έχουμε πολλούς χρήστες
Σενάριο: 10.000 concurrent invocations
- Όλα τα invocations μοιράζονται το ίδιο static semaphore.
- Μόνο ένα invocation θα εκτελέσει το refresh κάθε _minRefreshInterval.
- Τα υπόλοιπα invocations συνεχίζουν απευθείας.
- Δεν υπάρχει blocking bottleneck, όλα τα requests προχωρούν.
Σενάριο: 10.000.000 concurrent invocations σε cloud
Σε extreme scale:
Scale-out του Functions Worker
Το Azure Functions auto-scales instances ανάλογα με το load.
Κάθε instance έχει το δικό του static semaphore.
Συνεπώς, σε 10.000.000 users:
Μπορεί να υπάρξουν πολλαπλά refreshes, αλλά 1 ανά instance ανά 30 δευτερόλεπτα.Resource impact
Χρειάζονται περισσότερες CPU, RAM και threads.
Το middleware δεν προκαλεί bottleneck, αλλά οι instances καταναλώνουν πόρους.Throttling & App Configuration limits
Κάθε instance σέβεται _minRefreshInterval.
Η Azure App Configuration έχει όρια για requests ανά subscription/region.
Το throttling με semaphore και _minRefreshInterval μειώνει τον κίνδυνο rate limit errors.Cold starts
Νέα instances που spin-up σε scale-out αρχίζουν με _lastRefreshUtc = DateTime.MinValue.
Θα εκτελέσουν αμέσως refresh την πρώτη φορά, μετά throttling κάθε 30 δευτερόλεπτα.
Συμπέρασμα
- Ο semaphore περιορίζει την concurrency του refresh.
- Το _minRefreshInterval προστατεύει από υπερβολικές κλήσεις προς App Configuration.
Σε scale:
Middleware είναι safe για εκατομμύρια users.
Η μόνη προσοχή είναι resource allocation και scale-out behavior.Logging & error handling είναι ενσωματωμένα, ώστε failures να μην επηρεάζουν τις actual functions.
Top comments (0)