Ez a cikk azt ismerteti, hogy egy fejlesztői csapat hogyan használt metrikákat a szűk keresztmetszetek megtalálásához és az elosztott rendszerek teljesítményének javításához. A cikk egy mintaalkalmazás tényleges terheléstesztjén alapul.
Ez a cikk egy sorozat része. Olvassa el az első részt itt.
Forgatókönyv: Események adatfolyamának feldolgozása Azure Functions használatával.
Ebben a forgatókönyvben egy drónflotta valós időben küld pozícióadatokat Azure IoT Hub. A Functions-alkalmazások megkapják az eseményeket, GeoJSON formátumba alakítja át az adatokat, és az átalakított adatokat az Azure Cosmos DB-be írják. Az Azure Cosmos DB natív támogatást nyújt a térinformatikai adatokhoz, és az Azure Cosmos DB-gyűjtemények indexelhetők a hatékony térbeli lekérdezésekhez. Egy ügyfélalkalmazás például lekérdezheti az összes drónt egy adott hely 1 km-es körzetében, vagy megkeresheti az összes drónt egy adott területen belül.
Ezek a feldolgozási követelmények elég egyszerűek ahhoz, hogy ne igényelnek teljes körű streamfeldolgozó motort. A feldolgozás különösen nem kapcsolja össze a streameket, az összesítő adatokat és a folyamatokat az időablakokban. Ezen követelmények alapján a Azure Functions alkalmas az üzenetek feldolgozására. Az Azure Cosmos DB a nagyon magas írási átviteli sebesség támogatásához is méretezhető.
Ez a forgatókönyv érdekes teljesítménnyel kapcsolatos kihívást jelent. Az eszközönkénti adatsebesség ismert, de az eszközök száma ingadozhat. Ebben az üzleti forgatókönyvben a késési követelmények nem különösebben szigorúak. A drón jelentett pozíciójának csak egy percen belül pontosnak kell lennie. Ez azt jelenti, hogy a függvényalkalmazásnak lépést kell tartania az átlagos betöltési gyakorisággal az idő múlásával.
IoT Hub naplóstreamben tárolja az üzeneteket. A bejövő üzenetek a stream széléhez vannak fűzve. A stream egy olvasója – ebben az esetben a függvényalkalmazás – szabályozza a stream bejárásának sebességét. Az olvasási és írási útvonalak leválasztása nagyon hatékonyan IoT Hub, de azt is jelenti, hogy egy lassú olvasó lemaradhat. A feltétel észleléséhez a fejlesztői csapat hozzáadott egy egyéni metrikát az üzenet késésének méréséhez. Ez a metrika rögzíti a különbözetet, amikor egy üzenet megérkezik IoT Hub, és amikor a függvény megkapja az üzenetet feldolgozásra.
var ticksUTCNow = DateTimeOffset.UtcNow;
// Track whether messages are arriving at the function late.
DateTime? firstMsgEnqueuedTicksUtc = messages[0]?.EnqueuedTimeUtc;
if (firstMsgEnqueuedTicksUtc.HasValue)
{
CustomTelemetry.TrackMetric(
context,
"IoTHubMessagesReceivedFreshnessMsec",
(ticksUTCNow - firstMsgEnqueuedTicksUtc.Value).TotalMilliseconds);
}
A TrackMetric
metódus egyéni metrikát ír az Application Insightsba. Az Azure-függvények használatával TrackMetric
kapcsolatos információkért lásd: Egyéni telemetria a C#-függvényben.
Ha a függvény lépést tart az üzenetek mennyiségével, ennek a metrikának alacsony állandó állapotban kell maradnia. Bizonyos késés elkerülhetetlen, ezért az érték soha nem lesz nulla. Ha azonban a függvény lemarad, a lekért idő és a feldolgozási idő közötti eltérés felfelé fog menni.
Az első terhelési teszt azonnali problémát észlelt: A függvényalkalmazás folyamatosan HTTP 429-es hibákat kapott az Azure Cosmos DB-től, ami azt jelzi, hogy az Azure Cosmos DB szabályozta az írási kéréseket.
A csapat válaszul skálázta az Azure Cosmos DB-t a gyűjteményhez lefoglalt kérelemegységek számának növelésével, de a hibák folytatódtak. Ez furcsának tűnt, mert a borítékok hátoldali számítása azt mutatta, hogy az Azure Cosmos DB-nek nem lehet gondja az írási kérelmek mennyiségével.
A nap folyamán az egyik fejlesztő a következő e-mailt küldte a csapatnak:
Megnéztem az Azure Cosmos DB-t a meleg úthoz. Van egy dolog, amit nem értek. A partíciókulcs deliveryId, de nem küldünk kézbesítési azonosítót az Azure Cosmos DB-nek. Hiányzik valami?
Ez volt a nyom. A partíció hőtérképét vizsgálva kiderült, hogy az összes dokumentum ugyanazon a partíción landolt.
Amit látni szeretne a hőtérképen, az az összes partíció egyenletes eloszlása. Ebben az esetben, mivel minden dokumentum ugyanarra a partícióra lett írva, a kérelemegységek hozzáadása nem segített. Kiderült, hogy a probléma hiba a kódban. Bár az Azure Cosmos DB-gyűjtemény rendelkezik partíciókulcsokkal, az Azure-függvény valójában nem tartalmazta a partíciókulcsot a dokumentumban. A partíció hőtérképével kapcsolatos további információkért lásd : Az átviteli sebesség eloszlásának meghatározása a partíciók között.
Amikor a csapat üzembe helyezett egy kódjavítást, és újra futtatta a tesztet, az Azure Cosmos DB leállt a szabályozással. Egy darabig minden jól nézett ki. Egy bizonyos terhelésnél azonban a telemetriai adatok azt mutatták, hogy a függvény kevesebb dokumentumot írt, mint kellene. Az alábbi grafikonon IoT Hub és az Azure Cosmos DB-be írt dokumentumokból érkező üzenetek láthatók. A sárga vonal a fogadott üzenetek száma kötegenként, a zöld pedig a kötegenként írt dokumentumok száma. Ezeknek arányosnak kell lenniük. Ehelyett az adatbázis-írási műveletek száma kötegenként jelentősen csökken 07:30-kor.
A következő grafikon azt mutatja, hogy az üzenet IoT Hub eszközről való érkezésekor és az üzenet a függvényalkalmazás által feldolgozásakor eltelt késést mutatja. Láthatja, hogy ugyanabban az időpontban a késés drámaian megugrik, leesik és csökken.
Ennek az az oka, hogy az érték 5 percnél nagyobb, majd nullára csökken, az az, hogy a függvényalkalmazás elveti az 5 percnél hosszabb üzeneteket:
foreach (var message in messages)
{
// Drop stale messages,
if (message.EnqueuedTimeUtc < cutoffTime)
{
log.Info($"Dropping late message batch. Enqueued time = {message.EnqueuedTimeUtc}, Cutoff = {cutoffTime}");
droppedMessages++;
continue;
}
}
Ezt akkor láthatja a grafikonon, ha a késési metrika nullára csökken. Időközben az adatok elvesztek, mert a függvény üzeneteket küldött.
Mi történt? Ehhez az adott terhelési teszthez az Azure Cosmos DB-gyűjteményhez szükséges kérelemegységek voltak megkímélve, így a szűk keresztmetszet nem volt az adatbázisban. A probléma inkább az üzenetfeldolgozási hurokban volt. Egyszerűen fogalmazva, a függvény nem írt elég gyorsan dokumentumokat ahhoz, hogy lépést tartson a bejövő üzenetek mennyiségével. Idővel egyre tovább csökkent.
Ha az üzenet feldolgozásának ideje szűk keresztmetszet, az egyik megoldás, ha több üzenetet dolgoz fel párhuzamosan. Ebben a forgatókönyvben:
- Növelje a IoT Hub partíciók számát. Minden IoT Hub partícióhoz egyszerre egy függvénypéldány lesz hozzárendelve, ezért elvárjuk, hogy az átviteli sebesség lineárisan skálázható a partíciók számával.
- Párhuzamosítsa a dokumentumírásokat a függvényen belül.
A második lehetőség megismeréséhez a csapat módosította a függvényt a párhuzamos írások támogatásához. A függvény eredeti verziója az Azure Cosmos DB kimeneti kötését használta. Az optimalizált verzió közvetlenül meghívja az Azure Cosmos DB-ügyfelet, és párhuzamosan hajtja végre az írásokat a Task.WhenAll használatával:
private async Task<(long documentsUpserted,
long droppedMessages,
long cosmosDbTotalMilliseconds)>
ProcessMessagesFromEventHub(
int taskCount,
int numberOfDocumentsToUpsertPerTask,
EventData[] messages,
TraceWriter log)
{
DateTimeOffset cutoffTime = DateTimeOffset.UtcNow.AddMinutes(-5);
var tasks = new List<Task>();
for (var i = 0; i < taskCount; i++)
{
var docsToUpsert = messages
.Skip(i * numberOfDocumentsToUpsertPerTask)
.Take(numberOfDocumentsToUpsertPerTask);
// client will attempt to create connections to the data
// nodes on Azure Cosmos DB clusters on a range of port numbers
tasks.Add(UpsertDocuments(i, docsToUpsert, cutoffTime, log));
}
await Task.WhenAll(tasks);
return (this.UpsertedDocuments,
this.DroppedMessages,
this.CosmosDbTotalMilliseconds);
}
Vegye figyelembe, hogy a versenyfeltételek megközelítéssel lehetségesek. Tegyük fel, hogy ugyanabból a drónból két üzenet érkezik ugyanabban az üzenetkötegben. Ha párhuzamosan írja őket, a korábbi üzenet felülírhatja a későbbi üzenetet. Ebben az esetben az alkalmazás elviselheti az alkalmi üzenetek elvesztését. A drónok 5 másodpercenként küldenek új pozícióadatokat, így az Azure Cosmos DB adatai folyamatosan frissülnek. Más esetekben azonban fontos lehet szigorúan a sorrendben feldolgozni az üzeneteket.
A kódmódosítás üzembe helyezése után az alkalmazás több mint 2500 kérést tudott betölteni egy 32 partíciót tartalmazó IoT Hub használatával.
Az általános ügyfélélményt csökkentheti a kiszolgálóoldali agresszív párhuzamosítás. Fontolja meg az Azure Cosmos DB tömeges végrehajtói kódtár használatát (ez a megvalósítás nem jelenik meg), ami jelentősen csökkenti az Azure Cosmos DB-tárolóhoz lefoglalt átviteli sebesség telítettségéhez szükséges ügyféloldali számítási erőforrásokat. A tömeges importálási API-val adatokat író egyetlen szálon futó alkalmazás majdnem tízszer nagyobb írási átviteli sebességet ér el egy többszálú alkalmazáshoz képest, amely párhuzamosan írja az adatokat az ügyfélszámítógép processzorának telítésével.
Ebben a forgatókönyvben a következő szűk keresztmetszeteket azonosítottuk:
- Gyakori elérésű írási partíció az írott dokumentumokban hiányzó partíciókulcs-érték miatt.
- Dokumentumok írása sorosan IoT Hub partíciónként.
A problémák diagnosztizálásához a fejlesztői csapat a következő metrikákra támaszkodott:
- Szabályozott kérések az Azure Cosmos DB-ben.
- Partíció hőtérképe – Partíciónkénti maximális felhasznált kérelemegységek.
- A kapott üzenetek és a létrehozott dokumentumok.
- Üzenet késése.
Teljesítményelhárítók áttekintése