Я создаю функцию живого чата для своего веб-сайта с помощью AWS API Gateway WebSockets, Lambda, DynamoDB, всего бездействия сервера.
Моя лямбда-функция в основном выглядит следующим образом:
let userHandler = async ({ event, body, uuid, connection, ip, requestID }) => {
switch (body.action) {
case "hello":
// ...
case "register":
// ...
case "ping":
// ...
case "list":
// ...
case "send":
if (!body.msg) return JSONError();
if (!utils.isUniqueRequest(uuid, requestID)) return JSONReply("duplicate");
await utils.addMessageToConversation({
from: uuid,
to: "admin",
msg: body.msg
});
await utils.markUUIDUnread(body.uuid, true);
let sent = await utils.sendResponse({
from: uuid,
to: "admin",
msg: body.msg,
ws
});
return sent ? JSONReply("sent") : JSONReply("sendError");
}
};
Я оставил только путь для отправки кода, потому что именно он вызывает у меня проблемы. Все в кодовой базе является модульным тестированием и работает должным образом в производстве - за исключением этой странной проблемы. Иногда, когда пользователь отправляет сообщение или я им отвечаю, получатель получает два или три эквивалентных ответа подряд. Я проверил всех клиентов, обращающихся к этой конечной точке WebSocket, и их сетевой трафик, и они определенно не отправляют два или три запроса случайно.
Изначально я не знал, что лямбда-функции должны были быть идемпотентными, поскольку они не гарантированно запускаются только один раз. Понимая это, я построил следующую функцию isUniqueRequest
:
let isUniqueRequest = async (uuid, requestID) => {
let Key = {
uuid,
timestamp: 0
};
let lastRequestsServed = await dynamo("get", {
Key,
ProjectionExpression: "lastRequestsServed"
});
if (!lastRequestsServed.Item) return true; // unknown uuid
lastRequestsServed = lastRequestsServed.Item.lastRequestsServed.values;
if (lastRequestsServed.includes(requestID)) return false;
lastRequestsServed.push(requestID); // add new request
if (lastRequestsServed.length >= 6) lastRequestsServed.shift(); // drop the oldest saved id if there are too many
await dynamo("update", {
Key,
UpdateExpression: "SET lastRequestsServed = :newSet",
ExpressionAttributeValues: {
":newSet": module.exports.DynamoDocumentClient.createSet(lastRequestsServed)
}
});
return true;
};
Внутри каждого объекта диалога эта функция обращается к lastRequestsServed
, установленному для проверки, был ли обработан идентификатор запроса текущего события. Он также добавляет идентификатор, если его там еще нет, и проверяет, что одновременно хранится только 5 идентификаторов, удаляя самый старый, если места нет. Эта функция также была проверена модулем / интеграцией и (в тестировании) выполняет то, что я написал.
Нет прямого способа вызвать эту проблему, которую я обнаружил, однако, в одном сеансе с несколькими десятками отправленных сообщений это, кажется, всегда происходит.
Так что теперь я в растерянности, почему это так. Я думаю, что это может быть связано с тем фактом, что новая лямбда запускается для ответа на входящие запросы, если текущий занят, и каким-то образом возникает состояние гонки, при котором обе лямбды получают доступ к элементу разговора одновременно, прежде чем любой из них можете вставить в него свой идентификатор запроса? Я действительно не знаю, что мне нужно изменить в своем запросе, чтобы этого не произошло.
Любые идеи будут оценены, спасибо!