Какой сетевой протокол использовать для упрощенного уведомления удаленных приложений?

У меня такая ситуация.... Инициированная клиентом связь SOAP 1.1 между одним сервером и, скажем, десятками тысяч клиентов. Клиенты являются внешними, входящими через наш брандмауэр, аутентифицированными по сертификату, https и т. д. Они могут быть где угодно и обычно имеют свои собственные брандмауэры, маршрутизаторы NAT и т. д. Они действительно внешние, а не просто удаленные корпоративные офисы. Они могут быть в корпоративной/кампусной сети, DSL/кабельной, даже коммутируемой.

Клиент использует Delphi (2005 + исправления SOAP от 2007), а сервер - C #, но с точки зрения архитектуры/дизайна это не должно иметь значения.

В настоящее время клиенты передают новые данные на сервер и извлекают новые данные с сервера в течение 15-минутного цикла опроса. Сервер в настоящее время не отправляет данные — клиент использует метод «messagecount», чтобы увидеть, есть ли новые данные для извлечения. Если 0, он спит еще 15 минут и снова проверяет.

Мы пытаемся сократить это время до 7 секунд.

Если бы это было внутреннее приложение с одним или несколькими десятками клиентов, мы бы написали cilent-сервис мыла «слушателя» и передавали бы ему данные. Но поскольку они внешние, сидят за своими собственными брандмауэрами, а иногда и за частными сетями за маршрутизаторами NAT, это нецелесообразно.

Таким образом, у нас остается опрос на гораздо более быстром цикле. 10 000 клиентов, каждый из которых проверяет количество сообщений каждые 10 секунд, будут обрабатывать 1000 сообщений в секунду, которые в основном будут просто тратить ресурсы полосы пропускания, сервера, брандмауэра и аутентификатора.

Поэтому я пытаюсь разработать что-то лучшее, чем то, что можно было бы приравнять к DoS-атаке, которую я нанес сам себе.

Я не думаю, что сервер отправляет мыльные сообщения клиенту (push), так как это потребует слишком большой настройки на стороне клиента. Но я думаю, что есть альтернативы, о которых я не знаю. Такие как:

1) Есть ли способ для клиента сделать запрос на GetMessageCount() через Soap 1.1 и получить ответ, а затем, возможно, «оставаться на линии» в течение, возможно, 5-10 минут, чтобы получить дополнительные ответы в случае появления новых данных прибывает? то есть сервер говорит «0», а через минуту в ответ на какой-то триггер SQL (сервер C # на Sql Server, кстати), знает, что этот клиент все еще «на линии», и отправляет обновленное количество сообщений «5 "?

2) Есть ли какой-нибудь другой протокол, который мы могли бы использовать для «пингования» клиента, используя информацию, полученную из его последнего запроса GetMessageCount()?

3) даже не знаю. Думаю, я ищу какой-нибудь волшебный протокол, где клиент может отправить запрос GetMessageCount(), который будет включать информацию для «о, кстати, если ответ изменится в течение следующего часа, пропингуйте меня по этому адресу... ".

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

Там есть что-то подобное? Или я в значительной степени застрял с опросом?

TIA,
Крис

ОБНОВЛЕНИЕ 30 апреля 2010 г.:
Продемонстрировав, что получение 7-секундных уведомлений не является ни простым, ни дешевым делом, особенно если не выходить за рамки корпоративного стандарта HTTPS/SOAP/брандмауэров, мы, вероятно, собираемся представить двухэтапное решение. На этапе 1 клиенты будут опрашиваться «по запросу», при этом GetMessageCount будет выполняться через SOAP, здесь нет ничего необычного. Будет кнопка «обновить» для извлечения новых данных (что здесь разумно, поскольку у пользователя обычно есть основания подозревать, что новые данные готовы, т. е. они только что изменили цвет ткани в онлайн-системе, поэтому они знают, что нужно нажать ОБНОВИТЕ перед просмотром ведомости доставки на рабочем столе, и теперь они видят цвет в описании.) (На самом деле это НЕ приложение для одежды/моды, но вы поняли идею). Идея постоянной синхронизации двух приложений с обновлениями в режиме реального времени, передаваемыми с хоста, по-прежнему актуальна с использованием обсуждаемых здесь технологий. Но я ожидаю, что это будет перенесено на другой выпуск, так как мы можем предоставить 85% функциональности без необходимости делать это. Тем не менее, я надеюсь, что мы проведем Proof Of Concept и сможем продемонстрировать, что это сработает. Я вернусь и опубликую будущие обновления. Спасибо всем за помощь в этом.


person Chris Thornton    schedule 20.04.2010    source источник
comment
Привет, ребята, я добавил награду, но это не значит, что мне пока не нравятся ответы! Я хотел убедиться, что на это посмотрели как можно больше, и, кроме того, я всегда хотел сделать щедрость... Здесь есть много отличных ответов, и я очень ценю это.   -  person Chris Thornton    schedule 25.04.2010
comment
Хотел бы я разделить вознаграждение... здесь есть много отличных ответов, и, возможно, наиболее полезным на данном этапе проекта является подавляющее согласие, согласное с моей позицией, что мы не можем обрабатывать 10K соединений, выполняя быстрый опрос, и не можем мы отправляем им данные как обычные прослушиватели SOAP.   -  person Chris Thornton    schedule 30.04.2010


Ответы (10)


Двумя крупными участниками многоуровневой разработки в Delphi являются component4developers (их продукт kbmMW описан в ответе Марка Робинсона) и RemObjects со своим продуктом RemObjects SDK (у них есть хороший пример, который может быть похож на то, что вам нужно: Push-уведомления для iPhone).

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

Если соединение открыто, его можно использовать в двунаправленном режиме (это также используется удаленным взаимодействием .NET и WCF), но оно сопряжено с дополнительными накладными расходами.

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

--jeroen

person Jeroen Wiert Pluimers    schedule 20.04.2010
comment
Спасибо - у нас тут действительно есть лицензия на RemObjects, она была куплена для проекта, который так и не сдвинулся с мертвой точки. Я выкопаю это и проверю их пример. - person Chris Thornton; 21.04.2010
comment
@Chris: Если вы решите свою проблему, сообщите нам, как вы ее решили! - person Jeroen Wiert Pluimers; 21.04.2010
comment
+ Принято, поскольку это, вероятно, будет нашим решением. Я хотел бы разделить вознаграждение между этим и несколькими другими, рекомендующими RemObjects и kbmMW. Спасибо всем! - person Chris Thornton; 30.04.2010
comment
@Chris: я не знал о награде. Спасибо. Если кто-то знает, как я могу передать часть награды другим, дайте мне знать: я не против поделиться ею. - person Jeroen Wiert Pluimers; 01.05.2010

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

Пусть каждый отдельный клиент выполняет простой HTTP-запрос на количество сообщений таким образом, чтобы запретить любое кэширование (пример: GET http://yourserver.org/getcount/nodeid/timeofday/sequence). В серверной реализации HTTP-сервер задерживает предоставление ответа, если «счетчик» такой же, как и раньше (т.е. нет новых сообщений).

Я сделал это для приложения в стиле Ajax, которое работало в браузере и вело себя как приложение чата, но ваше решение может быть еще быстрее. Я реализовал материал на стороне сервера, используя сервер TIdHttp, и это позволило мне фактически отложить предоставление ответа на клиентский материал, просто выполнив Sleep() в его потоке. Со стороны клиента это выглядело как сервер, который иногда очень медленно дает ответ.

Псевдокод для серверной части:

function ClientHasMessages(ClientID:Integer; MaxWait:TDateTime):Boolean;
var MaxTime:TDateTime;
begin
  if ClientActuallyHasMessage(ClientID) then Result := True
  else
    begin
      MaxTime := Now + MaxWait;
      while Now < MaxTime do
      begin
        if ClientActuallyHasMessage(ClientID) then
          begin
            Result := True;
            Exit;
          end
        else
          Sleep(1000);
      end;
      Result := False; // TimeOut
    end;
end;

Идея этого кода: он работает в потоке на вашем собственном сервере, где он может проверить количество сообщений, по-видимому, за очень небольшие деньги:

  • Это не вызывает сетевого трафика во время ожидания.
  • Он не использует процессор во время сна.
  • Это позволит пользователю узнать о своем сообщении очень быстро.
  • Он позволяет клиенту контролировать, как долго может длиться ожидание (клиент увеличивает время, на которое сервер может задерживать ответ до тех пор, пока он больше не получит ответ, а затем немного отступает — таким образом протокол адаптируется к любым ошибкам NAT). маршрутизатор, который использует клиент).
  • Вы можете избавиться от длительных периодов отсутствия связи по протоколу TCP/IP и при этом иметь возможность мгновенно предоставить ответ. 30 секунд легко сделать, а для клиентов с хорошими маршрутизаторами NAT это может быть намного дольше.

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

  • Серверная реализация TCP/IP должна отслеживать большое количество одновременных подключений (у каждого клиента всегда будет активен HTTP-запрос). Моя машина Linux NAT прямо сейчас отслеживает 15 000 подключений и в основном простаивает, так что это может сработать.
  • На сервере всегда будет открыт поток для каждого HTTP-запроса клиента: Опять же, «Рабочая станция» Server 2008, которую я использую для написания этого (спасибо MSDN за то, что позволили мне делать такие возмутительные вещи), имеет около 1500 потоков. активен, и он также в основном бездействует ...
  • В зависимости от технологии, которую вы используете для серверного кода, ограничивающим фактором может быть ПАМЯТЬ.
person Cosmin Prund    schedule 21.04.2010

Я бы посмотрел на kbmMW

Я бы, возможно, использовал метод, аналогичный MS Exchange - подключение и аутентификация через tcp/ip, затем уведомление об обновлениях от сервера к клиенту через udp, затем клиент получает запрос udp и загружает обновление через tcp/ip.

(По крайней мере, так я понимаю работу MS Exchange)

person Mark Robinson    schedule 20.04.2010
comment
Прикольно - посмотрю. Мне нравится часть об использовании обмена сообщениями на основе публикации/подписки с помощью нескольких типов полностью доступных для разработчиков и настраиваемых очередей сообщений. - person Chris Thornton; 20.04.2010
comment
Ваше UDP-уведомление звучит так же, как то, что мне нужно. Вы знаете, сможет ли он вернуться через тот же порт брандмауэра? Я надеюсь на нулевую конфигурацию здесь (ноль сверх того, что им уже нужно для SOAP через https). - person Chris Thornton; 20.04.2010
comment
Я очень люблю kbmMW — это своего рода кривая обучения, но зато в этом есть смысл — бесплатная базовая версия тоже! - person Mark Robinson; 20.04.2010
comment
Я думаю, поскольку клиент открывает соединение и не отключается, должен быть обратный маршрут udp. - person Mark Robinson; 20.04.2010
comment
Просто чтобы добавить что-то, возможно, полезное, в последний раз, когда я использовал kbmMW, у клиента была очень-очень плохая связь, и ему также приходилось запускать voip - когда клиент впервые подключился, я установил время обновления на 30 секунд плюс случайный 1- 15 секунд - я полагаю, вы могли бы установить для каждого клиента частоту обновления от 3 до 7 секунд (изменение каждого подключения), чтобы гарантировать, что каждый клиент не подключается одновременно. - person Mark Robinson; 26.04.2010
comment
Использование UDP в дополнение к TCP может потребовать дополнительной настройки брандмауэра. - person Kevin Panko; 29.04.2010

Вы можете попробовать позвонить на сервер и подождать некоторое время (1 минуту?), пока у вас не появятся обновления. Таким образом, вам не нужно обратное соединение с сервера на клиент, и вы получаете почти мгновенные результаты для клиента (если у вас есть обновления в течение 1 минуты, вы завершаете вызов ожидания). Это относительно простой и широко (?) используемый веб-приложениями (например, Gmail: у него есть фоновое соединение, подобное этому: если приходит новое электронное письмо, вы сразу же видите его в своем почтовом ящике!). Я использую что-то вроде этого (RemObjects):

function TLoggingViewService.GetMessageOrWait: TLogMessageArray;
begin
  if (Session.Outputbuffer.Count > 0) or
     //or wait till new message (max 3s)
     Session.Outputbuffer.WaitForNewObject(3 * 1000)
  then
    //get all messages from list (without wait)
    Result := PeekMessage;
end;

Отрицательный момент: вы держите соединение открытым в течение относительно долгого времени (что, если соединение потеряно из-за Wi-Fi и т. д.?) И высокая «нагрузка» сервера (каждое соединение имеет поток, остается открытым: если у вас много клиентов, вы можете получить из ресурсов).

Мы используем здесь RemObjects и используем TCP + Binmessage, который имеет НАМНОГО меньше накладных расходов, чем SOAP + HTTP, и очень быстрый! Так что, если вы можете использовать это, я действительно могу рекомендовать это! (в вашем случае нужны Remobjects для Delphi и RemObjects для .Net). Используйте SOAP только в том случае, если вам нужно подключить третьи стороны, и используйте HTTP, только если вам это нужно из-за Интернета/брандмауэра. SOAP хорош, но имеет высокие накладные расходы и проблемы с производительностью.

Вы также можете использовать их комбинацию: простое (RemObjects) TCP-соединение (с небольшими накладными расходами) в фоновом потоке, опрашивая каждые 10 секунд и ожидая 5 секунд для новых данных.

person André    schedule 21.04.2010

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

Если клиентам нужно только «спросить, есть ли что-нибудь новое», самым легким протоколом, который легко реализовать, является UDP, следующим самым легким будет чистый TCP, оба с использованием клиентов Indy.

Сам протокол на самом деле может быть таким же простым, как отправка «Что-нибудь новое с [гггг-мм-дд чч:мм:сс]» на сервер, и он отвечает однобайтовым числом (возможны 256 ответов).

С TCP у вас будет дополнительное преимущество: держать «канал» открытым в течение нескольких минут, и вы можете отправлять сообщение «что-нибудь новое» каждые x секунд. Также с TCP сервер может «проталкивать» информацию в канал (клиент(ы)), когда что-то происходит, учитывая, что клиент периодически проверяет данные в канале.

person K.Sandell    schedule 24.04.2010
comment
Поскольку у IP-пакета довольно много служебных данных, на самом деле нет смысла отвечать одним байтом. Если вы вызываете отправку пакета, просто отправьте обратно то, что необходимо для передачи информации - 4 или 8 байтов вместо 1, например, не будут иметь такой большой разницы... - person mghie; 24.04.2010
comment
Совершенно верно, так что на самом деле можно просто подключиться к клиенту, затем некоторое время слушать что-то и отключаться, если ничего нет ... Настоящий трюк состоит в том, чтобы выбрать хорошие значения для того, чтобы оставаться на связи, и когда снова подключаться ... - person K.Sandell; 24.04.2010
comment
В связи с этим я думаю, что, возможно, нам следует настроить второй сервер подсчета сообщений за пределами брандмауэра. Главный сервер будет передавать ему счетчики сообщений, когда они изменятся. Затем клиенты будут подключаться к серверу MessageCount, используя один из этих других методов. Даже если бы мы прибегали к вызовам http get, они не мешали бы нашим внутренним брандмауэрам и аутентификаторам, поскольку, предположительно, количество сообщений могло бы быть представлено таким образом, что его не нужно было бы защищать. Интересно! - person Chris Thornton; 25.04.2010

Я бы постарался максимально распределить нагрузку между несколькими серверами. Для этого я бы сделал следующее:

  1. Клиенты регистрируются в вашем сервисе, чтобы получать уведомления. Они получают идентификатор сеанса, действительный в течение заданного периода времени (15 минут).
  2. Серверы будут периодически проверять, какой зарегистрированный клиент имеет входящее сообщение, и генерировать список таких клиентов (технически я бы вообще отправил это в другую БД в вашей демилитаризованной зоне).
  3. Вы запускаете несколько серверов «push-уведомлений», которые следуют очень и очень простому протоколу: они получают URL-запрос, содержащий идентификатор сеанса, и отвечают коротким HTTP-ответом: либо 404, либо 200 с (подписанным) URL-адресом SOAP-сервер, к которому нужно обращаться, чтобы получать сообщения. Для дополнительной производительности вы можете использовать HTTP 1.1 и постоянные соединения.
  4. Клиент будет объединять эти серверы push-уведомлений так часто, как захочет. Поскольку они очень просты и строго доступны только для чтения, они могут очень быстро отвечать на запросы и их легко масштабировать.
  5. Если клиент получает ответ 302, он может подключиться к правильному серверу SOAP (вы также можете использовать его для распределения нагрузки, если хотите) и получить сообщения.

Здесь вам придется позаботиться о безопасности. Во-первых, я предлагаю вам НЕ использовать HTTPS для ваших серверов push-уведомлений. Вместо этого вы можете подписать содержимое ответа сеансовым ключом, которым обмениваются, когда клиент запрашивает уведомления. Затем клиент несет ответственность за проверку ответа. Не забывайте, что вам нужно подписать не только статус, но и URL сервиса SOAP.

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

person Stephane    schedule 29.04.2010

Для этого мы используем «события» RemObjects SDK, но это может вам не подойти, потому что

a: Он работает только с собственным бинарным протоколом RemObjects, а не с SOAP (т.е. клиенты должны включать код RO)

b: По сути, это подход «держать линию открытой». поэтому масштабируемость до 10 000 клиентов является потенциальной проблемой.

Я бы попробовал несколько тестов, чтобы увидеть, какие накладные расходы на поддержание открытых сокетов 10K на самом деле. Если все, что вам нужно, это пара гигабайт дополнительной серверной памяти, это будет дешевое решение. И поскольку сокет открывается со стороны клиента, это не должно вызывать проблем с брандмауэром. Худшее, что может сделать брандмауэр, — это закрыть сокет, поэтому вашему клиенту потребуется снова открыть его, когда это произойдет.

person Roddy    schedule 29.04.2010
comment
Спасибо, это подход, к которому я склоняюсь, но с изюминкой: я думаю о том, чтобы иметь отдельный сервер только для уведомлений. Поскольку уведомления не содержат фактической полезной нагрузки данных (которая осталась бы в SOAP, с https, аутентификацией сертификата через брандмауэр и т. д.) и содержат только идентификатор клиента и количество сообщений, безопасность не так важна. беспокойство. Так что мы могли бы поместить это в DMZ или даже использовать внешний хостинг. продолжение... - person Chris Thornton; 29.04.2010
comment
... Итак, теперь возникает вопрос о том, сколько серверов вам нужно для обработки 10 000 каналов уведомлений RemObjects, а не о том, сколько $$$ будет стоить поддержка 10 000 запросов SOAP через https с аутентификацией клиентского сертификата через наш ESB. , вплоть до нашей серверной базы данных Sql*Server? - person Chris Thornton; 29.04.2010
comment
@ Родди, могу я спросить, сколько клиентов вы ДЕЙСТВИТЕЛЬНО держите открытыми одновременно? И есть ли образец проекта, который вы бы порекомендовали мне посмотреть, используя RO SDK? Я надеюсь, что через несколько недель у меня будет время на его прототип (сейчас я похоронен с производственными вещами). - person Chris Thornton; 30.04.2010

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

Возможно, вы сможете уменьшить накладные расходы на опрос, упростив вызов. Используйте простое веб-действие, чтобы вернуть клиенту самый высокий номер сообщения, и пусть клиент выполнит простой HTTP GET для получения этого номера. Это уменьшает объем полосы пропускания и делает его простым. Если затем клиенту необходимо получить обновленные данные, можно выполнить полный вызов мыла.

person skamradt    schedule 20.04.2010

Каждый раз, когда у вас есть один сервер и более 10 000 клиентов, и вам нужно получать обновления каждые несколько секунд, вы столкнетесь с проблемами. Я бы получил еще несколько серверов и держал бы клиентов подключенными к фоновому потоку в клиенте, который сначала подключается, а затем ждет уведомлений от сервера со встроенным механизмом поддержания активности.

Если вы пытаетесь передать данные с сервера клиенту, который в данный момент не подключен, удачи, если у вас нет контроля над средами клиентов. Звучит для меня так, как будто вы вынуждены устанавливать соединения, инициированные клиентом.

person Darian Miller    schedule 21.04.2010

Что-то немного не в левом поле.

Почему аутентификация требуется только для получения флага о готовности обновлений? Почему бы не иметь машину за пределами брандмауэра аутентификации... или даже в облаке... которая ничего не делает, кроме как обрабатывает эти запросы "есть ли что-нибудь доступное". Затем, если что-то доступно, попросите клиента пройти через обручи, чтобы получить реальные данные. Этот сервер запросов может выполнять 7-секундный getcount с реального сервера.

Сейчас мы говорим об очень небольшом количестве данных и очень небольшом времени установки для простого «флажка», даже не считая.

Это по-прежнему тысячи запросов, но тысячи запросов с минимальными накладными расходами по сравнению с полностью аутентифицированным запросом.

person Gregor Brandt    schedule 11.11.2010