В каких условиях (если таковые имеются) целесообразно запрашивать два сервера и использовать только самый быстрый ответ?

12

Я спросил, что теперь является вопросом, удаленным сообществом, на SO о том, почему кто-то использует javascript Promise.race, и пользователь с высокими репутациями прокомментировал это:

Если у вас есть две службы, которые вычисляют какое-то значение, вы можете запрашивать их обе параллельно и использовать, какое значение всегда возвращается первым, вместо того, чтобы запрашивать одно, ожидать сбоя и затем запрашивать второе.

Я погуглил по поводу избыточности и этого варианта использования в целом, но ничего не смог найти, и из моего POV никогда не было бы хорошей идеей просто добавить рабочую нагрузку на сервер / службу, если вы не собираетесь использовать ответ.

Аделин
источник
Пример с игрушкой: вместо того, чтобы всегда использовать быструю сортировку, вы копируете данные, отправляете их на быструю сортировку, и сортировку слиянием, и сортировку, и т. Д. И т. Д. Вам не нужно проверять ввод, чтобы увидеть, является ли это патологическим случаем для любого из них, потому что это не будет патологическим случаем для всех них
Caleth
В статье Дина и Баррозу «Хвост в масштабе» вариация этого подхода называется «хеджированные запросы». В нем также обсуждаются плюсы и минусы нескольких связанных подходов к контролю изменчивости длинных хвостов по частоте ошибок и задержке.
Даниэль Приден
Второй «запрос к серверу» может быть подделкой. Может потребоваться 5 секунд, а затем вернуть ответ заполнителя. Это дает вам тайм-аут на реальном запросе.
user253751
Закажите Lyft, а затем закажите Uber. Возьми, кто бы ни пришел первым.
user2023861
@ user2023861 по этой аналогии, в то время как один водитель бессмысленно ехал к вашему местоположению, он / она мог бы вместо этого выполнить другой запрос
Аделин

Ответы:

11

Я бы сказал, что это больше вопрос экономики. Тем не менее, это призыв к суждению, который должен сделать инженер. Следовательно, я отвечаю.

Я делю свой ответ на четыре части:

  • Управление рисками
  • Стратегии
  • Расходы
  • Интуиция

Управление рисками

Таким образом, иногда ваш клиент не может получить ответ от сервера. Я предполагаю, что это не из-за программной ошибки (в противном случае решение состоит в том, чтобы исправить это, так что иди и сделай это). Вместо этого, это должно быть из-за случайной ситуации вне вашего контроля ...

Но не за пределами ваших знаний. Ты должен знать:

  • Как часто это случается.
  • Какое влияние это имеет.

Например, если сбой и повторная попытка происходят только около 2% времени, вероятно, не стоит их устранять. Если это происходит в 80% случаев, хорошо ... зависит ...

Сколько времени клиент должен ждать? И как это приводит к затратам ... вы видите, у вас небольшая задержка в обычном приложении, это, вероятно, не имеет большого значения. Если это важно, и у вас есть приложение в реальном времени или онлайн-видеоигра, это отвлечет пользователей, и вам, вероятно, лучше инвестировать в большее или большее количество серверов. В противном случае вы, вероятно, можете поместить сообщение «загрузка» или «ожидание сервера». Если задержка действительно велика (порядка десятков секунд), то она может быть слишком большой даже для обычного применения.


Стратегии

Как я уже говорил выше, есть несколько способов решения этой проблемы. Я предполагаю, что у вас уже есть реализация цикла try-fail-retry. Итак, давайте посмотрим ...

  • Поместите загрузочное сообщение. Это дешево, помогает удерживать пользователей.
  • Запрос параллельно. Может быть быстрее, все еще может потерпеть неудачу. Потребуется резервный сервер (может быть дорогим), будет тратить время сервера и сетевой трафик.
  • Выполните параллельный запрос, чтобы стабилизировать работу более быстрого сервера и использовать его оттуда. Может быть быстрее, все еще может потерпеть неудачу. Потребуется резервный сервер (может быть дорогим), не будет тратить столько времени сервера и сетевого трафика.

Теперь обратите внимание, я говорю, что они все еще могут потерпеть неудачу. Если предположить, что запрос к серверу имеет 80% вероятности сбоя, то параллельный запрос к двум серверам имеет 64% вероятности сбоя. Таким образом, вам все равно придется повторить попытку.

Дополнительным преимуществом выбора более быстрого сервера и его дальнейшего использования является то, что более быстрый сервер также менее подвержен сбоям из-за проблем с сетью.

Что напоминает мне, если вы можете выяснить, почему запрос не выполнен, сделайте это. Это может помочь вам лучше управлять ситуацией, даже если вы не можете предотвратить сбои. Например, вам нужна большая скорость передачи на стороне сервера?

Еще немного:

  • Разверните несколько серверов по всему миру и выберите сервер по геолокации.
  • Выполните балансировку нагрузки на стороне сервера (выделенный компьютер будет принимать все запросы и перенаправлять их на ваши серверы, у вас может быть там параллелизм или лучшая стратегия балансировки).

И кто сказал, что вы должны сделать только один из них? Вы можете поместить сообщение о загрузке, запросить несколько серверов, которые распределены по всему миру, чтобы выбрать более быстрый и использовать его только после этого, при повторной попытке сбоя в цикле, и чтобы каждый из этих серверов был кластером машин с балансировкой нагрузки , Почему нет? Ну, стоит ...


Расходы

Есть четыре стоимости:

  • Стоимость разработки (обычно очень дешевая)
  • Стоимость развертывания (обычно высокая)
  • Стоимость времени выполнения (зависит от типа приложения и бизнес-модели)
  • Стоимость отказа (вероятно, низкая, но не обязательно)

Вы должны сбалансировать их.

Например, допустим, вы зарабатываете около доллара на одного довольного пользователя. Это у вас 3000 пользователей в день. То, что запросы терпят неудачу около 50% времени. И это 2% пользователей уходят без оплаты, когда запрос не удается. Это означает, что вы теряете (3000 * 50% * 2%) 30 долларов в день. Теперь предположим, что разработка новой функции обойдется вам в 100 долларов, а развертывание серверов обойдется вам в 800 долларов, а игнорирование затрат времени выполнения означает, что вы получите возврат инвестиций в ((100 + 800) / 30. ) 30 дней. Теперь вы можете проверить свой бюджет и принять решение.

Не считайте эти ценности представительными для реальности, я выбрал их для удобства математики.

Дополнения:

  • Помните, что я также игнорирую детали. Например, у вас может быть небольшая стоимость развертывания, но вы платите за процессорное время, и вам нужно учитывать это.
  • Некоторые клиенты могут оценить, если вы не тратите их пакет данных на избыточные запросы.
  • Улучшение вашего продукта может помочь привнести естественную рекламу.
  • Не забывайте альтернативные издержки. Должны ли вы разрабатывать что-то еще?

Дело в том, что если вы рассматриваете проблему с точки зрения балансирования затрат, вы можете оценить стоимость стратегий, которые вы рассматриваете, и использовать этот анализ для принятия решения.


Интуиция

Интуиция, если воспитывается на опыте. Я не предлагаю проводить такой анализ каждый раз. Некоторые люди делают, и это нормально. Я предлагаю вам иметь некоторое понимание этого и выработать интуицию для этого.

Кроме того, в области машиностроения, помимо знаний, которые мы получаем от реальной науки, мы также учимся на практике и собираем рекомендации о том, что работает, а что нет. Поэтому часто бывает мудро увидеть, в каком состоянии находится искусство ... хотя иногда вам нужно видеть за пределами своего района.

В этом случае я бы посмотрел на онлайн видеоигры. У них есть экраны загрузки, у них есть несколько серверов, они выбирают сервер в зависимости от времени ожидания, и они могут даже позволить пользователю переключать серверы. Мы знаем, что работает.

Я бы предложил сделать это вместо того, чтобы тратить сетевой трафик и серверное время на каждый запрос, а также помнить, что даже с резервным сервером может произойти сбой.

Theraot
источник
2
Я не думаю, что мне нужно это говорить, но это отличный ответ :) Я знал, что приму это в первых 10 строках, но я дал вам возможность все равно потерпеть неудачу и прочитать его до конца. Вы не
Аделин
9

Это приемлемо, если время клиента более ценно, чем время на сервере.

Если клиент должен быть быстрым и точным. Вы можете обосновать запрос нескольких серверов. И приятно отменить запрос, если получен действительный ответ.

И, конечно же, всегда полезно проконсультироваться с владельцами / менеджерами серверов.

Тун Крижте
источник
Почему вам нужно отменить запрос? Конечно, это субъективно.
августа
@ JᴀʏMᴇᴇ, это сборка в паранойе. Однажды я работал с системой, которая не очищала свою очередь, и она вылетала, когда очередь была заполнена (да, это было профессиональное программное обеспечение).
Toon Krijthe
4

Эта техника может уменьшить задержку. Время ответа сервера не является детерминированным. В масштабе, вероятно, будет по крайней мере один сервер, показывающий плохое время отклика. Поэтому все, что использует этот сервер, также будет иметь низкое время отклика. Отправляя сообщения на несколько серверов, вы снижаете риск разговора с плохо работающим сервером.

Затраты включают в себя дополнительные трафики в сети, потерю обработки сервера и сложность приложения (хотя это может быть скрыто в библиотеке). Эти затраты могут быть уменьшены путем отмены неиспользованных запросов или краткого ожидания перед отправкой второго запроса.

Вот один документ , а другой . Я помню, как читал статью о Google и их реализацию.

Майкл Грин
источник
2

Я в основном согласен с другими ответами, но я думаю, что это должно быть чрезвычайно редко на практике. Я хотел бы поделиться гораздо более распространенным и разумным примером того, когда вы будете использовать Promise.race(), то, что мне довелось использовать это пару недель назад (ну, эквивалент Python).

Скажем, у вас есть длинный список задач, некоторые из которых могут выполняться параллельно, а некоторые должны выполняться раньше других. Вы можете запустить все задачи без зависимостей, а затем подождать в этом списке с помощью Promise.race(). Как только первая задача завершится, вы можете запустить любые задачи, которые зависели от этой первой задачи, и Promise.race()снова в новом списке в сочетании с незавершенными задачами из исходного списка. Продолжайте повторять, пока все задачи не будут выполнены.

Обратите внимание, что API Javascript не идеально подходит для этого. Это почти тот минимум, который работает, и вы должны добавить немало клея. Однако я хочу сказать, что подобные функции race()редко используются для резервирования. Они предназначены в первую очередь для тех случаев, когда вы действительно хотите получить результаты всех обещаний, но не хотите ждать завершения всех из них, прежде чем предпринимать последующие действия.

Карл Билефельдт
источник
Проблема в том, что, по крайней мере, с помощью Promise.race в Javascript вы фактически запускаете задачу каждый раз, когда выполняете метод гонки. Это не будет на незавершенной задаче, это будет новый набор задач, безотносительно к тому, что было выполнено ранее (если вы не реализуете эту логику на уровне задачи). В противном случае исходный список забыт, и остается только возвращаемое значение первого задания
Аделин
1
Обещания в Javascript запускаются с нетерпением, когда new Promiseвызывается, и не перезапускаются при Promise.race()вызове. Некоторые реализации обещаний ленивы, но стремление встречается гораздо чаще. Вы можете проверить, создав обещание в консоли, которое регистрируется в консоли. Вы увидите это журналы сразу. Тогда передайте это обещание Promise.race(). Вы увидите, что он не регистрируется снова.
Карл Билефельдт
Ах, это правда. Но afaik возвращаемое значение остальных обещаний, кроме первого, забыто с обещанием .race
Adelin
Вот почему я сказал, что API не идеально разработан. Вы должны хранить исходный набор задач в переменной где-то.
Карл Билефельдт
1

В дополнение к техническим соображениям вы можете использовать этот подход, когда он является частью вашей реальной бизнес-модели.

Вариации этого подхода относительно распространены в ставках в реальном времени на рекламу. В этой модели издатель (поставщик рекламного пространства) попросит рекламодателей (поставщиков рекламы) сделать ставку на показ определенного пользователя. Таким образом, для каждого такого показа вы должны запросить каждого из подписчиков рекламодателя, отправив запрос с деталями показа в конечную точку, предоставленную каждым рекламодателем (или, в качестве альтернативы, сценарий, предоставленный рекламодателем в качестве конечной точки на ваших собственных серверах), участвуя в гонке. все эти запросы до тайм-аута (например, 100 мс), а затем принимают самую высокую ставку, игнорируя остальные.

Конкретный вариант этого, который помогает сократить время ожидания клиента, состоит в том, чтобы издатель разрешил минимальное целевое значение для ставки, чтобы первая ставка рекламодателя, превышающая это значение, была немедленно принята (или, если ни одна из ставок не превышает значение, максимальное будет принято). Таким образом, в этом варианте первый поступающий запрос может быть выигран, а другой - отброшен, даже если он такой же хороший или даже лучший.

yoniLavi
источник