Share via


Entitásfüggvények

Az entitásfüggvények a kis méretű állapotok olvasására és frissítésére szolgáló műveleteket, más néven tartós entitásokat határoznak meg. A vezénylő függvényekhez hasonlóan az entitásfüggvények is speciális triggertípusú függvények, az entitás-eseményindítók. A vezénylő függvényekkel ellentétben az entitásfüggvények explicit módon kezelik az entitás állapotát, nem pedig implicit módon képviselik az állapotot a vezérlési folyamaton keresztül. Az entitások eszközként szolgálnak az alkalmazások horizontális felskálázására azáltal, hogy a munkát több entitás között osztják el, amelyek mindegyike szerény méretű állapotban van.

Megjegyzés:

Az entitásfüggvények és a kapcsolódó funkciók csak a Durable Functions 2.0-s és újabb verziókban érhetők el. Jelenleg a .NET in-proc, a .NET izolált feldolgozója, a JavaScript és a Python támogatja őket, a PowerShellben és a Java-ban azonban nem.

Fontos

Az entitásfüggvények jelenleg nem támogatottak a PowerShellben és a Java-ban.

Általános fogalmak

Az entitások egy kicsit úgy viselkednek, mint az üzeneteken keresztül kommunikáló apró szolgáltatások. Minden entitás egyedi identitással és belső állapotmal rendelkezik (ha létezik). A szolgáltatásokhoz vagy objektumokhoz hasonlóan az entitások is műveleteket hajtanak végre, amikor a rendszer erre kéri. Egy művelet végrehajtásakor előfordulhat, hogy frissíti az entitás belső állapotát. Külső szolgáltatásokat is hívhat, és várhatja a választ. Az entitások megbízható üzenetsorokon keresztül implicit módon küldött üzenetek használatával kommunikálnak más entitásokkal, vezénylésekkel és ügyfelekkel.

Az ütközések elkerülése érdekében az egyetlen entitáson végzett összes művelet garantáltan sorosan, azaz egymás után fut.

Megjegyzés:

Egy entitás meghívásakor feldolgozza a hasznos adatokat a befejezésig, majd ütemez egy új végrehajtást, hogy a jövőbeli bemenetek érkezésekor aktiválódjon. Ennek eredményeképpen előfordulhat, hogy az entitás-végrehajtási naplók további végrehajtást mutatnak az egyes entitások meghívása után; ez várható.

Entitás azonosítója

Az entitások egy egyedi azonosítón, az entitásazonosítón keresztül érhetők el. Az entitásazonosítók egyszerűen egy sztringpárok, amelyek egyedileg azonosítják az entitáspéldányokat. A következő elemekből áll:

  • Entitás neve, amely az entitás típusát azonosító név. Ilyen például a "Számláló". Ennek a névnek meg kell egyeznie az entitást megvalósító entitásfüggvény nevével. Nem érzékeny a kis- és nagybetűkre.
  • Entitáskulcs, amely egy sztring, amely egyedileg azonosítja az entitást az azonos nevű többi entitás között. Ilyen például a GUID.

Például egy Counter entitásfüggvény használható a pontszám online játékban való megtartásához. A játék minden példánya egyedi entitásazonosítóval rendelkezik, például @Counter@Game1 és @Counter@Game2. Az adott entitást megcélzó összes művelethez meg kell adnia egy entitásazonosítót paraméterként.

Entitásműveletek

Egy entitáson végzett művelet meghívásához adja meg a következőket:

  • A cél entitás entitásazonosítója .
  • Művelet neve, amely egy sztring, amely meghatározza a végrehajtandó műveletet. Az entitás például Counter támogathatja adda műveleteket getvagy reset a műveleteket.
  • A művelet bemenete, amely a művelet opcionális bemeneti paramétere. A hozzáadási művelet például egész számokat vehet fel bemenetként.
  • Ütemezett idő, amely nem kötelező paraméter a művelet szállítási idejének megadásához. Egy művelet például megbízhatóan ütemezhető úgy, hogy a jövőben több napig fusson.

A műveletek eredményértéket vagy hibaeredményt, például JavaScript-hibát vagy .NET-kivételt adhatnak vissza. Ez az eredmény vagy hiba a műveletet nevező vezénylésekben fordul elő.

Az entitásművelet emellett létrehozhatja, elolvashatja, frissítheti és törölheti az entitás állapotát. Az entitás állapota mindig tartósan megmarad a tárolóban.

Entitások definiálása

Függvényalapú szintaxissal definiálhat entitásokat, ahol az entitások függvényként jelennek meg, és az alkalmazás explicit módon küldi el a műveleteket.

Jelenleg két különböző API van az entitások definiálása érdekében a .NET-ben:

Függvényalapú szintaxis használata esetén az entitások függvényként jelennek meg, és az alkalmazás explicit módon küldi el a műveleteket. Ez a szintaxis jól működik az egyszerű állapotú, kevés művelettel vagy dinamikus műveletkészlettel rendelkező entitások esetében, például az alkalmazás-keretrendszerekben. Ez a szintaxis nehéz lehet fenntartani, mert fordításkor nem észleli a típushibákat.

Az adott API-k attól függenek, hogy a C#-függvények izolált feldolgozófolyamatban futnak (ajánlott), vagy ugyanabban a folyamatban, mint a gazdagép.

Az alábbi kód egy egyszerű entitásra mutat be példát Counter , amely tartós függvényként van implementálva. Ez a függvény három olyan műveletet határoz meg, addresetgetamelyek mindegyike egész számon működik.

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "reset":
            ctx.SetState(0);
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

A függvényalapú szintaxisról és annak használatáról további információt a Függvényalapú szintaxis című témakörben talál.

A tartós entitások az npm-csomag 1.3.0-s verziójától kezdve érhetők el a durable-functions JavaScriptben. Az alábbi kód a Counter JavaScriptben írt tartós függvényként implementált entitás.

Counter/function.json

{
  "bindings": [
    {
      "name": "context",
      "type": "entityTrigger",
      "direction": "in"
    }
  ],
  "disabled": false
}

Counter/index.js

const df = require("durable-functions");

module.exports = df.entity(function(context) {
    const currentValue = context.df.getState(() => 0);
    switch (context.df.operationName) {
        case "add":
            const amount = context.df.getInput();
            context.df.setState(currentValue + amount);
            break;
        case "reset":
            context.df.setState(0);
            break;
        case "get":
            context.df.return(currentValue);
            break;
    }
});

Megjegyzés:

A V2-modell működésével kapcsolatos további részletekért tekintse meg az Azure Functions Python fejlesztői útmutatóját .

A következő kód a Counter Pythonban írt tartós függvényként implementált entitás.

import azure.functions as func
import azure.durable_functions as df

# Entity function called counter
@myApp.entity_trigger(context_name="context")
def Counter(context):
    current_value = context.get_state(lambda: 0)
    operation = context.operation_name
    if operation == "add":
        amount = context.get_input()
        current_value += amount
    elif operation == "reset":
        current_value = 0
    elif operation == "get":
        context.set_result(current_value)
    context.set_state(current_value)

Access-entitások

Az entitások egyirányú vagy kétirányú kommunikációval érhetők el. A következő terminológia a kommunikáció két formáját különbözteti meg:

  • Az entitás meghívása kétirányú (oda-vissza) kommunikációt használ. Műveletüzenetet küld az entitásnak, majd várja meg a válaszüzenetet a folytatás előtt. A válaszüzenet eredményértéket vagy hibaeredményt adhat meg, például JavaScript-hibát vagy .NET-kivételt. Ezt az eredményt vagy hibát a hívó ezután megfigyeli.
  • Az entitás jelzése egyirányú (tűz és felejtés) kommunikációt használ. Műveletüzenetet küld, de nem vár válaszra. Bár az üzenet végleges kézbesítése garantált, a feladó nem tudja, hogy mikor és nem tudja megfigyelni az eredményértékeket vagy hibákat.

Az entitások az ügyfélfüggvényekből, a vezénylő függvényekből vagy az entitásfüggvényekből érhetők el. A kommunikáció nem minden formáját támogatja minden környezet:

  • Az ügyfeleken belül entitásokat jelezhet, és beolvashatja az entitás állapotát.
  • A vezényléseken belül entitásokat jelezhet, és entitásokat hívhat meg.
  • Az entitások között entitásokat is jelezhet.

Az alábbi példák az entitások elérésének különböző módjait szemléltetik.

Példa: Az ügyfél egy entitást jelez

Az entitások egy szokásos Azure-függvényből, más néven ügyfélfüggvényből való eléréséhez használja az entitásügyfél-kötést. Az alábbi példa egy üzenetsor által aktivált függvényt mutat be, amely egy entitást jelez ezzel a kötéssel.

Megjegyzés:

Az egyszerűség kedvéért az alábbi példák az entitásokhoz való hozzáférés lazán beírt szintaxisát mutatják be. Általában azt javasoljuk, hogy az entitásokat interfészeken keresztül érje el, mert az több típusellenőrzést biztosít.

[FunctionName("AddFromQueue")]
public static Task Run(
    [QueueTrigger("durable-function-trigger")] string input,
    [DurableClient] IDurableEntityClient client)
{
    // Entity operation input comes from the queue message content.
    var entityId = new EntityId(nameof(Counter), "myCounter");
    int amount = int.Parse(input);
    return client.SignalEntityAsync(entityId, "Add", amount);
}
const df = require("durable-functions");

module.exports = async function (context) {
    const client = df.getClient(context);
    const entityId = new df.EntityId("Counter", "myCounter");
    await client.signalEntity(entityId, "add", 1);
};
import azure.functions as func
import azure.durable_functions as df

# An HTTP-Triggered Function with a Durable Functions Client to set a value on a durable entity
@myApp.route(route="entitysetvalue")
@myApp.durable_client_input(client_name="client")
async def http_set(req: func.HttpRequest, client):
    logging.info('Python HTTP trigger function processing a request.')
    entityId = df.EntityId("Counter", "myCounter")
    await client.signal_entity(entityId, "add", 1)
    return func.HttpResponse("Done", status_code=200)

A jel kifejezés azt jelenti, hogy az entitás API-meghívása egyirányú és aszinkron. Az ügyfélfüggvények nem tudják, hogy az entitás mikor dolgozza fel a műveletet. Emellett az ügyfélfüggvény nem figyelhet meg eredményértékeket vagy kivételeket.

Példa: Az ügyfél beolvassa az entitás állapotát

Az ügyfélfüggvények egy entitás állapotát is lekérdezhetik az alábbi példában látható módon:

[FunctionName("QueryCounter")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Function)] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client)
{
    var entityId = new EntityId(nameof(Counter), "myCounter");
    EntityStateResponse<JObject> stateResponse = await client.ReadEntityStateAsync<JObject>(entityId);
    return req.CreateResponse(HttpStatusCode.OK, stateResponse.EntityState);
}
const df = require("durable-functions");

module.exports = async function (context) {
    const client = df.getClient(context);
    const entityId = new df.EntityId("Counter", "myCounter");
    const stateResponse = await client.readEntityState(entityId);
    return stateResponse.entityState;
};
# An HTTP-Triggered Function with a Durable Functions Client to retrieve the state of a durable entity
@myApp.route(route="entityreadvalue")
@myApp.durable_client_input(client_name="client")
async def http_read(req: func.HttpRequest, client):
    entityId = df.EntityId("Counter", "myCounter")
    entity_state_result = await client.read_entity_state(entityId)
    entity_state = "No state found"
    if entity_state_result.entity_exists:
      entity_state = str(entity_state_result.entity_state)
    return func.HttpResponse(entity_state)

Az entitásállapot-lekérdezéseket a rendszer elküldi a Durable tracking store-nak, és visszaadja az entitás legutóbb megőrzött állapotát. Ez az állapot mindig "véglegesített" állapot, vagyis soha nem ideiglenes köztes állapot, amelyet a művelet végrehajtása során feltételeznek. Előfordulhat azonban, hogy ez az állapot elavult az entitás memóriában lévő állapotához képest. Az entitás memóriabeli állapotát csak vezénylések olvashatják el a következő szakaszban leírtak szerint.

Példa: Vezénylési jelek és entitás meghívása

Az Orchestrator-függvények api-kkal férhetnek hozzá az entitásokhoz a vezénylési trigger kötésén. Az alábbi példakód egy entitás meghívását és jelzését végző vezénylő függvényt Counter mutat be.

[FunctionName("CounterOrchestration")]
public static async Task Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var entityId = new EntityId(nameof(Counter), "myCounter");

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await context.CallEntityAsync<int>(entityId, "Get");
    if (currentValue < 10)
    {
        // One-way signal to the entity which updates the value - does not await a response
        context.SignalEntity(entityId, "Add", 1);
    }
}
const df = require("durable-functions");

module.exports = df.orchestrator(function*(context){
    const entityId = new df.EntityId("Counter", "myCounter");

    // Two-way call to the entity which returns a value - awaits the response
    currentValue = yield context.df.callEntity(entityId, "get");
});

Megjegyzés:

A JavaScript jelenleg nem támogatja az entitások vezénylőtől való jelzését. Use callEntity instead.

@myApp.orchestration_trigger(context_name="context")
def orchestrator(context: df.DurableOrchestrationContext):
    entityId = df.EntityId("Counter", "myCounter")
    context.signal_entity(entityId, "add", 3)
    logging.info("signaled entity")
    state = yield context.call_entity(entityId, "get")
    return state

Csak a vezénylések képesek entitásokat meghívni és választ kapni, ami lehet visszatérési érték vagy kivétel. Az ügyfélkötést használó ügyfélfüggvények csak entitásokat jelezhetnek.

Megjegyzés:

Az entitások vezénylőfüggvényekből való meghívása hasonló ahhoz, mint egy tevékenységfüggvény meghívása egy vezénylőfüggvényből . A fő különbség az, hogy az entitásfüggvények olyan tartós objektumok, amelyek címe az entitásazonosító. Az entitásfüggvények támogatják a műveletnév megadását. A tevékenységfüggvények viszont állapot nélküliek, és nem rendelkeznek a műveletek fogalmával.

Példa: Az entitás egy entitást jelez

Az entitásfüggvények jeleket küldhetnek más entitásoknak vagy akár önmagának is, miközben egy műveletet hajtanak végre. Módosíthatjuk például az előző Counter entitás példáját, hogy egy "mérföldkő által elért" jelet küldjön egy figyelési entitásnak, amikor a számláló eléri a 100 értéket.

   case "add":
        var currentValue = ctx.GetState<int>();
        var amount = ctx.GetInput<int>();
        if (currentValue < 100 && currentValue + amount >= 100)
        {
            ctx.SignalEntity(new EntityId("MonitorEntity", ""), "milestone-reached", ctx.EntityKey);
        }

        ctx.SetState(currentValue + amount);
        break;
    case "add":
        const amount = context.df.getInput();
        if (currentValue < 100 && currentValue + amount >= 100) {
            const entityId = new df.EntityId("MonitorEntity", "");
            context.df.signalEntity(entityId, "milestone-reached", context.df.instanceId);
        }
        context.df.setState(currentValue + amount);
        break;

Megjegyzés:

A Python egyelőre nem támogatja az entitások közötti jeleket. Ehelyett használjon vezénylőt az entitások jelzéséhez.

Entitáskoordináció

Előfordulhat, hogy több entitás műveleteit kell koordinálnia. Egy banki alkalmazásban például lehetnek olyan entitások, amelyek egyéni bankszámlákat jelölnek. Amikor pénzt utal át egyik számláról a másikra, győződjön meg arról, hogy a forrásfiók rendelkezik elegendő pénzeszközzel. Arról is gondoskodnia kell, hogy a forrás- és célfiókok frissítése tranzakciós konzisztens módon történjen.

Példa: Pénzátutalás

Az alábbi példakód egy vezénylő függvény használatával két fiókentitással ruház át pénzt. Az entitásfrissítések koordinálásához a LockAsync módszer használatával létre kell hoznia egy kritikus szakaszt a vezénylésben.

Megjegyzés:

Az egyszerűség kedvéért ez a példa újra felhasználja a Counter korábban definiált entitást. Egy valós alkalmazásban jobb lenne részletesebb entitást BankAccount definiálni.

// This is a method called by an orchestrator function
public static async Task<bool> TransferFundsAsync(
    string sourceId,
    string destinationId,
    int transferAmount,
    IDurableOrchestrationContext context)
{
    var sourceEntity = new EntityId(nameof(Counter), sourceId);
    var destinationEntity = new EntityId(nameof(Counter), destinationId);

    // Create a critical section to avoid race conditions.
    // No operations can be performed on either the source or
    // destination accounts until the locks are released.
    using (await context.LockAsync(sourceEntity, destinationEntity))
    {
        ICounter sourceProxy = 
            context.CreateEntityProxy<ICounter>(sourceEntity);
        ICounter destinationProxy =
            context.CreateEntityProxy<ICounter>(destinationEntity);

        int sourceBalance = await sourceProxy.Get();

        if (sourceBalance >= transferAmount)
        {
            await sourceProxy.Add(-transferAmount);
            await destinationProxy.Add(transferAmount);

            // the transfer succeeded
            return true;
        }
        else
        {
            // the transfer failed due to insufficient funds
            return false;
        }
    }
}

A .NET-ben visszaadja IDisposablea LockAsync kritikus szakaszt a megsemmisítéskor. Ez az IDisposable eredmény blokkokkal using együtt használható a kritikus szakasz szintaktikai ábrázolásának lekéréséhez.

Az előző példában egy vezénylő függvény pénzösszegeket ad át egy forrás entitásból egy cél entitásba. A LockAsync metódus zárolta a forrás- és célfiók-entitásokat is. Ez a zárolás biztosította, hogy egyetlen másik ügyfél sem tudta lekérdezni vagy módosítani egyik fiók állapotát sem, amíg a vezénylési logika ki nem lépett a kritikus szakaszból az using utasítás végén. Ez a viselkedés megakadályozza a forrásfiókból történő túllépés lehetőségét.

Megjegyzés:

Ha egy vezénylés általában vagy hiba miatt leáll, a folyamatban lévő kritikus szakaszok implicit módon befejeződnek, és az összes zárolás felszabadul.

Kritikus szakasz viselkedése

A LockAsync metódus kritikus szakaszt hoz létre egy vezénylésben. Ezek a kritikus szakaszok megakadályozzák, hogy más vezénylések egymást átfedő módosításokat hajtsanak végre egy adott entitáskészleten. Az API belsőleg "zárolási" műveleteket küld az entitásoknak, LockAsync és akkor ad vissza, amikor ezektől az entitásoktól kap egy "zárolt" válaszüzenetet. A zárolás és a zárolás feloldása az összes entitás által támogatott beépített műveletek.

Más ügyfelektől származó műveletek nem engedélyezettek egy entitáson, amíg zárolt állapotban van. Ez a viselkedés biztosítja, hogy egyszerre csak egy vezénylési példány zároljon egy entitást. Ha egy hívó megpróbál meghívni egy műveletet egy entitáson, miközben egy vezénylés zárolja, a művelet függőben lévő műveleti üzenetsorba kerül. A függőben lévő műveletek feldolgozása csak azután történik, hogy a holding vezénylés feloldja a zárolását.

Megjegyzés:

Ez a viselkedés kissé eltér a legtöbb programozási nyelvben használt szinkronizálási primitívektől, például a lock C# utasításától. A C#-ban például az lock utasítást minden szálnak használnia kell a több szál közötti megfelelő szinkronizálás biztosításához. Az entitások azonban nem követelik meg, hogy minden hívó explicit módon zároljon egy entitást. Ha bármely hívó zárol egy entitást, az entitás összes többi művelete le lesz tiltva, és a zárolás mögött várólistára kerül.

Az entitások zárolásai tartósak, így akkor is megmaradnak, ha a végrehajtási folyamatot újrafeldolgozzák. A zárolások belsőleg megmaradnak az entitás tartós állapotának részeként.

A tranzakcióktól eltérően a kritikus szakaszok nem hajtanak végre automatikusan módosításokat hibák esetén. Ehelyett minden hibakezelést, például a visszaállítást vagy az újrapróbálkozást explicit módon kell kódolva kezelni, például hibák vagy kivételek befogásával. Ez a kialakítási döntés szándékos. A vezénylés minden hatásának automatikus visszaállítása általában nehéz vagy lehetetlen, mert a vezénylések tevékenységeket futtathatnak, és olyan külső szolgáltatásokhoz fordulhatnak, amelyek nem állíthatók vissza. A visszaállítási kísérletek szintén sikertelenek lehetnek, és további hibakezelést igényelnek.

Kritikus szakaszszabályok

A legtöbb programozási nyelvben az alacsony szintű zárolási primitívekkel ellentétben a kritikus szakaszok garantáltan nem holtpontra kerülnek. A holtpontok elkerülése érdekében a következő korlátozásokat kényszerítjük ki:

  • A kritikus szakaszok nem ágyazhatók be.
  • A kritikus szakaszok nem hozhatnak létre alműhelyeket.
  • A kritikus szakaszok csak a zárolt entitásokat hívhatják meg.
  • A kritikus szakaszok nem hívhatják meg ugyanazt az entitást több párhuzamos hívással.
  • A kritikus szakaszok csak a nem zárolt entitásokat jelezhetik.

A szabályok bármilyen megsértése futásidejű hibát okoz, például LockingRulesViolationException a .NET-ben, amely egy üzenetet tartalmaz, amely ismerteti, hogy mi volt a szabály megszegve.

Összehasonlítás virtuális szereplőkkel

A tartós entitások számos funkcióját az aktormodell ihlette. Ha már ismeri a színészeket, a cikkben ismertetett fogalmak közül számosat felismerhet. A tartós entitások hasonlóak a virtuális szereplőkhöz vagy szemcsékhez, ahogyan azt az Orleans-projekt is népszerűsíti. Például:

  • A tartós entitások egy entitásazonosítón keresztül kezelhetők.
  • A tartós entitásműveletek sorozatosan, egyenként hajtják végre a versenyfeltételeket.
  • A tartós entitások implicit módon jönnek létre, amikor meghívják vagy jelzik őket.
  • A tartós entitások csendesen törlődnek a memóriából, amikor nem hajtanak végre műveleteket.

Vannak fontos különbségek, amelyeket érdemes megjegyezni:

  • A tartós entitások előnyben részesítik a tartósságot a késéssel szemben, ezért előfordulhat, hogy nem megfelelőek a szigorú késési követelményekkel rendelkező alkalmazásokhoz.
  • A tartós entitások nem rendelkeznek beépített időtúllépésekkel az üzenetekhez. Orleansban az összes üzenet egy konfigurálható idő után időtúllépést hajt végre. Az alapértelmezett érték 30 másodperc.
  • Az entitások között küldött üzenetek kézbesítése megbízhatóan és sorrendben történik. Orleansban a streameken keresztül küldött tartalmak esetében a megbízható vagy a megrendelt kézbesítés támogatott, de a szemcsék közötti összes üzenet esetében nem garantált.
  • Az entitásokban a kérés-válasz minták vezénylésekre korlátozódnak. Az entitásokon belül csak egyirányú üzenetkezelés (más néven jelzés) engedélyezett, ahogyan az eredeti színészi modellben, és ellentétben az Orleans-i szemcsékkel.
  • A tartós entitások nem patthelyzetben maradnak. Orleansban holtpontok fordulhatnak elő, és nem oldódnak fel, amíg az üzenetek időtúllépést nem érnek el.
  • A tartós entitások tartós vezénylésekkel és elosztott zárolási mechanizmusok támogatásával használhatók.

További lépések