.NET не имеет надежной асинхронной сокетной связи?

Однажды я написал Crawler в.NET. Чтобы улучшить его масштабируемость, я попытался воспользоваться асинхронным API.NET.

System.Net.HttpWebRequest имеет асинхронный API BeginGetResponse/EndGetResponse. Однако эта пара API предназначена только для получения заголовков ответа HTTP и экземпляра Stream, из которого мы можем извлечь содержимое ответа HTTP. Итак, моя стратегия состоит в том, чтобы использовать BeginGetResponse / EndGetResponse для асинхронного получения потока ответа, а затем использовать BeginRead/EndRead для асинхронного получения байтов из экземпляра потока ответа.

Все кажется идеальным, пока Crawler не пройдет стресс-тест. В стресс-тесте Crawler страдает от высокого использования памяти. Я проверил память с помощью WinDbg+SoS и выяснил, что экземпляры System.Threading.OverlappedData очищают множество байтовых массивов. После некоторых поисков в Интернете я нашел этот KB http://support.microsoft.com/kb/947862 от Microsoft.

Согласно КБ, число асинхронных операций ввода-вывода должно иметь "верхнюю границу", но это не говорит о "предложенной" граничной величине. Так что, на мой взгляд, этот КБ ничего не помогает. Это, очевидно, ошибка.NET. Наконец, я должен отказаться от идеи сделать асинхронное извлечение байтов из потока ответа, и просто сделать это синхронно.

Библиотека.NET, которая допускает асинхронный ввод-вывод с точечными сокетами (Socket.BeginSend / Socket.BeginReceive / NetworkStream.BeginRead / NetworkStream.BeginWrite), должна иметь верхнюю границу количества ожидающих буферов (отправлять или получать) с их асинхронным вводом-выводом,

Сетевое приложение должно иметь верхнюю границу числа ожидающих асинхронных операций ввода-вывода, которые оно публикует.

Изменить: добавить несколько знаков вопроса.

У кого-нибудь есть опыт выполнения асинхронного ввода-вывода на Socket & NetworkStream? Вообще говоря, работает ли crawler в производственной среде с операциями ввода-вывода через Интернет с синхронным или асинхронным подключением?

5 ответов

Хм, это не проблема.NET Framework. Ссылка на статью базы знаний могла бы быть немного более явной: "вы используете заряженный пистолет, это то, что происходит, когда вы наводите его на свою ногу". Пули в этом ружье.NET дают вам возможность запускать столько асинхронных запросов ввода / вывода, сколько вы решитесь. Он будет делать то, что вы просите, пока вы не достигнете какого-то лимита ресурсов. В этом случае, вероятно, слишком много закрепленных приемных буферов в куче 0 поколения.

Управление ресурсами по-прежнему остается нашей работой, а не.NET. Это ничем не отличается от выделения памяти без ограничений. Решение этой конкретной проблемы требует от вас ограничения количества незавершенных запросов BeginGetResponse(). Наличие сотен из них мало смысла, каждый из них должен протискиваться через Intertube по одному. Добавление еще одного запроса просто приведет к тому, что его выполнение займет больше времени. Или сбой вашей программы.

Это не ограничивается.Net.

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

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

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

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

Очевидно, что вы хотите ограничить количество одновременных запросов, независимо от того, используется ли для вашего сканера синхронизация / асинхронность. Это ограничение не является фиксированным, оно зависит от вашего оборудования, сети,...

Я не совсем уверен, что ваш вопрос здесь, так как.NET-реализация HTTP/Sockets "в порядке". Есть некоторые дыры (см. Мой пост о правильном контроле таймаутов), но он выполняет свою работу (у нас есть рабочий сканер, который извлекает ~ сотни страниц в секунду).

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

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

Ни одна статья КБ не может дать вам верхний предел. Верхние границы могут варьироваться в зависимости от доступного оборудования - верхний предел для машины с памятью 2G будет отличаться для машины с 16 г оперативной памяти. Это также будет зависеть от размера кучи GC, насколько она фрагментирована и т. Д.

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

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

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

Другие вопросы по тегам