#parallel-processing #botframework #azure-cosmosdb #etag
#параллельная обработка #botframework #azure-cosmosdb #etag
Вопрос:
У меня есть несколько ботов, обращающихся к CosmosDB. Они получают идентификатор пользователя через параметр URL от клиента.
Моя проблема возникает при использовании одного и того же бота в разных экземплярах одновременно.
Допустим, я начинаю разговор с идентификатора пользователя «1», ввожу некоторые данные, такие как имя, возраст, пол. В середине разговора я открываю другой веб-сайт с идентификатором пользователя «2». Пользователь 2 вводит имя. Пользователь 1 завершает создание профиля. Пользователю 2 предлагается указать возраст. Внезапно идентификатор пользователя от пользователя 2 меняется на «1», а данные профиля идентичны пользовательским данным от пользователя 1.
Время от времени я получаю следующую ошибку:
{ code: 412,
body: ‘{“code”:“PreconditionFailed”,“message”:“Operation cannot be performed because one of the specified precondition is not met., rnRequestStartTime: 2019-03-07T20:57:19.6998897Z, RequestEndTime: 2019-03-07T20:57:19.7098745Z, Number of regions attempted: 1rnResponseTime: 2019-03-07T20:57:19.7098745Z, StoreResult: StorePhysicalAddress: rntbd://cdb-ms-prod-westeurope1-fd21.documents.azure.com:16817/apps/9bc5d0cc-9b7c-4b1d-9be2-0fa2654271c4/services/3507a423-5215-48ca-995a-8763ec527db8/partitions/940cd512-aa34-4c93-9596-743de41037da/replicas/131964420031154286p/, LSN: 172, GlobalCommittedLsn: 171, PartitionKeyRangeId: 0, IsValid: True, StatusCode: 412, SubStatusCode: 0, RequestCharge: 1.24, ItemLSN: -1, SessionToken: 172, UsingLocalLSN: False, TransportException: null, ResourceType: Document, OperationType: Replacern, Microsoft.Azure.Documents.Common/2.2.0.0”}’,
activityId: ‘fbdaeedb-a73f-4052-bd55-b1417b10583f’ }
Итак, бот, похоже, каким-то образом перепутал идентификаторы пользователей.
Я сохраняю UserState и ConversationState в локальном хранилище, поскольку мне не нужно, чтобы они сохранялись в моей базе данных:
// Local browser storage
const memoryStorageLocal = new MemoryStorage();
// ConversationState and UserState
const conversationState = new ConversationState(memoryStorageLocal);
const userState = new UserState(memoryStorageLocal);
Я получаю идентификатор пользователя следующим образом, который равен параметру URL
for (var idx in turnContext.activity.membersAdded) {
if (turnContext.activity.membersAdded[idx].id !== turnContext.activity.recipient.id) {
console.log("User added");
this.userID = turnContext.activity.membersAdded[idx].id;
}
}
Я читаю и записываю в базу данных следующим образом:
// Read userData object
try {
user = await this.memoryStorage.read([this.userID]);
console.log("User Object read from DB: " util.inspect(user, false, null, true /* enable colors */));
}
catch(e) {
console.log("Reading user data failed");
console.log(e);
}
// If user is new, create UserData object and save it to DB and read it for further use
if(isEmpty(user)) {
await step.context.sendActivity("New User Detected");
this.changes[this.userID] = this.userData;
await this.memoryStorage.write(this.changes);
user = await this.memoryStorage.read([this.userID]);
}
Это «пустой» объект userData, который я сохраняю в базе данных, если пользователь новый:
this.userData = {
name: "",
age: "",
gender: "",
education: "",
major: "",
riskData: {
roundCounter: "",
riskAssessmentComplete: "",
riskDescription: "",
repeat: "",
choices: "",
},
investData: {
repeat: "",
order: "",
choice: "",
follow: "",
win1: "",
win2: "",
loss1: "",
loss2: "",
},
endRepeat: "",
eTag: '*',
}
Мне кажется, что настоящая проблема заключается не в том, как я читаю и записываю из CosmosDB и в CosmosDB, а в том, что не существует разных экземпляров бота, поскольку идентификаторы пользователей и userdata перепутываются при их одновременном использовании.
Как я могу заставить это работать? Вы можете найти код моего бота здесь:
https://github.com/FRANZKAFKA13/roboadvisoryBot
Заранее спасибо за любую помощь!
Ответ №1:
Это потому, что вы храните все в своем конструкторе, который вызывается ботом только один раз. Одни и те же экземпляры this.changes
и this.userId
используются обоими пользователями одновременно. Когда один пользователь изменяет его, он также изменяется для другого пользователя. По сути, это делает их глобальными переменными, которые любой пользователь может изменить.
Вместо этого вы можете передавать нужные вам переменные в диалоговые окна с помощью step.options
.
Например,
Вызов диалогового окна:
await dc.beginDialog('welcome', userId)
Использование в диалоговом окне:
this.userId = step.options // It may also be step.options.userId, if you pass it in as part of a larger object, like .beginDialog('welcome', { userId: '123', userName: 'xyz' })
Примечание
Что касается способа, которым вы изначально пытались это сделать, путем создания экземпляра this.userId
в конструкторе:
На самом деле вам это может сойти с рук в C #, поскольку в C # SDK конструктор вызывается при каждом сообщении (в JS / TS он вызывается только при первом запуске бота). Это основное различие существует между JS-файлами BotFramework и C # SDK, потому что C # многопоточен, а JS — однопоточен. Вызывая конструктор для каждого сообщения на C #, вы можете распределить нагрузку по потокам. Это невозможно в JS / TS.
При этом я бы все равно избегал этого, поскольку в практике кода лучше не использовать «глобальные» переменные, когда это возможно.
Комментарии:
1. Это сработало, большое спасибо! Для других людей, читающих это: вы не должны использовать «это» в диалоговом окне, как описано в примере кода выше, поскольку это приведет к тому же результату, что и мой первый подход. Также, по крайней мере, в моем случае, я получил идентификатор пользователя, который был указан в качестве опции с «userId = step.options», а не «step.options.userId».
2. @FRANZKAFKA Спасибо за дополнительное примечание! Я отредактировал свой ответ, чтобы отразить это. Забавно, что вы действительно можете сделать
this
свой оригинальный способ в C # из-за способа ASP.NET работает. Я отредактирую это в своем ответе для других, как только у меня будет лучшее понимание этого.