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).
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 a les étiquettes suivantes associées à celui-ci
- 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
- Le Worker 1 a été créé avec les étiquettes suivantes :
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.
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.