Глубокое понимание тайм-аутов JDBC

Java

Это самая подробная статья, которую я недавно читал о проблеме тайм-аута JDBC. Исходный текст: http://www.cubrid.org/blog/understanding-jdbc-internals-and-timeout-configuration. Да, много контекстной информации. утеряна, вот ретрансляция с моим пониманием.

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

Сервер веб-приложений перестает отвечать на запросы после DDoS-атаки

(Это пересказ того, что произошло в реальном случае)

После DDoS-атаки вся служба не может работать должным образом, так как не работает коммутатор 4-го уровня и разрывается сетевое соединение, что также вызывает WAS (WAS можно понимать как приложение компании автора) Не может нормально работать. Вскоре после атаки служба безопасности заблокировала все DDoS-атаки, и сеть вернулась в нормальное функционирование, но WAS по-прежнему не работал.

При анализе журнала дампа системы было обнаружено, что бизнес-система остановилась при вызове JDBC API. Через 20 минут система все еще находилась в состоянии ожидания и не могла ответить.Примерно через 30 минут система внезапно вышла из строя, а затем служба вернулась в нормальное русло.

Почему WAS ждет 30 минут после установки времени ожидания запроса на 3 секунды? Почему WAS снова начал работать через 30 минут?

Если вы понимаете механизм тайм-аута JDBC, вы можете найти ответ.

Зачем нам нужно знать драйвер JDBC

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

Что такое драйвер JDBC

JDBC — это набор стандартных API для доступа к базам данных в приложениях Java, определенных Sun.4 типа драйверов JDBC. Моя компания в основном использует четвертый тип, который написан на чистом языке Java и взаимодействует с базой данных через сокет в приложениях Java.

图1: 类型4驱动

Драйверы типа 4 обрабатывают потоки байтов через сокеты, и их основные операции такие же, как у HttpClient, библиотеки классов сетевых операций. Как и другие сетевые библиотеки, она также потребляет много ресурсов ЦП и теряет отклик, когда происходит тайм-аут. Если вы использовали HttpClient раньше, вы, должно быть, столкнулись с ошибками, вызванными тем, что не установили время ожидания. Драйверы типа 4 также могут иметь ту же ошибку (соединение заблокировано), если тайм-аут сокета не установлен должным образом.

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

Иерархия для установки тайм-аутов между WAS и базой данных

图2: 超时的层次

На рис. 2 показана иерархия времени ожидания для упрощенной связи WAS и базы данных.

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

Мы получили много комментариев, в которых говорилось:

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

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

На тайм-аут сокета драйвера JDBC также влияет конфигурация тайм-аута сокета в операционной системе. Это объясняет, почему соединение JDBC в данном случае было заблокировано на 30 минут после сбоя в сети, несмотря на то, что время ожидания сокета для драйвера JDBC не было настроено.

Пул соединений DBCP находится в левой части рисунка 2. Вы обнаружите, что тайм-ауты на различных уровнях отделены от DBCP. DBCP отвечает за соединения с базой данных (т.Connection) создание и управление не требуют обработки времени ожидания. Когда в DBCP создается подключение к базе данных или отправляется SQL-оператор для проверки правильности подключения, тайм-аут сокета влияет на обработку этих процессов, но не влияет напрямую на приложение.

Однако при вызове метода getConnection() DBCP в приложении можно указать время ожидания для приложения для получения соединения с базой данных, но это не имеет ничего общего с временем ожидания соединения JDBC.

图3: 每一层级的超时

Что такое тайм-аут транзакции

время ожидания транзакцииТайм-ауты действительны только на уровне платформы (Spring, контейнер EJB) или приложения.

Тайм-ауты транзакций могут быть необычайна концепция. Проще говоря, тайм-аут транзакции равна ** тайм-аут оператора * N (количество операторов, которые необходимо выполнить) + другие (сбор мусора и другие раз) **. Тайм-аут транзакции используется для ограничения общего времени выполнения всех выполнений операторов в транзакции.

Например, если для выполнения оператора требуется 0,1 секунды, то выполните оператор несколько раз. Это не проблема, но если он будет выполнен 100 000 раз, это займет 10 000 секунд (около 7 часов), которые могут использовать тайм-аут транзакции.

Декларативное управление транзакциями EJB (транзакция, управляемая контейнером) является типичным сценарием использования, но декларативное управление транзакциями лишь определяет соответствующие спецификации, а за обработку и реализацию транзакций в контейнере отвечает разработчик контейнера. Наша компания не использует EJB, а использует самый распространенный фреймворк Spring, поэтому Spring также управляет конфигурацией тайм-аута транзакций. В Spring тайм-ауты транзакций можно настроить явно в файле XML или в коде Java с помощью аннотации Transactional.

<tx:attributes>
        <tx:method name="…" timeout="3"/>
</tx:attributes>

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

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

Реализация декларативного управления транзакциями в EJB аналогична, и идея реализации очень проста. Если тайм-аут транзакции очень важен, но используемый вами контейнер или фреймворк не предоставляют эту функцию, вы также можете реализовать ее самостоятельно, стандартного API для тайм-аута транзакции не существует.

Версии 1.5 и 1.6 платформы Lucy не поддерживают тайм-ауты транзакций, но вы можете добиться того же эффекта с помощью управления транзакциями Spring.

Предполагая, что в транзакции 5 операторов, время выполнения каждого оператора составляет 200 миллисекунд, а время выполнения другой бизнес-логики или операций платформы составляет 100 миллисекунд, тогда допустимое время ожидания транзакции должно быть не менее 1100 миллисекунд ( 200*5+100).

Что такое время ожидания заявления

Тайм-аут оператора используется для ограничения времени выполнения оператора, и его конкретное значение устанавливается через JDBC API. Драйвер JDBC выполняет обработку тайм-аута во время выполнения инструкции на основе этого значения. Тайм-аут оператора настраивается с помощью метода setQueryTimeout(int timeout) класса java.sql.Statement в JDBC API. Однако современные разработчики редко настраивают его прямо в коде, а чаще задают через фреймворк.

Взяв в качестве примера iBatis, вы можете установить глобальное значение тайм-аута оператора по умолчанию с помощью атрибута настройки defaultStatementTimeout в SqlMapConfig.xml. Вы также можете переопределить атрибут оператора тега обновления select вставки в конкретном файле сопоставления sql.

При использовании Lucy версии 1.5 или 1.6 можно установить время ожидания оператора на уровне источника данных, установив свойство queryTimeout.

Конкретное значение тайм-аута оператора зависит от ситуации в каждом приложении, и нет рекомендуемой конфигурации.

Обработка тайм-аута оператора в драйвере JDBC

Обработка тайм-аутов операторов также различается для каждой базы данных и драйвера. Oracle и SQLServer работают одинаково, MySQL и CUBRID похожи.

Обработка тайм-аута оператора в Oracle

  1. Вызовите метод createStatement() Connection, чтобы создать объект Statement.
  2. Вызовите метод executeQuery() оператора.
  3. Оператор отправляет команды запроса в базу данных Oracle через внутренне связанный объект Connection.
  4. Заявление для потока обработки тайм-аута OracleOracleTimeoutPollingThread(по одному для этого потока на загрузчик классов) зарегистрируйте оператор для обработки тайм-аутов
  5. Произошел тайм-аут
  6. OracleTimeoutPollingThread от Oracle вызывает метод cancel() OracleStatement
  7. Отправьте сообщение через соединение оператора, чтобы отменить запрос, который все еще выполняется.

图4 Oracle 的 Statement 超时执行过程

Обработка тайм-аута оператора в JTDS (MS SQLServer)

1. Вызовите метод createStatement() Connection, чтобы создать объект Statement. 2. Вызовите метод executeQuery() оператора. 3. Оператор отправляет команды запроса в базу данных MS SqlServer через внутреннее соединение. 4. Постановка на MS SQLServerTimerThreadПоток регистрирует оператор для обработки тайм-аутов. 5. Происходит тайм-аут 6. TimerThread вызывает метод TsdCore.cancel() внутри JtdsStatement. 7. Отправьте сообщение через ConnectionJDBC, чтобы отменить текущий запрос.

图5 MS SQLServer 的 Statement 超时执行过程

Обращение времени ожидания в MySQL (5.0.8)

  1. Вызовите метод createStatement() Connection, чтобы создать объект Statement.
  2. Вызовите метод executeQuery() оператора.
  3. Оператор передает команды запроса в базу данных MySqlServer через внутреннее соединение.
  4. Оператор создает новый поток выполнения тайм-аута (timeout-execution) для обработки тайм-аутов
  5. Версия 5.1 и выше изменена, чтобы выделять один поток для каждого соединения.
  6. Зарегистрируйте текущий оператор в потоке выполнения тайм-аута.
  7. Произошел тайм-аут
  8. Поток выполнения тайм-аута создает соединение с той же конфигурацией.
  9. Отправьте команду для отмены запроса с вновь созданным соединением.

图6 MySQL 的 Statement 超时执行过程

Обработка тайм-аута оператора в CUBRID

  1. Вызовите метод createStatement() Connection, чтобы создать объект Statement.
  2. Вызовите метод executeQuery() оператора.
  3. Оператор отправляет команды запроса в базу данных CUBRID через внутреннее соединение.
  4. Оператор создает новый поток выполнения тайм-аута (timeout-execution) для обработки тайм-аутов
  5. Зарегистрируйте текущий оператор в потоке выполнения тайм-аута.
  6. Произошел тайм-аут
  7. Поток выполнения тайм-аута создает соединение с той же конфигурацией.
  8. Отправьте команду для отмены запроса с вновь созданным соединением.

图7 CUBRID 的 Statement 超时执行过程

Что такое время ожидания сокета

Драйвер JDBC типа 4 подключается к базе данных через Socket, и время ожидания соединения между приложением и базой данных не обрабатывается базой данных.

Когда база данных внезапно выходит из строя или возникает сетевая ошибка (сбой устройства и т. д.), требуется значение времени ожидания сокета, управляемое JDBC. Из-за структуры TCP/IP Socket не может обнаруживать сетевые ошибки, поэтому приложение не может определить потерю соединения с базой данных. Если время ожидания сокета не установлено, приложение будет ждать, пока база данных вернет результат. (Это соединение также называется «мертвым соединением»). Чтобы избежать мертвого соединения, Socket должен установить тайм-аут. Время ожидания сокета можно настроить с помощью драйвера JDBC. Установив тайм-аут сокета, вы можете предотвратить ожидание и сократить время простоя в случае сетевой ошибки.

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

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

  • Тайм-аут подключения к сокету: настраивается с помощью метода connect (конечная точка SocketAddress, int timeout) объекта Socket.
  • Тайм-аут для чтения и записи Socket: настраивается с помощью метода setSoTimeout(int timeout) объекта Socket.

Изучив исходный код драйвера JDBC для CUBRID, MySQL, MS SQL Server (JTDS) и Oracle, мы подтвердили, что все вышеперечисленные драйверы используют два вышеуказанных API для установки времени ожидания сокета.

Ниже перечислены способы настройки тайм-аута сокета.

JDBC-драйвер Конфигурация тайм-аута соединения Конфигурация тайм-аута сокета Формат URL-адреса JDBC Пример
MySQL connectTimeout (по умолчанию: 0, единица измерения: миллисекунды) socketTimeout (по умолчанию: 0, единица измерения: мс) jdbc:mysql://[host:port],[host:port].../[database]
[?propertyName1][=propertyValue1][&propertyName2][=propertyValue2]...
jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000
MS-SQL , jTDS loginTimeout (по умолчанию: 0, единица измерения: секунды) socketTimeout (по умолчанию: 0, единица измерения: с) jdbc:jtds:<server_type>://[:][/][;=[;...]] jdbc:jtds:sqlserver://server:port/database;loginTimeout=60;socketTimeout=60
Oracle oracle.net.CONNECT_TIMEOUT (по умолчанию: 0, единица измерения: миллисекунды) oracle.jdbc.ReadTimeout (по умолчанию: 0, единица измерения: миллисекунды) Он не поддерживает настройку через URL-адрес и может быть установлен только через API OracleDatasource.setConnectionProperties(). При использовании DBCP вы можете вызвать BasicDatasource.setConnectionProperties() или BasicDatasource.addConnectionProperties(), чтобы установить -
CUBRID Нет отдельных элементов конфигурации (по умолчанию: 5000, единица измерения: миллисекунды) Нет отдельных элементов конфигурации (по умолчанию: 5000, единица измерения: миллисекунды) - -
  • Значение по умолчанию для connectTimeout и socketTimeout равно 0, что означает, что тайм-аут не произойдет.
  • Вы также можете настроить через свойства без прямого использования API DBCP.

При настройке через свойства необходимо передать ключ «connectionProperties», а формат его значения — «[propertyName=property;]*». Ниже приведен пример настройки свойств через файл xml в iBatis.

<transactionManager type="JDBC">
  <dataSource type="com.nhncorp.lucy.db.DbcpDSFactory">
     ....
     <property name="connectionProperties" value="oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=6000"/> 
  </dataSource>
</transactionManager>

Настройка времени ожидания сокета на уровне операционной системы

Если тайм-аут сокета или тайм-аут соединения не установлен, в большинстве случаев программа не может обнаруживать сетевые ошибки. На этом этапе приложение будет ждать, пока база данных не будет подключена или данные не смогут быть прочитаны. Однако, если вы посмотрите на реальную ситуацию, с которой столкнулся реальный сервис, проблема часто решается после того, как приложение (WAS) попытается повторно подключиться к сети через 30 минут. Это связано с тем, что операционная система также настраивает время ожидания сокета. На сервере Linux, который использует моя компания, тайм-аут сокета установлен на 30 минут. Это проверит сетевое соединение на уровне ОС. Поскольку цикл проверки KeepAlive на Linux-сервере компании составляет 30 минут, даже если время ожидания сокета в приложении установлено равным 0, проблема с сетевым подключением к базе данных, вызванная сетью, не превысит 30 минут.

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

До сих пор я объяснял внутреннюю работу JDBC, надеюсь, это поможет вам правильно настроить время ожидания и уменьшить количество ошибок.

До сих пор я объяснял внутреннюю работу JDBC, надеясь, что все узнают, как правильно настроить время ожидания, тем самым уменьшая количество ошибок.