Jednoúčelové orchestrátory v Durable Functions (Azure Functions)
Článek
U úloh na pozadí často potřebujete zajistit, aby najednou běžela jenom jedna instance konkrétního orchestrátoru. Tento druh jednoznačného chování v Durable Functions můžete zajistit přiřazením ID konkrétní instance orchestrátoru při jeho vytváření.
Příklad typu Singleton
Následující příklad ukazuje funkci HTTP-trigger, která vytvoří orchestraci úlohy na pozadí s jedním objektem. Kód zajišťuje, že pro zadané ID instance existuje pouze jedna instance.
[FunctionName("HttpStartSingle")]
public static async Task<HttpResponseMessage> RunSingle(
[HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}/{instanceId}")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
string functionName,
string instanceId,
ILogger log)
{
// Check if an instance with the specified ID already exists or an existing one stopped running(completed/failed/terminated).
var existingInstance = await starter.GetStatusAsync(instanceId);
if (existingInstance == null
|| existingInstance.RuntimeStatus == OrchestrationRuntimeStatus.Completed
|| existingInstance.RuntimeStatus == OrchestrationRuntimeStatus.Failed
|| existingInstance.RuntimeStatus == OrchestrationRuntimeStatus.Terminated)
{
// An instance with the specified ID doesn't exist or an existing one stopped running, create one.
dynamic eventData = await req.Content.ReadAsAsync<object>();
await starter.StartNewAsync(functionName, instanceId, eventData);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
else
{
// An instance with the specified ID exists or an existing one still running, don't create one.
return new HttpResponseMessage(HttpStatusCode.Conflict)
{
Content = new StringContent($"An instance with ID '{instanceId}' already exists."),
};
}
}
Poznámka
Předchozí kód jazyka C# je určený pro Durable Functions 2.x. Pro Durable Functions 1.x musíte místo atributu DurableClient použít OrchestrationClient atribut a typ parametru DurableOrchestrationClientIDurableOrchestrationClientmísto . Další informace o rozdílech mezi verzemi najdete v článku Durable Functions verze.
const df = require("durable-functions");
module.exports = async function(context, req) {
const client = df.getClient(context);
const instanceId = req.params.instanceId;
const functionName = req.params.functionName;
// Check if an instance with the specified ID already exists or an existing one stopped running(completed/failed/terminated).
const existingInstance = await client.getStatus(instanceId);
if (!existingInstance
|| existingInstance.runtimeStatus == "Completed"
|| existingInstance.runtimeStatus == "Failed"
|| existingInstance.runtimeStatus == "Terminated") {
// An instance with the specified ID doesn't exist or an existing one stopped running, create one.
const eventData = req.body;
await client.startNew(functionName, instanceId, eventData);
context.log(`Started orchestration with ID = '${instanceId}'.`);
return client.createCheckStatusResponse(req, instanceId);
} else {
// An instance with the specified ID exists or an existing one still running, don't create one.
return {
status: 409,
body: `An instance with ID '${instanceId}' already exists.`,
};
}
};
import logging
import azure.functions as func
import azure.durable_functions as df
async def main(req: func.HttpRequest, starter: str) -> func.HttpResponse:
client = df.DurableOrchestrationClient(starter)
instance_id = req.route_params['instanceId']
function_name = req.route_params['functionName']
existing_instance = await client.get_status(instance_id)
if existing_instance.runtime_status in [df.OrchestrationRuntimeStatus.Completed, df.OrchestrationRuntimeStatus.Failed, df.OrchestrationRuntimeStatus.Terminated, None]:
event_data = req.get_body()
instance_id = await client.start_new(function_name, instance_id, event_data)
logging.info(f"Started orchestration with ID = '{instance_id}'.")
return client.create_check_status_response(req, instance_id)
else:
return {
'status': 409,
'body': f"An instance with ID '${existing_instance.instance_id}' already exists"
}
@FunctionName("HttpStartSingle")
public HttpResponseMessage runSingle(
@HttpTrigger(name = "req") HttpRequestMessage<?> req,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
String instanceID = "StaticID";
DurableTaskClient client = durableContext.getClient();
// Check to see if an instance with this ID is already running
OrchestrationMetadata metadata = client.getInstanceMetadata(instanceID, false);
if (metadata.isRunning()) {
return req.createResponseBuilder(HttpStatus.CONFLICT)
.body("An instance with ID '" + instanceID + "' already exists.")
.build();
}
// No such instance exists - create a new one. De-dupe is handled automatically
// in the storage layer if another function tries to also use this instance ID.
client.scheduleNewOrchestrationInstance("MyOrchestration", null, instanceID);
return durableContext.createCheckStatusResponse(req, instanceID);
}
VE výchozím nastavení jsou ID instancí náhodně generovaná identifikátory GUID. V předchozím příkladu se ale ID instance předává v datech směrování z adresy URL. Kód pak načte metadata instance orchestrace a zkontroluje, jestli je již spuštěná instance se zadaným ID. Pokud žádná taková instance není spuštěná, vytvoří se nová instance s tímto ID.
Poznámka
V této ukázce existuje potenciální konflikt časování. Pokud se současně spustí dvě instance HttpStartSingle , obě volání funkce budou hlásit úspěch, ale ve skutečnosti se spustí pouze jedna instance orchestrace. V závislosti na vašich požadavcích to může mít nežádoucí vedlejší účinky.
Na podrobnostech implementace funkce orchestrátoru ve skutečnosti nezáleží. Může to být běžná funkce orchestrátoru, která začíná a dokončuje, nebo to může být funkce, která běží napořád (tedy věčná orchestrace). Důležité je, že vždy je spuštěná vždy jen jedna instance.