Foglalt előtér kizárási minta
A nagy mennyiségű háttérbeli szálon végzett aszinkron feladatok elvonhatják az erőforrásokat más egyidejű előtérbeli feladatok elől, ami elfogadhatatlan mértékben megnöveli a válaszidőket.
A probléma leírása
A nagy erőforrásigényű feladatok növelhetik a felhasználói kérések válaszidejét, és magas késést eredményezhetnek. A válaszidők javításának egyik módja az, ha a nagy erőforrásigényű feladatokat kiszervezzük egy önálló szálra. Ezzel a megoldással az alkalmazás továbbra is válaszkész maradhat, miközben a feldolgozás a háttérben zajlik. Azonban a háttérbeli szálon futó feladatok továbbra is használnak erőforrásokat. Ha túl sok az ilyen feladat, azok elvonhatják az erőforrásokat azon szálak elől, amelyek a kéréseket kezelik.
Megjegyzés:
Az erőforrás kifejezés sok mindent foglalhat magában, köztük a processzor- és memóriakihasználtságot, illetve a hálózati vagy lemez I/O-t.
Ez a probléma általában akkor fordul elő, ha egy alkalmazás egyetlen nagy kódtömbként jött létre, ahol az összes üzleti logika egy rétegben található, és osztozik a megjelenítési réteggel.
A következő ASP.NET-alapú példa bemutatja a problémát. A teljes kódmintát itt találja.
public class WorkInFrontEndController : ApiController
{
[HttpPost]
[Route("api/workinfrontend")]
public HttpResponseMessage Post()
{
new Thread(() =>
{
//Simulate processing
Thread.SpinWait(Int32.MaxValue / 100);
}).Start();
return Request.CreateResponse(HttpStatusCode.Accepted);
}
}
public class UserProfileController : ApiController
{
[HttpGet]
[Route("api/userprofile/{id}")]
public UserProfile Get(int id)
{
//Simulate processing
return new UserProfile() { FirstName = "Alton", LastName = "Hudgens" };
}
}
A
WorkInFrontEnd
vezérlőben lévőPost
metódus egy HTTP POST műveletet implementál. Ez a művelet egy hosszan futó, nagy processzorigényű feladatot szimulál. A feladat egy önálló szálon fut, hogy a POST művelet gyorsan befejeződhessen.A
UserProfile
vezérlőben lévőGet
metódus egy HTTP GET műveletet valósít meg. Ennek a metódusnak sokkal kisebb a processzorigénye.
Az elsődleges szempont a Post
metódus erőforrásigénye. Bár a metódus egy háttérbeli szálra helyezi a feladatot, a feladat így is jelentős mértékű processzor-erőforrásokat használhat. Ezeket az erőforrásokat egyidejűleg más műveletek is használják, amelyeket más felhasználók végeznek. Ha közepes számú felhasználó egyszerre küldi el ezt a kérést, az valószínűleg rontja az összteljesítményt, és lelassítja az összes műveletet. A Get
metódus használatakor a felhasználók többek között jelentős késleltetéseket tapasztalhatnak.
A probléma megoldása
Helyezze át a jelentős erőforrás-használatú folyamatokat egy külön háttérre.
Így az előtér az erőforrás-igényes feladatokat egy üzenetsorba állítja. A háttér felveszi a feladatokat aszinkron feldolgozásra. Az üzenetsor terheléselosztóként is működik, amely puffereli a kéréseket a háttér számára. Ha az üzenetsor túl hosszúra nő, az automatikus skálázás konfigurálható a háttér horizontális felskálázásra.
Az alábbiakban az előző kód átdolgozott verziója látható. Ebben a verzióban a Post
metódus a Service Bus-üzenetsorban helyez el egy üzenetet.
public class WorkInBackgroundController : ApiController
{
private static readonly QueueClient QueueClient;
private static readonly string QueueName;
private static readonly ServiceBusQueueHandler ServiceBusQueueHandler;
public WorkInBackgroundController()
{
var serviceBusConnectionString = ...;
QueueName = ...;
ServiceBusQueueHandler = new ServiceBusQueueHandler(serviceBusConnectionString);
QueueClient = ServiceBusQueueHandler.GetQueueClientAsync(QueueName).Result;
}
[HttpPost]
[Route("api/workinbackground")]
public async Task<long> Post()
{
return await ServiceBusQueueHandler.AddWorkLoadToQueueAsync(QueueClient, QueueName, 0);
}
}
A háttér lekéri az üzeneteket a Service Bus-üzenetsorból, és elvégzi a feldolgozást.
public async Task RunAsync(CancellationToken cancellationToken)
{
this._queueClient.OnMessageAsync(
// This lambda is invoked for each message received.
async (receivedMessage) =>
{
try
{
// Simulate processing of message
Thread.SpinWait(Int32.MaxValue / 1000);
await receivedMessage.CompleteAsync();
}
catch
{
receivedMessage.Abandon();
}
});
}
Considerations
- Ez a módszer összetettebbé teszi az alkalmazást. Gondoskodni kell a biztonságos sorba állításról és sorból való eltávolításról, hogy ne vesszenek el a kérések hiba esetén.
- Az alkalmazás függőséget vesz fel egy további szolgáltatásra az üzenetsorhoz.
- A feldolgozási környezetnek megfelelően skálázhatónak kell lennie, hogy képes legyen kezelni a várt számításifeladat-mennyiséget, és teljesíteni tudja az átviteli sebességgel kapcsolatos követelményeket.
- Ennek a megoldásnak összességében növelnie kellene a válaszkészséget, de előfordulhat, hogy a háttérbe áthelyezett feladatok elvégzése hosszabb időt vesz igénybe.
A probléma észlelése
Az elfoglalt előtér tünetei közé tartozik a magas válaszidő a nagy erőforrásigényű feladatok végrehajtásakor. A végfelhasználók valószínűleg hosszabb válaszidőket vagy a szolgáltatások időtúllépése által okozott hibákat jelentik. Ezek a hibák HTTP 500 (belső kiszolgáló) vagy HTTP 503 (szolgáltatás nem érhető el) hibákat is eredményezhetnek. Vizsgálja át a webkiszolgáló eseménynaplóit, amelyek valószínűleg részletesebb információkat tartalmaznak a hibák okairól és körülményeiről.
A következő lépések végrehajtásával azonosíthatja a problémát:
- Az éles rendszer folyamatmonitorozásával azonosíthatja azokat a pontokat, ahol a válaszidők lelassulnak.
- Az ezeken a pontokon gyűjtött telemetriaadatok vizsgálatával megállapíthatja, hogy mely műveletek mennek végbe és mely erőforrások vannak használatban.
- Megtalálhatja az összefüggéseket a gyenge válaszidők és az adott időpontokban futó műveletek mennyisége és kombinációi között.
- Végezzen terhelési teszteket a gyanús műveletekkel, így megállapíthatja, hogy mely műveletek használják az erőforrásokat és veszik el azokat más műveletek elől.
- Tekintse át az adott műveletek forráskódját, amiből kiderülhet, hogy a műveletek miért járnak túlzott erőforráshasználattal.
Diagnosztikai példa
Az alábbi szakaszokban ezeket a lépéseket hajtjuk végre a fentebb leírt mintaalkalmazáson.
A lassulási pontok azonosítása
Tagolja az egyes metódusokat, hogy nyomon követhesse az egyes kérések futási idejét és erőforrás-használatát. Ezután monitorozza az alkalmazást éles környezetben. Ezzel átfogó képet kaphat arról, hogy a kérések hogyan versengenek egymással. A nagy nyomással járó időszakokban a lassan futó, nagy erőforrásigényű kérések valószínűleg hatással lesznek a többi műveletre. Ezt a viselkedést úgy figyelheti meg, ha a rendszer monitorozásakor észreveszi a teljesítménycsökkenést.
Az alábbi képen egy monitorozási irányítópult látható. (Használtuk AppDynamics a tesztjeinkhez.) Kezdetben a rendszer könnyű terheléssel rendelkezik. Ezután a felhasználók elkezdik lekérni a UserProfile
GET metódust. A teljesítmény viszonylag jó marad egészen addig, amíg más felhasználók el nem kezdenek kéréseket küldeni a WorkInFrontEnd
POST metódus számára. Ekkor a válaszidők jelentős mértékben megnövekednek (első nyíl). A válaszidők csak akkor kezdenek el csökkenni, amikor a WorkInFrontEnd
vezérlő számára küldött kérések mennyisége lecsökken (második nyíl).
A telemetriaadatok vizsgálata és az összefüggések felderítése
A következő képen néhány olyan mérőszám látható, amelyek ugyanezen időszak alatt az erőforráshasználat monitorozása céljából lettek összegyűjtve. Először kevés felhasználó fér hozzá a rendszerhez. Ahogy további felhasználók csatlakoznak, a processzorkihasználtság rendkívül magasra emelkedik (100%). A processzor kihasználtságával együtt kezdetben a hálózati I/O is megnövekszik. A processzorhasználat tetőzésével azonban a hálózati I/O visszaesik. Ennek az az oka, hogy a rendszer csak viszonylag kevés kérést tud egyszerre kezelni, miután a processzor elérte a maximális kapacitását. Ahogy a felhasználók bontják a kapcsolatot, a processzor terhelése fokozatosan csökken.
Ekkor úgy tűnik, hogy a WorkInFrontEnd
vezérlő Post
metódusát kell közelebbről megvizsgálni. Az elmélet megerősítéséhez további lépések szükségesek ellenőrzött környezetben.
Terhelési tesztelés végrehajtása
A következő lépés tesztek végrehajtása ellenőrzött környezetben. Például hajtson végre több olyan terhelési tesztet, amelyek először tartalmazzák, majd kihagyják az egyes kéréseket, és ez alapján mérje fel a hatásukat.
Az alábbi grafikonon látható terhelésiteszt-eredmények egy ugyanolyan felhőszolgáltatás üzemelő példányán lettek elvégezve, mint a korábbi tesztek. A tesztben 500 felhasználó hajtotta végre a Get
műveletet a UserProfile
vezérlőben, miközben lépéses terhelés is történt, ahol a felhasználók a Post
műveletet végezték a WorkInFrontEnd
vezérlőben.
Kezdetben a lépéses terhelés 0, így az egyedüli aktív felhasználók a UserProfile
kéréseket hajtják végre. A rendszer körülbelül másodpercenként 500 kérésre képes válaszolni. 60 másodperc után további 100 felhasználó kezd el POST kéréseket küldeni a WorkInFrontEnd
vezérlőnek. A UserProfile
vezérlőnek elküldött számításifeladat-mennyiség szinte azonnal másodpercenként 150 kérésre csökken. Ez a terhelési teszt működési mechanizmusa miatt van. A teszt a kérések elküldése előtt megvárja az előző kérdésre kapott választ, ezért minél hosszabb ideig tart a válasz érkezése, annál alacsonyabb lesz a kérések aránya.
Ahogy több felhasználó küld POST kéréseket a WorkInFrontEnd
vezérlőnek, úgy csökken tovább a UserProfile
vezérlő válaszadási aránya. Vegye figyelembe azonban, hogy a vezérlő által WorkInFrontEnd
kezelt kérelmek mennyisége viszonylag állandó marad. Láthatóvá válik a rendszer túltelítődése, ahogy a két kérés összesített sebessége egy egyenletesen alacsony korlát felé tart.
A forráskód áttekintése
Az utolsó lépés a forráskód áttekintése. A fejlesztőcsapat tisztában van azzal, hogy a Post
metódus jelentős időt vehet igénybe, ezért az eredeti implementációban egy külön szál lett erre a célra használva. Ezzel a közvetlen probléma megoldódott, mivel a Post
metódus nem blokkolt le arra várva, hogy egy hosszan futó feladat befejeződjön.
Azonban ez a metódus továbbra is használja a processzort, a memóriát és az egyéb erőforrásokat. A folyamat aszinkron módon való futásának engedélyezése valójában csökkentheti a teljesítményt, mivel a felhasználók nagy mennyiségű ilyen műveletet aktiválhatnak egyszerre, felügyelet nélkül. A kiszolgálók csak véges számú szálat tudnak egyszerre futtatni. Ennek elérése után az alkalmazások valószínűleg kivételt kapnak, ha megpróbálnak elindítani egy új szálat.
Megjegyzés:
Ez nem jelenti azt, hogy az aszinkron műveleteket kerülni kellene. Az aszinkron várakoztatás végrehajtása a hálózati hívásoknál ajánlott eljárás. (Lásd: Szinkron I/O antipattern.) A probléma itt az, hogy a processzorigényes munkát egy másik szálon hozták.
A megoldás megvalósítása és az eredmény ellenőrzése
A következő képen a teljesítmény monitorozása látható a megoldás implementálása után. A terhelés hasonló, mint korábban, de a UserProfile
vezérlő válaszideje már sokkal rövidebb. Az adott időtartam alatt fogadott kérések száma 2759-ről 23 565-re nőtt.
Emellett a WorkInBackground
vezérlő sokkal nagyobb mennyiségű kérést kezelt. Ebben az esetben azonban nem lehet közvetlen párhuzamot vonni, mivel e vezérlő feladatvégzése teljesen más, mint az eredeti kód. Az új verzió egyszerűen sorba állít egy kérést, ahelyett, hogy elvégezne egy időigényes számítási feladatot. A fő szempont az, hogy ez a metódus már nem csökkenti az egész rendszer teljesítményét nagy terhelés esetén.
A teljesítményjavulás a processzor és a hálózat kihasználtságában is megmutatkozik. A processzor kihasználtsága egyszer sem érte el a 100%-ot, a kezelt hálózati kérések száma a korábbinál sokkal nagyobb volt, és nem csökkent a számítási feladat befejezéséig.
A következő grafikon egy terhelési teszt eredményeit mutatja. A kiszolgált kérések teljes mennyisége nagymértékben nőtt a korábbi tesztekhez képest.
Kapcsolódó útmutatók
Visszajelzés
https://aka.ms/ContentUserFeedback.
Hamarosan elérhető: 2024-ben fokozatosan kivezetjük a GitHub-problémákat a tartalom visszajelzési mechanizmusaként, és lecseréljük egy új visszajelzési rendszerre. További információ:Visszajelzés küldése és megtekintése a következőhöz: