Данная проблема характерна не только для GUI и терминальных решений на базе СУБД Caché и Ensemble компании InterSystems, а вообще для любого клиент-серверного взаимодействия по протоколу TCP/IP. Обычно она решается на прикладном уровне путём периодического обмена пустыми сообщениями специального вида, предназначенными лишь для того, чтобы просигнализировать о том, что приложение «живо».
Ниже о том, как можно решать эту проблему без программирования.
Источник проблемы
Источник проблемы лежит в природе протокола TCP/IP. Как правило, источник сеанса TCP/IP и его приемник находятся в различных сетях, и на пути сеанса встречается несколько маршрутизаторов. Хотя бы один из них обычно выполняет NAT-преобразование адресов. Ресурсы маршрутизатора всегда ограничены, поэтому некоторые из них выполняют очистку NAT-таблиц от «мёртвых» сеансов. Сеанс считается «мёртвым», если по нему не передавались пакеты в течение некоторого заданного интервала времени (назовем его интервал очистки). Таким образом, «молчаливый» сеанс может быть принят за «мёртвый» и вычищен из NAT-таблицы. При этом ни источник, ни приемник об этом не уведомляются («не царское дело»), и оба они остаются в уверенности, что сеанс ещё «жив» (в чем легко убедиться командой netstat, выполнив ее на клиенте или на сервере в момент возникновения ошибки, но до нажатия на ОК). Когда пользователь, получивший сообщение об ошибке, нажмет на ОК, о разрыве сеанса узнает клиент; серверный же процесс завершится, когда «умерший» сеанс распознает ОС.
Экспериментально установлено, что интервал очистки у многих маршутизаторов (по крайней мере, с прошитым Linux 2.4/iptables) составляет около 10 минут. Постараемся заставить наш TCP-сеанс автоматически поддерживать себя в активном состоянии, даже когда не передается никаких пакетов с данными.
Предлагаемое решение
На уровне ОС обнаружением разорванных TCP-соединений управляют следующие параметры ядра, управляющие работой механизма tcp_keepalive [1]:
• tcp_keepalive_time — интервал времени с момента отправки последнего пакета с данными; по истечении этого срока соединение помечается как требующее проверки; после начала проверки параметр не используется;
• tcp_keepalive_intvl — интервал между проверочными пакетами (отправка которых начинается по истечении tcp_keepalive_time);
• tcp_keepalive_probes — количество неподтвержденных проверочных пакетов; по исчерпании этого счетчика соединение считается разорванным.
Надо сказать, что механизм tcp_keepalive имеет двойное назначение: он может использоваться как для искусственного поддержания активности соединения, так и для выявления разорванных (так называемых «полуоткрытых») соединений. В данной статье обсуждается в основном первое применение, о втором применении, возможно, речь пойдёт в следующей статье на эту тему.
Для того чтобы механизм tcp_keepalive был задействован для TCP-соединений, необходимы два условия:
• поддержка на уровне ОС; к счастью, и в Windows, и в Linux она имеется;
• на одном из концов соединения сокет должен быть открыт с параметром SO_KEEPALIVE. Как оказалось, сервисы Caché открывают сокеты с этим параметром, а сервис OpenSSH несложно заставить поступать аналогично.
Наибольший интерес для нас представляет первый параметр (tcp_keepalive_time), так как именно от него зависит, насколько часто будет выполняться проверка неактивных (с точки зрения отсутствия трафика) соединений. Его значение по умолчанию — и в Windows, и в Linux — равно двум часам (7200 с). Типичное же время бездействия, после которого наступает разрыв, составляет около 10 минут. Поэтому предлагается установить значение параметра в 5 минут, что позволит искусственно поддерживать активность TCP-сеансов, не перегружая сеть избыточным трафиком (5 минут — это не 5 секунд).
Установка параметров tcp_keepalive на сервере Windows
Вы должны обладать правами Администратора к серверу. В разделе реестра
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
создайте параметр DWORD с именем KeepAliveTime и значением 300000 (десятичным). Параметр задаётся в миллисекундах, поэтому предлагаемое значение — это 5 минут. После чего остановите Caché и перезагрузите сервер.
Что касается двух других параметров tcp_keepalive, то их умолчания в Windows таковы:
KeepAliveInterval
Key: Tcpip\Parameters
Value Type: REG_DWORD—time in milliseconds
Valid Range: 0–0xFFFFFFFE
Default: 1000 (1 секунда)
KeepAliveProbes
Такого параметра (устанавливающего количество неподтвержденных проверочных пакетов), в реестре не существует. Согласно [2], в Windows 2000 / XP / 2003 в этом качестве используется значение параметра TcpMaxDataRetransmission (умолчание — 5), а в более поздних версиях [3] — фиксированное значение, равное 10. Поэтому, если менять только значение первого параметра (с 7200 на 300), сохраняя умолчание для второго, сервер Windows 2008 будет узнавать о разрыве TCP-соединения через 1*10 + 300 = 310 секунд.
Установка параметров tcp_keepalive на сервере Linux
Изменить значения параметров можно «на ходу», не перезагружая сервер. Зайдите как root и выполните:
echo 300 > /proc/sys/net/ipv4/tcp_keepalive_time
Чтобы сделать изменение долговечным по отношению к возможным перезагрузкам сервера, проще всего отредактировать файл параметров ядра /etc/sysctl.conf, добавив в него строку (лучше две):
# help to prevent disconnects net.ipv4.tcp_keepalive_time = 300
Обратите внимание, что в отличие от Windows, значение параметра задается в секундах.
Что касается остальных двух параметров tcp_keepalive, то их умолчания в Linux таковы:
net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_keepalive_probes = 9
Если менять только значение первого параметра (с 7200 на 300), сохраняя умолчания для остальных двух, сервер Linux будет узнавать о разрыве соединения только через 75*9 + 300 = 975 секунд.
Установка параметра TCPKeepAlive в конфигурации СУБД Caché
Начиная с версии 2008.2, в Caché для платформ Windows и Linux появилась возможность задавать tcp_keepalive_time на уровне сокета, что удобно, так как позволяет избежать изменения настроек операционной системы. Однако «в чистом виде» эта возможность, в основном, представляет интерес лишь для независимых разработчиков сокет-серверов. К счастью, она была дополнена параметром конфигурации TCPKeepAlive=n в секции [SQL], доступным для редактирования со страницы Портала управления системой: Конфигурация > Общие Настройки SQL. Значение по умолчанию — 300 секунд (то, что доктор прописал). Действие параметра распространяется не только на SQL, но и, как нетрудно догадаться, на любые соединения с Caché, обслуживаемые сервисом %Service_Bindings. К ним относится, в частности, и объектный доступ через CacheActiveX.Factory, поэтому если ваше приложение может использовать этот протокол в качестве транспорта, не стоит упускать такую возможность.
Установка параметров KeepAlive в конфигурации сервера OpenSSH
Если вы используете SSH [4] (для работы в режиме командной оболочки или как транспорт для вашего GUI-приложения), то… скорее всего, проделанной настройки ядра будет достаточно, поскольку сервис OpenSSH (по крайней мере, в версии 5.x) по-умолчанию открывает сокет с параметром SO_KEEPALIVE.
На всякий случай стоит проверить конфигурационный файл /etc/ssh/sshd_config. Найдите в нем строку
#TCPKeepAlive yes
Если нашли, то делать ничего не надо, так как значения параметров по умолчанию предоставляются в закомментированном виде.
Протокол SSH v.2 имеет альтернативные средства контроля активности сеансов, например, с помощью настройки параметров сервиса OpenSSH ClientAliveInterval и ClientAliveCountMax.
При использовании этих параметров, в отличие от TCPKeepAlive, запросы KeepAlive отправляются через защищённый SSH канал и не могут быть подменены. Приходится признать, что альтернативные средства являются более безопасными, нежели традиционный механизм TCPKeepAlive, для которого существует опасность анализа KeepAlive-пакетов и организации DoS-атак [5].
Устанавливает время ожидания в секундах, по истечении которого, если не поступает информация со стороны клиента, sshd отправляет ему запрос отклика через защищённый канал. По умолчанию используется 0, что означает, что клиенту не будет направлен такой запрос.ClientAliveInterval 0
Устанавливает количество запросов клиенту, которые могут быть отправлены sshd без получения на них отклика. Если предел достигнут, sshd разъединяется с клиентом и завершает сеанс. Значение по умолчанию: 3. Если вы установите значение параметра ClientAliveInterval равным 60, оставив ClientAliveCountMax без изменений, то не отвечающие ssh-клиенты будут отключены примерно через 180 секунд. При этом следует отключить механизм TCP KeepAlive, установивClientAliveCountMax 3
TCPKeepAlive no
Всегда ли это работает?
Существуют категории сетевых проблем, в которых описанный подход может быть малоэффективен.
Одна из них имеет место, когда из-за низкого качества сетевого обслуживания связь может физически пропадать в течение коротких промежутков времени. Если сеанс бездействует, а связь временно пропадает и восстанавливается до того, как клиент или сервер попытаются что-то друг другу послать, то никто из них ничего «не замечает», и TCP-сеанс сохраняется. В случае периодических проверок TCPKeepAlive возрастает вероятность обращения сервера к клиенту в моменты временного исчезновения связи, что может вести к принудительным разрывам TCP-соединения. В такой ситуации можно попробовать увеличить на сервере KeepAliveInterval до 60-75 секунд (вспомнив, что в Windows умолчание — 1 секунда) при максимальном количестве повторов равным 10, в надежде, что за 10 минут любая временная сетевая проблема самоустранится. Правда, если повторные передачи будут длиться слишком долго, и окажется, что
KeepAliveTime + (KeepAliveInterval * кол-во_повторов) > 10 минут
то TCP-сеанс, несмотря на все предпринятые усилия, может быть принят за «мёртвый» и вычищен из NAT-таблицы.
Другая категория проблем связана с недостаточной пропускной способности используемых маршрутизаторов и/или каналов связи, когда при перегрузке могут теряться любые пакеты, в том числе и KeepAlive. В случае маршрутизаторов такие проблемы иногда решаются сменой прошивки (мне, например, это помогло победить Acorp ADSL XXXX), или, в худшем случае, заменой его на более производительную модель. В случае «слишком узких» каналов связи не остаётся ничего другого, кроме как их расширять.
Заключение
Предложенный подход позволяет искусственно поддерживать активность сеансов TCP/IP, по которым в текущий момент не передается никаких данных, исключительно на системном уровне, не внося каких-либо изменений в прикладной код. На сегодня он проверен и успешно используется в Caché for UNIX (Red Hat Enterprise Linux 5 for x86-64) 2010.1.4 (Build 803), Caché for Windows (x86-64) 2010.1.4 (Build 803), а также в более поздних версиях.
Следует признать, что он эффективно работает, если сетевое соединение физически устойчиво, и кроме разрыва неактивных сеансов других сетевых проблем у вас нет.
При развёртывании приложения в агрессивной среде (удалённый доступ, распределённые системы и т.д.), подумайте о реализации KeepAlive не на уровне TCP, а на уровне защищённого протокола более высокого уровня; хорошим кандидатом здесь оказывается SSH.
Литература
[1] Fabio Bussato. TCP Keepalive HOWTO
[2] How to harden the TCP/IP stack against denial of service attacks in Windows Server 2003
[3] TCP/IP Registry Values for Microsoft Windows Vista and Windows Server 2008
[4] Интерактивная система просмотра системных руководств (man-ов)
[5] OpenSSH: установка и конфигурирование sshd
