Entiteitsfuncties
Met entiteitsfuncties worden bewerkingen gedefinieerd voor het lezen en bijwerken van kleine stukjes status, ook wel duurzame entiteiten genoemd. Entiteitsfuncties zijn, net zoals orchestratorfuncties, functies met een speciaal type trigger: de entiteitstrigger. In tegenstelling tot orchestratorfuncties, wordt met entiteitsfuncties de status van een entiteit expliciet beheerd, in plaats van de status impliciet te bepalen via de controlestroom. Entiteiten bieden een manier om toepassingen uit te schalen door het werk over een groot aantal entiteiten te verdelen, elk met een status van bescheiden grootte.
Notitie
Entiteitsfuncties en gerelateerde functionaliteit zijn alleen beschikbaar in Durable Functions 2.0 en hoger. Ze worden momenteel ondersteund in .NET in-proc, .NET geïsoleerde werkrol, JavaScript en Python, maar niet in PowerShell of Java.
Belangrijk
Entiteitsfuncties worden momenteel niet ondersteund in PowerShell en Java.
Algemene concepten
Entiteiten gedragen zich een beetje als kleine services die communiceren via berichten. Elke entiteit heeft een unieke id en een interne status (indien aanwezig). Net zoals services of objecten voeren entiteiten bewerkingen uit wanneer daarom wordt gevraagd. Wanneer een bewerking wordt uitgevoerd, kan de interne status van de entiteit worden bijgewerkt. Er kunnen ook externe services worden aanroepen en er kan worden gewacht op een reactie. Entiteiten communiceren met andere entiteiten, orchestrations en clients door gebruik te maken van berichten die impliciet worden verzonden via betrouwbare wachtrijen.
Om conflicten te voorkomen, worden alle bewerkingen op een entiteit serieel (dat wil zeggen, een voor een) uitgevoerd.
Notitie
Wanneer een entiteit wordt aangeroepen, wordt de nettolading verwerkt tot voltooiing en wordt vervolgens een nieuwe uitvoering gepland om te activeren zodra toekomstige invoer binnenkomt. Als gevolg hiervan kunnen de uitvoeringslogboeken van uw entiteit een extra uitvoering weergeven na elke aanroep van de entiteit; dit wordt verwacht.
Entiteits-id
Entiteiten worden geopend via een unieke id, de entiteits-id. Een entiteits-id bestaat uit een paar tekenreeksen waarmee een exemplaar van een entiteit uniek wordt geïdentificeerd. Het bestaat uit:
- Een entiteitsnaam, een naam die het soort entiteit aanduidt. Een voorbeeld is 'Teller'. Deze naam moet overeenkomen met de naam van de entiteitsfunctie waarmee de entiteit wordt geïmplementeerd. De naam is niet hoofdlettergevoelig.
- Een entiteitssleutel, een tekenreeks waarmee de entiteit uniek wordt geïdentificeerd onder alle andere entiteiten met dezelfde naam. Een voorbeeld hiervan is een GUID.
Zo kan de entiteit Counter
worden gebruikt om de score in een online spel bij te houden. Elk exemplaar van het spel heeft een unieke entiteits-id, zoals @Counter@Game1
en @Counter@Game2
. Voor alle bewerkingen die gericht zijn op een bepaalde entiteit, moet een entiteits-id als parameter worden opgegeven.
Entiteitsbewerkingen
Als u een bewerking voor een entiteit wilt aanroepen, geeft u het volgende op:
- Entiteits-id van de doelentiteit.
- Bewerkingsnaam, een tekenreeks die aangeeft welke bewerking moet worden uitgevoerd. De entiteit
Counter
kan bijvoorbeeld de bewerkingenadd
,get
enreset
ondersteunen. - Invoer voor bewerking, een optionele invoerparameter voor de bewerking. De bewerking 'add' (toevoegen) kan bijvoorbeeld een geheel getal als invoer hebben.
- Geplande tijd, een optionele parameter voor het opgeven van de uitvoeringstijd van de bewerking. Een bewerking kan bijvoorbeeld betrouwbaar worden gepland om over enkele dagen te worden uitgevoerd.
Bewerkingen kunnen een waarde of een fout als resultaat retourneren, zoals een JavaScript-fout of een .NET-uitzondering. Dit resultaat of deze fout treedt op in indelingen die de bewerking worden genoemd.
Een entiteitsbewerking kan ook de status van de entiteit maken, lezen, bijwerken en verwijderen. De status van de entiteit wordt altijd persistent bewaard in de opslag.
Entiteiten definiëren
U definieert entiteiten met behulp van een syntaxis op basis van een functie, waarbij entiteiten worden weergegeven als functies en bewerkingen expliciet worden verzonden door de toepassing.
Er zijn momenteel twee afzonderlijke API's voor het definiëren van entiteiten in .NET:
Wanneer u een syntaxis op basis van een functie gebruikt, worden entiteiten weergegeven als functies en worden bewerkingen expliciet verzonden door de toepassing. Deze syntaxis is handig voor entiteiten met een eenvoudige status, weinig bewerkingen of een dynamische set bewerkingen, zoals in toepassingsframeworks. Deze syntaxis kan lastig te onderhouden zijn, omdat typefouten niet worden gedetecteerd tijdens het compileren.
De specifieke API's zijn afhankelijk van of uw C#-functies worden uitgevoerd in een geïsoleerd werkproces (aanbevolen) of in hetzelfde proces als de host.
De volgende code is een voorbeeld van een eenvoudige Counter
-entiteit die als een duurzame functie is geïmplementeerd. Deze functie definieert drie bewerkingen, add
, reset
en get
, die zijn gericht op de status van een geheel getal.
[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;
}
}
Zie Op functies gebaseerde syntaxis voor meer informatie over op functies gebaseerde syntaxis en hoe u deze kunt gebruiken.
Duurzame entiteiten zijn beschikbaar in JavaScript vanaf versie 1.3.0 van het npm-pakket durable-functions
. De volgende code is de Counter
-entiteit die als een duurzame functie is geïmplementeerd en is geschreven in JavaScript.
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;
}
});
Notitie
Raadpleeg de Ontwikkelaarshandleiding voor Python van Azure Functions voor meer informatie over de werking van het V2-model.
De volgende code is de Counter
entiteit die is geïmplementeerd als een duurzame functie die is geschreven in Python.
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)
Toegang tot entiteiten
U kunt toegang krijgen tot entiteiten via eenrichtings- of tweerichtingscommunicatie. De volgende terminologie maakt onderscheid tussen de volgende twee vormen van communicatie:
- Bij het aanroepen van een entiteit wordt tweerichtingscommunicatie (round-trip) gebruikt. U verzendt een bewerkingsbericht naar de entiteit en wacht vervolgens op het antwoordbericht voordat u doorgaat. In het antwoordbericht kan een waarde of een fout als resultaat worden geretourneerd, zoals een JavaScript-fout of een .NET-uitzondering. Het resultaat of de fout wordt vervolgens waargenomen door de oproepende functie.
- Bij het signaleren van een entiteit wordt eenrichtingscommunicatie (fire and forget) gebruikt. U verzendt een bewerkingsbericht, maar u hoeft niet te wachten op een antwoord. Het bericht wordt gegarandeerd uiteindelijk bezorgd, maar de afzender weet niet wanneer en er wordt geen waarde of fout als resultaat geretourneerd.
Entiteiten zijn toegankelijk via clientfuncties, orchestratorfuncties of entiteitsfuncties. Niet alle vormen van communicatie worden ondersteund in elke context:
- Vanuit clients kunt u een signaal naar entiteiten verzenden en de status van de entiteit lezen.
- Vanuit orchestrations kunt u een signaal naar entiteiten verzenden en entiteiten aanroepen.
- Vanuit entiteiten kunt u een signaal naar entiteiten verzenden.
In de volgende voorbeelden ziet u de verschillende manieren waarop u toegang tot entiteiten kunt krijgen.
Voorbeeld: Client geeft een entiteit aan
Als u toegang wilt tot entiteiten vanuit een gewone Azure-functie, ook wel een clientfunctie genoemd, gebruikt u de clientbinding voor de entiteit. In het volgende voorbeeld ziet u een door de wachtrij geactiveerde functie waarmee een signaal naar een entiteit wordt verzonden met behulp van deze binding.
Notitie
In de volgende voorbeelden wordt de syntaxis met losse typen voor toegang tot entiteiten weergegeven. Over het algemeen raden we u aan om toegang tot entiteiten via interfaces te gebruiken, omdat het meer mogelijkheden voor typecontrole biedt.
[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)
De term signaleren staat voor het asynchroon aanroepen van de entiteits-API met eenrichtingscommunicatie. Bij een clientfunctie is het niet mogelijk om te weten wanneer de bewerking door de entiteit is verwerkt. Er kunnen ook geen resultaatwaarden of uitzonderingen worden waargenomen.
Voorbeeld: Client leest een entiteitsstatus
Met een clientfunctie kan ook de status van een entiteit worden opgevraagd, zoals u kunt zien in het volgende voorbeeld:
[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)
Entiteitsstatusquery's worden verzonden naar het Durable-opslagarchief en retourneren de laatste persistente status van de entiteit. Deze status is altijd een 'doorgevoerde' status. Dat wil zeggen dat er nooit een tijdelijke tussenliggende status wordt aangenomen tijdens het uitvoeren van een bewerking. Het is echter mogelijk dat deze status is verlopen ten opzichte van de status in het geheugen van de entiteit. Alleen orchestrations kunnen de status in het geheugen van een entiteit lezen, zoals wordt beschreven in de volgende sectie.
Voorbeeld: Indelingssignalen en roept een entiteit aan
Orchestratorfuncties hebben toegang tot entiteiten met behulp van API's voor de orchestration-triggerbinding. De volgende voorbeeldcode toont een orchestratorfunctie waarmee een Counter
-entiteit wordt aangeroepen en gesignaleerd.
[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");
});
Notitie
JavaScript biedt momenteel geen ondersteuning voor het signaleren van een entiteit vanuit een orchestrator. Gebruik in plaats daarvan callEntity
.
@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
Alleen met orchestrations kan een entiteit worden aangeroepen en een reactie worden verkregen, wat een geretourneerde waarde of een uitzondering kan zijn. Clientfuncties die gebruikmaken van de clientbinding kunnen alleen entiteiten signaleren.
Notitie
Het aanroepen van een entiteit vanuit een orchestratorfunctie is vergelijkbaar met het aanroepen van een activiteitsfunctie vanuit een orchestratorfunctie. Het belangrijkste verschil is dat entiteitsfuncties duurzame objecten zijn met een adres, wat de entiteits-id is. Entiteitsfuncties bieden ondersteuning voor het opgeven van een bewerkingsnaam. Activiteitsfuncties zijn daarentegen staatloos en kennen niet het concept van bewerkingen.
Voorbeeld: Entiteit geeft een entiteit aan
Een entiteitsfunctie kan signalen verzenden naar andere entiteiten, of zelfs naar zichzelf, terwijl er een bewerking wordt uitgevoerd.
Zo kunnen we het voorbeeld van de vorige Counter
-entiteit aanpassen zodat er een 'mijlpaal bereikt'-signaal wordt verzonden naar een bepaalde controle-entiteit wanneer de teller de waarde 100 bereikt.
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;
Notitie
Python biedt nog geen ondersteuning voor entiteit-naar-entiteitssignalen. Gebruik in plaats daarvan een orchestrator voor het signaleren van entiteiten.
Entiteitscoördinatie
Het kan voorkomen dat u bewerkingen tussen meerdere entiteiten moet coördineren. In een toepassing voor bankieren kunt u bijvoorbeeld entiteiten voor afzonderlijke bankrekeningen hebben. Wanneer u geld van de ene naar de andere rekening overmaakt, moet u controleren of het saldo op de bronrekening hoog genoeg is. U moet er ook voor zorgen dat wijzigingen in de bron- en doelrekening op consistente wijze worden verwerkt.
Voorbeeld: Geld overdragen
Met de volgende voorbeeldcode wordt geld tussen twee rekeningentiteiten overgeboekt met behulp van een orchestratorfunctie. Voor het coördineren van entiteitswijzigingen moet u de methode LockAsync
gebruiken om een kritieke sectie in de orchestration te maken.
Notitie
Voor het gemak wordt in dit voorbeeld de eerder gedefinieerde Counter
-entiteit opnieuw gebruikt. In een echte toepassing is het beter om een gedetailleerde entiteit zoals BankAccount
(Bankrekening) te definiëren.
// 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;
}
}
}
In .NET retourneert LockAsync
IDisposable
, waarmee de kritieke sectie wordt beëindigd wanneer deze wordt verwijderd. Dit IDisposable
-resultaat kan worden gebruikt in combinatie met een using
-blok om een syntactische weergave van de kritieke sectie te krijgen.
In het vorige voorbeeld draagt een orchestratorfunctie fondsen over van een bronentiteit naar een doelentiteit. De entiteiten van de bron- en doelrekening zijn vergrendeld door de methode LockAsync
. Deze vergrendeling heeft ervoor gezorgd dat een andere client de status van de rekeningen niet kan opvragen of wijzigen, totdat de orchestrationlogica de kritieke sectie aan het einde van de using
-instructie heeft verlaten. Hiermee wordt voorkomen dat er te veel geld van de bronrekening wordt afgeschreven.
Notitie
Wanneer een orchestration wordt beëindigd, normaal of met een fout, worden alle actieve kritieke secties impliciet beëindigd en worden alle vergrendelingen vrijgegeven.
Gedrag van kritieke secties
Met de methode LockAsync
wordt een kritieke sectie in een orchestration gemaakt. Met deze kritieke secties kunt u voorkomen dat andere orchestrations overlappende wijzigingen aanbrengen in een bepaalde set entiteiten. Intern verzendt de LockAsync
-API vergrendelingsbewerkingen naar de entiteiten en wordt een antwoordbericht geretourneerd als de vergrendeling op elk van deze entiteiten is verkregen. Vergrendelen en ontgrendelen zijn ingebouwde bewerkingen die door alle entiteiten worden ondersteund.
Er zijn geen bewerkingen van andere clients toegestaan op een entiteit die de status 'vergrendeld' heeft. Dit gedrag zorgt ervoor dat elke entiteit slechts kan worden vergrendeld door één orchestrationexemplaar tegelijk. Als een oproepende functie probeert een bewerking aan te roepen voor een entiteit terwijl deze is vergrendeld door een orchestration, wordt die bewerking geplaatst in een wachtrij voor bewerkingen die in behandeling zijn. Wachtende bewerkingen worden pas verwerkt als de vergrendeling is vrijgegeven.
Notitie
Dit gedrag wijkt enigszins af van synchronisatieprimitieven die worden gebruikt in de meeste programmeertalen, zoals de lock
-instructie in C#. In C# moet bijvoorbeeld de lock
-instructie door alle threads worden gebruikt om de juiste synchronisatie tussen meerdere threads te garanderen. Voor entiteiten hoeven echter niet alle oproepende functies expliciet een entiteit te vergrendelen. Als een oproepende functie een entiteit vergrendelt, worden alle andere bewerkingen op die entiteit geblokkeerd en achter die vergrendeling in de wachtrij geplaatst.
Vergrendelingen op entiteiten zijn duurzaam zodat ze behouden blijven, zelfs als het uitvoerende proces wordt gerecycled. Vergrendelingen worden intern persistent gemaakt als onderdeel van de duurzame status van een entiteit.
In tegenstelling tot transacties worden belangrijke secties niet automatisch teruggedraaid wanneer er fouten optreden. In plaats daarvan moet elke foutafhandeling, zoals terugdraaien of opnieuw proberen, expliciet worden gecodeerd, bijvoorbeeld door fouten of uitzonderingen te ondervangen. Dit is een opzettelijke ontwerpkeuze. Het automatisch terugdraaien van de effecten van een orchestration is in het algemeen moeilijk of onmogelijk, omdat met orchestrations mogelijk activiteiten en aanroepen naar externe services worden uitgevoerd die niet kunnen worden teruggedraaid. Ook terugdraaipogingen kunnen mislukken en verdere foutafhandeling vereisen.
Regels voor kritieke secties
In tegenstelling tot vergrendelingsprimitieven op laag niveau in de meeste programmeertalen, raken kritieke secties gegarandeerd niet in een impasse. Om impasses te voorkomen, dwingen we de volgende beperkingen af:
- Kritieke secties kunnen niet worden genest.
- In kritieke secties kunnen geen suborchestrations worden gemaakt.
- Met kritieke secties kunnen alleen entiteiten worden aangeroepen die zijn vergrendeld.
- Met kritieke secties kan niet dezelfde entiteit worden aangeroepen met meerdere parallelle aanroepen.
- Met kritieke secties kunnen alleen entiteiten worden gesignaleerd die niet zijn vergrendeld.
Eventuele schendingen van deze regels veroorzaken een runtime-fout, zoals LockingRulesViolationException
in .NET, die een bericht bevat waarin wordt uitgelegd welke regel is verbroken.
Vergelijking met virtuele actors
Veel functies van duurzame entiteiten zijn geïnspireerd op het actor-model. Als u al bekend bent met actors, herkent u waarschijnlijk veel van de concepten die in dit artikel worden beschreven. Duurzame entiteiten zijn vergelijkbaar met virtuele actoren of korrels, zoals populair gemaakt door het Orleans-project. Bijvoorbeeld:
- Duurzame entiteiten zijn adresseerbaar via een entiteits-id.
- Bewerkingen van duurzame entiteiten worden serieel, één voor één, uitgevoerd om racevoorwaarden te voorkomen.
- Duurzame entiteiten worden impliciet gemaakt wanneer ze worden aangeroepen of gesignaleerd.
- Duurzame entiteiten worden op de achtergrond uit het geheugen verwijderd wanneer er geen bewerkingen worden uitgevoerd.
Er vallen echter enkele belangrijke verschillen op te merken:
- Bij duurzame entiteiten gaat duurzaamheid boven latentie. Ze zijn dus mogelijk niet geschikt voor toepassingen met strikte latentievereisten.
- Duurzame entiteiten hebben geen ingebouwde time-outs voor berichten. In Orleans treedt er altijd een time-out op voor berichten na een configureerbare tijd. De standaardwaarde is 30 seconden.
- Berichten die tussen entiteiten worden verzonden, worden betrouwbaar en op volgorde bezorgd. In Orleans wordt een betrouwbare of bestelde levering ondersteund voor inhoud die via stromen wordt verzonden, maar niet gegarandeerd voor alle berichten tussen korrels.
- Aanvraag-antwoordpatronen in entiteiten zijn beperkt tot orchestrations. Vanuit entiteiten is alleen eenrichtingscommunicatie (ook wel signalering genoemd) toegestaan, zoals in het oorspronkelijke actor-model en niet zoals bij korrels in Orleans.
- Duurzame entiteiten kunnen niet in een impasse raken. In Orleans kunnen impasses optreden die pas worden opgelost na een time-out van berichten.
- Duurzame entiteiten kunnen worden gebruikt met duurzame indelingen en ondersteuning bieden voor gedistribueerde vergrendelingsmechanismen.