Расскажите о настройке jdbc socketTimeout

задняя часть

последовательность

Эта статья в основном знакомит с настройкой тайм-аута сокета jdbc.

категория времени ожидания jdbc

В основном это следующие категории

  • transaction timeout

Установите время выполнения транзакции, которая может содержать несколько операторов

  • statement timeout(也相当于result set fetch timeout)

Установлено время ожидания выполнения оператора, то есть время ожидания драйвером завершения выполнения оператора и получения данных (注意statement的timeout不是整个查询的timeout,只是statement执行完成并拉取fetchSize数据返回的超时,之后resultSet的next在必要的时候还会触发fetch数据,每次fetch的超时时间是单独算的,默认也是以statement设置的timeout为准)

  • jdbc socket timeout

Период тайм-аута для операций чтения и записи сокета ввода-вывода jdbc установлен, чтобы предотвратить блокировку и ожидание драйвера из-за проблем с сетью или базой данных. (建议比statement timeout的时间长)

  • os socket timeout

Это настройка сокета уровня ОС (如果jdbc socket timeout没有设置,而os级别的socket timeout有设置,则使用系统的socket timeout值).

Чем выше тайм-аут различных уровней выше, тем выше приоритет, то есть, если следующая конфигурация меньше, чем указанное выше значение конфигурации, тайм-аут будет активирован первым, что эквивалентно указанному выше значению конфигурации «недействительно».

jdbc socket timeout

Реализация драйвера jdbc для этих разных данных отличается

mysql

jdbc:mysql://localhost:3306/ag_admin?useUnicode=true&characterEncoding=UTF8&connectTimeout=60000&socketTimeout=60000

Передайте его через параметр URL

pg

jdbc:postgresql://localhost/test?user=fred&password=secret&&connectTimeout=60&socketTimeout=60

pg также передается через URL-адрес, но его единица измерения отличается от mysql, mysql — миллисекунды, а pg — секунды.

oracle

Oracle необходимо установить с помощью параметра oracle.jdbc.ReadTimeout, а параметр тайм-аута соединения — oracle.net.CONNECT_TIMEOUT.

  • Установить по свойствам
            Class.forName("oracle.jdbc.driver.OracleDriver");
            Properties props = new Properties() ;
            props.put( "user" , "test_schema") ;
            props.put( "password" , "pwd") ;
            props.put( "oracle.net.CONNECT_TIMEOUT" , "10000000") ;
            props.put( "oracle.jdbc.ReadTimeout" , "2000" ) ;
            Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@10.0.1.9:1521:orcl" , props ) ;
  • Установить через переменные окружения
String readTimeout = "10000"; // ms
System.setProperty("oracle.jdbc.ReadTimeout", readTimeout);
Class.forName("oracle.jdbc.OracleDriver");
Connection conn = DriverManager.getConnection(jdbcUrl, user, pwd);

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

  • tomcat jdbc pool

Как правило, мы не используем соединение jdbc напрямую, а используем пул соединений. Поскольку пул tomcat jdbc — это пул соединений с базой данных, используемый Springboot по умолчанию, вот как установить его в пуле tomcat jdbc.

spring.datasource.tomcat.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000

Обратите внимание, что точка с запятой здесь отделена, единицей измерения являются миллисекунды, а префикс можно настроить в соответствии с соответствующей ситуацией (tomcat jdbc连接池的话,默认是spring.datasource.tomcat), которые можно настроить, например

    @Bean
    @Qualifier("writeDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.write")
    public DataSource writeDataSource() {
        return DataSourceBuilder.create().build();
    }

Предположим, вы настроили префикс здесь как spring.datasource.write, тогда приведенная выше конфигурация становится

spring.datasource.write.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000

Если oracle.jdbc.ReadTimeout не установлен, значение по умолчанию в драйвере равно 0

oracle.jdbc.ReadTimeout

Драйвер внутренне устанавливает это значение в переменную oracle.net.READ_TIMEOUT.

  • oracle.net.nt.TcpNTAdapter
    @Override
    public void setReadTimeoutIfRequired(final Properties properties) throws IOException, NetException {
        String s = ((Hashtable<K, String>)properties).get("oracle.net.READ_TIMEOUT");
        if (s == null) {
            s = "0";
        }
        this.setOption(3, s);
    }
    
    public void setOption(int var1, Object var2) throws IOException, NetException {
        String var3;
        switch(var1) {
        case 0:
            var3 = (String)var2;
            this.socket.setTcpNoDelay(var3.equals("YES"));
            break;
        case 1:
            var3 = (String)var2;
            if(var3.equals("YES")) {
                this.socket.setKeepAlive(true);
            }
        case 2:
        default:
            break;
        case 3:
            this.sockTimeout = Integer.parseInt((String)var2);
            this.socket.setSoTimeout(this.sockTimeout);
        }

    }

Доступно, чтобы увидеть, что последний набор - это soTimeout сокета

пример

	@Test
	public void testReadTimeout() throws SQLException {
		Connection connection = dataSource.getConnection();
		String sql = "select * from demo_table";
		PreparedStatement pstmt;
		try {
			pstmt = (PreparedStatement)connection.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery();
			int col = rs.getMetaData().getColumnCount();
			System.out.println("============================");
			while (rs.next()) {
				for (int i = 1; i <= col; i++) {
					System.out.print(rs.getObject(i));
				}
				System.out.println("");
			}
			System.out.println("============================");
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			//close resources
		}
	}

вывод ошибки тайм-аута

//部分数据输出......
java.sql.SQLRecoverableException: IO 错误: Socket read timed out
	at oracle.jdbc.driver.T4CPreparedStatement.fetch(T4CPreparedStatement.java:1128)
	at oracle.jdbc.driver.OracleResultSetImpl.close_or_fetch_from_next(OracleResultSetImpl.java:373)
	at oracle.jdbc.driver.OracleResultSetImpl.next(OracleResultSetImpl.java:277)
	at com.example.demo.DemoApplicationTests.testReadTimeout(DemoApplicationTests.java:68)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: oracle.net.ns.NetException: Socket read timed out
	at oracle.net.ns.Packet.receive(Packet.java:339)
	at oracle.net.ns.DataPacket.receive(DataPacket.java:106)
	at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:315)
	at oracle.net.ns.NetInputStream.read(NetInputStream.java:260)
	at oracle.net.ns.NetInputStream.read(NetInputStream.java:185)
	at oracle.net.ns.NetInputStream.read(NetInputStream.java:102)
	at oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket(T4CSocketInputStreamWrapper.java:124)
	at oracle.jdbc.driver.T4CSocketInputStreamWrapper.read(T4CSocketInputStreamWrapper.java:80)
	at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1137)
	at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:290)
	at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
	at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
	at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:207)
	at oracle.jdbc.driver.T4CPreparedStatement.fetch(T4CPreparedStatement.java:1119)
	... 35 more

В начале будет вывод данных, но когда они дойдут до следующего набора результатов, будет сообщено о тайм-ауте (close_or_fetch_from_next), это время ожидания указывает, когда метод result.next инициирует получение нового пакета данных (当一个fetchSize的数据消费完之后,接下来的next会触发新一批数据的fetch), а затем данные, возвращаемые базой данных, не принимаются в течение времени ожидания.

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

резюме

Установка значения socketTimeout для jdbc должна быть очень осторожной.Настройки драйвера jdbc для разных баз данных отличаются, особенно если используются разные пулы соединений, настройки также могут отличаться. Для служб, сильно зависящих от операций с базой данных, очень важно установить это значение, иначе в случае исключений сети или базы данных поток службы всегда будет заблокирован в java.net.SocketInputStream.socketRead0.

  • Если есть много данных запросов, список данных, удерживаемый потоком, который не может быть выпущен, что эквивалентно утечке памяти, и, наконец, приводит к OOM
  • Если запрошенная операция базы данных слишком велика и заблокирована, количество рабочих потоков, доступных серверу, будет уменьшено, а в тяжелых случаях служба будет недоступна, и nginx сообщит 504 Gateway Timeout.

doc