Partage via


Comment personnaliser le classement des workers pour obtenir le meilleur mode de distribution

Le mode de distribution best-worker sélectionne les workers qui sont les plus à même de gérer le travail en premier. Il est possible de personnaliser la logique de classement des collaborateurs, avec une expression ou une fonction Azure permettant d’en comparer deux. L’exemple suivant montre comment personnaliser cette logique avec votre propre fonction Azure.

Scénario : Règle de score personnalisée dans le mode de distribution BestWorker

Nous voulons distribuer les offres entre les Workers associés à une file d’attente. Les Workers reçoivent un score en fonction de leurs étiquettes et de leurs compétences. Le Worker qui détient le score le plus élevé doit obtenir la première offre (mode de distribution BestWorker).

Diagram showing Best Worker Distribution Mode problem statement

Situation

  • Un travail a été créé et classé.
    • Le travail a les étiquettes suivantes associées à celui-ci
      • ["CommunicationType"] = "Chat"
      • ["IssueType"] = "XboxSupport"
      • ["Language"] = "en"
      • ["HighPriority"] = true
      • ["SubIssueType"] = "ConsoleMalfunction"
      • ["ConsoleType"] = "XBOX_SERIES_X"
      • ["Model"] = "XBOX_SERIES_X_1TB"
    • Le travail a les WorkerSelectors suivants associés à celui-ci
      • ["English"] >= 7
      • ["ChatSupport"] = true
      • ["XboxSupport"] = true
  • Le travail se trouve actuellement à l’état « En file d’attente » dans Xbox Hardware Support Queue (File d’attente du support matériel Xbox) pour être mis en correspondance avec un Worker.
  • Plusieurs Workers deviennent disponibles simultanément.
    • Le Worker 1 a été créé avec les étiquettes suivantes :
      • ["HighPrioritySupport"] = true
      • ["HardwareSupport"] = true
      • ["Support_XBOX_SERIES_X"] = true
      • ["English"] = 10
      • ["ChatSupport"] = true
      • ["XboxSupport"] = true
    • Le Worker 2 a été créé avec les étiquettes suivantes :
      • ["HighPrioritySupport"] = true
      • ["HardwareSupport"] = true
      • ["Support_XBOX_SERIES_X"] = true
      • ["Support_XBOX_SERIES_S"] = true
      • ["English"] = 8
      • ["ChatSupport"] = true
      • ["XboxSupport"] = true
    • Le Worker 3 a été créé avec les étiquettes suivantes :
      • ["HighPrioritySupport"] = false
      • ["HardwareSupport"] = true
      • ["Support_XBOX"] = true
      • ["English"] = 7
      • ["ChatSupport"] = true
      • ["XboxSupport"] = true

Expectation

Nous souhaitons obtenir le comportement suivant lors de l’attribution de scores aux Workers afin de sélectionner celui qui obtient la première offre.

Decision flow diagram for scoring worker

Le flux de décision (cf. ci-dessus) est le suivant :

  • Si un travail n’est PAS HighPriority :

    • Workers avec étiquette : ["Support_XBOX"] = true obtiennent un score de 100
    • Sinon, obtiennent un score de 1
  • Si un travail est HighPriority :

    • Workers avec étiquette : ["HighPrioritySupport"] = false obtiennent un score de 1
    • Sinon, si ["HighPrioritySupport"] = true :
      • Le Worker est-il spécialisé dans un type de console ? -> Workers avec étiquette : ["Support_<jobLabels.ConsoleType>"] = true obtiennent un score de 200
      • Sinon, obtiennent un score de 100

Création d’une fonction Azure

Avant d’aller plus loin dans le processus, commençons par définir une fonction Azure qui évalue le Worker.

Remarque

La fonction Azure suivante utilise JavaScript. Pour plus d’informations, consultez Démarrage rapide : Création d’une fonction JavaScript dans Azure à l’aide de Visual Studio Code.

Exemple d’entrée pour Worker 1 :

{
  "job": {
    "CommunicationType": "Chat",
    "IssueType": "XboxSupport",
    "Language": "en",
    "HighPriority": true,
    "SubIssueType": "ConsoleMalfunction",
    "ConsoleType": "XBOX_SERIES_X",
    "Model": "XBOX_SERIES_X_1TB"
  },
  "selectors": [
    {
      "key": "English",
      "operator": "GreaterThanEqual",
      "value": 7,
      "expiresAfterSeconds": null
    },
    {
      "key": "ChatSupport",
      "operator": "Equal",
      "value": true,
      "expiresAfterSeconds": null
    },
    {
      "key": "XboxSupport",
      "operator": "Equal",
      "value": true,
      "expiresAfterSeconds": null
    }
  ],
  "worker": {
    "Id": "e3a3f2f9-3582-4bfe-9c5a-aa57831a0f88",
    "HighPrioritySupport": true,
    "HardwareSupport": true,
    "Support_XBOX_SERIES_X": true,
    "English": 10,
    "ChatSupport": true,
    "XboxSupport": true
  }
}

Exemple d’implémentation :

module.exports = async function (context, req) {
    context.log('Best Worker Distribution Mode using Azure Function');

    let score = 0;
    const jobLabels = req.body.job;
    const workerLabels = req.body.worker;

    const isHighPriority = !!jobLabels["HighPriority"];
    context.log('Job is high priority? Status: ' + isHighPriority);

    if(!isHighPriority) {
        const isGenericXboxSupportWorker = !!workerLabels["Support_XBOX"];
        context.log('Worker provides general xbox support? Status: ' + isGenericXboxSupportWorker);

        score = isGenericXboxSupportWorker ? 100 : 1;

    } else {
        const workerSupportsHighPriorityJob = !!workerLabels["HighPrioritySupport"];
        context.log('Worker provides high priority support? Status: ' + workerSupportsHighPriorityJob);

        if(!workerSupportsHighPriorityJob) {
            score = 1;
        } else {
            const key = `Support_${jobLabels["ConsoleType"]}`;
            
            const workerSpecializeInConsoleType = !!workerLabels[key];
            context.log(`Worker specializes in consoleType: ${jobLabels["ConsoleType"]} ? Status: ${workerSpecializeInConsoleType}`);

            score = workerSpecializeInConsoleType ? 200 : 100;
        }
    }
    context.log('Final score of worker: ' + score);

    context.res = {
        // status: 200, /* Defaults to 200 */
        body: score
    };
}

Sortie pour Worker 1 :

200

Avec l’implémentation ci-dessus pour le travail donné, nous obtenons les scores suivants pour les Workers :

Worker Score
Worker 1 200
Worker 2 200
Worker 3 1

Distribution d’offres selon le mode BestWorker

Maintenant que l’application de fonction Azure est prête, créons une instance du mode de distribution BestWorker à l’aide du kit de développement logiciel (SDK, Software Development Kit) du routeur.

var administrationClient = new JobRouterAdministrationClient("<YOUR_ACS_CONNECTION_STRING>");

// Setup Distribution Policy
var distributionPolicy = await administrationClient.CreateDistributionPolicyAsync(
    new CreateDistributionPolicyOptions(
        distributionPolicyId: "BestWorkerDistributionMode",
        offerExpiresAfter: TimeSpan.FromMinutes(5),
        mode: new BestWorkerMode(scoringRule: new FunctionRouterRule(new Uri("<insert function url>")))
    ) { Name = "XBox hardware support distribution" });

// Setup Queue
var queue = await administrationClient.CreateQueueAsync(
    new CreateQueueOptions(
        queueId: "XBox_Hardware_Support_Q",
        distributionPolicyId: distributionPolicy.Value.Id
    ) { Name = "XBox Hardware Support Queue" });

// Create workers
var worker1 = await client.CreateWorkerAsync(new CreateWorkerOptions(workerId: "Worker_1", capacity: 100)
    {
        Queues = { queue.Value.Id },
        Channels = { new RouterChannel(channelId: "Xbox_Chat_Channel", capacityCostPerJob: 10) },
        Labels =
        {
            ["English"] = new RouterValue(10),
            ["HighPrioritySupport"] = new RouterValue(true),
            ["HardwareSupport"] = new RouterValue(true),
            ["Support_XBOX_SERIES_X"] = new RouterValue(true),
            ["ChatSupport"] = new RouterValue(true),
            ["XboxSupport"] = new RouterValue(true)
        }
    });

var worker2 = await client.CreateWorkerAsync(new CreateWorkerOptions(workerId: "Worker_2", capacity: 100)
    {
        Queues = { queue.Value.Id },
        Channels = { new RouterChannel(channelId: "Xbox_Chat_Channel", capacityCostPerJob: 10) },
        Labels =
        {
            ["English"] = new RouterValue(8),
            ["HighPrioritySupport"] = new RouterValue(true),
            ["HardwareSupport"] = new RouterValue(true),
            ["Support_XBOX_SERIES_X"] = new RouterValue(true),
            ["ChatSupport"] = new RouterValue(true),
            ["XboxSupport"] = new RouterValue(true)
        }
    });

var worker3 = await client.CreateWorkerAsync(new CreateWorkerOptions(workerId: "Worker_3", capacity: 100)
    {
        Queues = { queue.Value.Id },
        Channels = { new RouterChannel(channelId: "Xbox_Chat_Channel", capacityCostPerJob: 10) },
        Labels =
        {
            ["English"] = new RouterValue(7),
            ["HighPrioritySupport"] = new RouterValue(true),
            ["HardwareSupport"] = new RouterValue(true),
            ["Support_XBOX_SERIES_X"] = new RouterValue(true),
            ["ChatSupport"] = new RouterValue(true),
            ["XboxSupport"] = new RouterValue(true)
        }
    });

// Create Job
var job = await client.CreateJobAsync(
    new CreateJobOptions(jobId: "job1", channelId: "Xbox_Chat_Channel", queueId: queue.Value.Id)
    {
        Priority = 100,
        ChannelReference = "ChatChannel",
        RequestedWorkerSelectors =
        {
            new RouterWorkerSelector(key: "English", labelOperator: LabelOperator.GreaterThanEqual, value: new RouterValue(7)),
            new RouterWorkerSelector(key: "ChatSupport", labelOperator: LabelOperator.Equal, value: new RouterValue(true)),
            new RouterWorkerSelector(key: "XboxSupport", labelOperator: LabelOperator.Equal, value: new RouterValue(true))
        },
        Labels =
        {
            ["CommunicationType"] = new RouterValue("Chat"),
            ["IssueType"] = new RouterValue("XboxSupport"),
            ["Language"] = new RouterValue("en"),
            ["HighPriority"] = new RouterValue(true),
            ["SubIssueType"] = new RouterValue("ConsoleMalfunction"),
            ["ConsoleType"] = new RouterValue("XBOX_SERIES_X"),
            ["Model"] = new RouterValue("XBOX_SERIES_X_1TB")
        }
    });

// Wait a few seconds and see which worker was matched
await Task.Delay(TimeSpan.FromSeconds(5));
var getJob = await client.GetJobAsync(job.Value.Id);
Console.WriteLine(getJob.Value.Assignments.Select(assignment => assignment.Value.WorkerId).First());

Sortie

Worker_1 // or Worker_2

Since both workers, Worker_1 and Worker_2, get the same score of 200,
the worker who has been idle the longest will get the first offer.