Ладно, позвольте мне разобраться с этим.
Итак, проблема, с которой я столкнулся в этом вопросе, состоит в том, что вы настолько абстрагировали проблему, что вам действительно трудно помочь оптимизировать ее.Непонятно, что делает ваш «длительный процесс», и то, что он делает, повлияет на решение проблемы обработки нескольких одновременных запросов.Что делает ваш API, что вы беспокоитесь о потреблении ресурсов?
Из вашего кода сначала я догадался, что вы запускаете какое-то длительное задание (например, преобразование файлов или что-то в этом роде), но затем некоторые изменения и комментарии заставляют меня думать, что это может бытьпросто сложный запрос к базе данных, который требует много вычислений, чтобы правильно и, следовательно, вы хотите кэшировать результаты запроса.Но я также мог видеть, что это что-то другое, например, запрос к группе сторонних API, которые вы собираете, или что-то в этом роде.У каждого сценария есть свой нюанс, который меняет то, что является оптимальным.
Тем не менее, я объясню сценарий «кэширования», и вы можете сказать мне, если вас больше интересует одно из других решений.
По сути, вы уже находитесь в правильном поле для кэша.Если вы еще этого не сделали, я бы порекомендовал взглянуть на cache-manager , который немного упрощает ваш шаблон для этих сценариев (и давайте вам установим недействительность кэша и даже обеспечим многоуровневое кэширование).Часть, которую вы упускаете, заключается в том, что вы, по сути, должны всегда отвечать всем, что у вас есть в кеше, и заполнять кеш вне области любого данного запроса.Используя ваш код в качестве отправной точки, что-то вроде этого (исключая все попытки .. ловушки и проверки ошибок и так далее для простоты):
// A GET is OK here, because no matter what we're firing back a response quickly,
// and semantically this is a query
app.get("/api", async function(req, res) {
const itemID = req.query.itemID
// In this case, I'm assuming you have a cache object that basically gets whatever
// is cached in your cache storage and can set new things there too.
let item = await cache.get(itemID)
// Item isn't in the cache at all, so this is the very first attempt.
if (!item) {
// go ahead and let the client know we'll get to it later. 202 Accepted should
// be fine, but pick your own status code to let them know it's in process.
// Other good options include [503 Service Unavailable with a retry-after
// header][2] and [420 Enhance Your Calm][2] (non-standard, but funny)
res.status(202).send({ id: itemID });
// put an empty object in there so we know it's working on it.
await cache.set(itemID, {});
// start the long-running process, which should update the cache when it's done
await populateCache(itemID);
return;
}
// Here we have an item in the cache, but it's not done processing. Maybe you
// could just check to see if it's an empty object or not, but I'm assuming
// that we've setup a boolean flag on the cached object for when it's done.
if (!item.processed) {
// The client should try again later like above. Exit early. You could
// alternatively send the partial item, an empty object, or a message.
return res.status(202).send({ id: itemID });
}
// if we get here, the item is in the cache and done processing.
return res.send(item);
}
Теперь я не знаю точно, что все ваши вещиделает, но если это я, populateCache
сверху - довольно простая функция, которая просто вызывает любой сервис, который мы используем для выполнения длительной работы, а затем помещает его в кеш.
async function populateCache(itemId) {
const item = await service.createThisWorkOfArt(itemId);
await cache.set(itemId, item);
return;
}
Дайте мне знать, если это не ясно или ваш сценарий действительно отличается от того, что я предполагаю.
Как уже упоминалось в комментариях, этот подход будет охватывать большинство обычных проблем, которые могут возникнуть у вас в описанном сценарии, но он все равно позволит двум запросам отключить длительный процесс, если они поступят быстрее, чемзапись в кэш-хранилище (например, Redis).Я считаю, что вероятность того, что это произойдет, довольно мала, но если вы действительно обеспокоены этим, то следующей более параноидальной версией этого будет простое удаление кода продолжительного процесса из вашего веб-API в целом.Вместо этого ваш API просто записывает, что кто-то просил, чтобы такое произошло, и если в кеше ничего нет, то отвечайте, как я делал выше, но полностью удалите блок, который фактически вызывает populateCache
в целом.
Вместо этого у вас должен быть запущен отдельный рабочий процесс, который периодически (как часто зависит от вашего бизнес-сценария) проверяет кэш на необработанные задания и запускает работу для их обработки.Делая это таким образом, даже если у вас есть 1000 одновременных запросов на один и тот же элемент, вы можете гарантировать, что обрабатываете его только один раз.Недостатком, конечно, является то, что вы добавляете периодичность проверки к задержке получения полностью обработанных данных.