首页 > 其他分享 >【SpringBoot】服务停止数据源的关闭时机

【SpringBoot】服务停止数据源的关闭时机

时间:2024-05-22 20:51:06浏览次数:21  
标签:SpringBoot 数据源 默认 关闭 超时 连接 空闲

1  前言

微服务中我们会用到数据源,数据源中其实就是管理我们的数据库连接,对于数据库而言,连接数是很珍贵的资源,所以释放无用或者长时间空闲的连接显得很重要。

那么对于微服务比如我们的 SpringBoot 当服务启动的时候会初始化数据源,那么停止的时候,是如何关闭数据源,释放连接的呢?这节我们就来看看不同数据源下的连接释放时机。

这节我们主要从三种数据源看起:动态数据源(DynamicDataSource)、SpringBoot现在默认的数据源(HikariDataSource)、阿里的德鲁伊(DruidDataSource),看下三者是什么时候释放连接的。

2  释放方式 

我们站在服务的角度,也就是我们的应用角度,释放时机分主动(应用主动关闭数据库连接)和被动(数据库服务关闭连接)。

2.1  被动方式

我理解的每个数据库都有自己的空闲连接或者超时连接的释放,我查了下来自(通义千问):

不同数据库系统的默认空闲连接超时时间设置各不相同,以下是一些常见数据库的默认空闲超时时间概览:

  • MySQL: 默认的空闲连接超时时间是8小时,通过wait_timeout和interactive_timeout两个变量控制。这两个变量的默认值通常是28800秒(8小时)。

  • PostgreSQL: PostgreSQL默认没有明确的空闲超时设置。但可以通过设置idle_in_transaction_session_timeout参数来限制处于事务中但空闲的会话时间,其默认值是0,意味着没有限制。

  • Microsoft SQL Server: SQL Server的默认空闲超时由连接字符串中的Connection Lifetime或Idle Timeout参数决定,或者由数据库服务器的配置设置。如果不显式设置,通常没有默认的空闲超时限制。

  • Oracle: Oracle数据库的空闲超时时间可以通过配置Profile来设定,特别是通过修改IDLE_TIME参数。默认情况下,如果未特别配置,Oracle可能没有固定的空闲超时时间,需要根据实际的Profile设置来确定。

  • OceanBase: OceanBase的默认空闲连接超时时间也是8小时,通过wait_timeout和interactive_timeout设置,类似于MySQL。另外,OceanBase还提供了ob_trx_timeout来控制事务超时时间,默认值为100000000微秒(约100秒)。  

请注意,这些默认值可能会随数据库版本的不同而有所变化,且通常都可以通过数据库的配置文件或动态修改系统参数的方式来调整。在实际应用中,根据应用需求和资源管理策略,通常会根据具体情况调整这些参数。

被动方式的,我看 PGSQL 默认居然不设置,那我有疑问了,那我服务强制停止,那连接谁来释放呢?下边我会实际来操作,把它归到了主动方式里。

2.2  主动方式(正常)

主动方式我这里分开,一种是服务正常停止也叫优雅停止比如类似 kill -15,一种是非正常的比如强制停止服务类似 kill -9。

2.2.1  SpringBoot + ShutdownHook

在了解正常的服务停止时,我们得先回忆下 Runtime.getRuntime().addShutdownHook(...),这个是 JVM 退出时会执行的回调,这个之前看 SpringBoot 的启动过程或者 Spring 的上下文刷新的时候都有看过,这里我们简单看下:

在 SpringBoot 里的 Bean 实现了 DisposableBean 接口的 Bean都会得到执行,至于 SpringBoot 什么时候放进去这个回调的,是在 Springboot 刷新上下文的时候注册的 shutdownHook:

{@link org.springframework.boot.SpringApplication#refreshContext(ConfigurableApplicationContext)
private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
               }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
       }*     }
}
@Override
public void registerShutdownHook() {
    if (this.shutdownHook == null) {
        // No shutdown hook registered yet.
        this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
                       @Override
           public void run() {
                synchronized (startupShutdownMonitor) {
                    doClose();
               }
           }        *         };
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
   }
}

doClose() 方法里的 destroyBeans(); 就会调用每个实现了 DisposableBean 接口的 destory 销毁方法。

2.2.2  DynamicDataSource下的关闭

动态数据源就是实现了销毁的接口:

public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {
    ...
    // 释放每个数据源的连接
    public void destroy() throws Exception {
        log.info("dynamic-datasource start closing ....");
        Iterator var1 = this.dataSourceMap.entrySet().iterator();
        while(var1.hasNext()) {
            Entry<String, DataSource> item = (Entry)var1.next();
            DataSource dataSource = (DataSource)item.getValue();
            if (this.seata) {
                DataSourceProxy dataSourceProxy = (DataSourceProxy)dataSource;
                dataSource = dataSourceProxy.getTargetDataSource();
            }
            if (this.p6spy) {
                Field realDataSourceField = P6DataSource.class.getDeclaredField("realDataSource");
                realDataSourceField.setAccessible(true);
                dataSource = (DataSource)realDataSourceField.get(dataSource);
            }
            Class clazz = dataSource.getClass();
            try {
                // 调用每个数据源的 close 方法
                Method closeMethod = clazz.getDeclaredMethod("close");
                closeMethod.invoke(dataSource);
            } catch (NoSuchMethodException var6) {
                log.warn("dynamic-datasource close the datasource named [{}] failed,", item.getKey());
            }
        }
        log.info("dynamic-datasource all closed success,bye");
    }
}

效果如下图:

2.2.3  HikariDataSource下的关闭

(1)spring.datasource.hikari.idle-timeout 空闲时间设置,超过该设置会数据源会释放该连接,有个固定间隔的调度任务会来进行这项操作:

 (2)close 方法 关闭数据源:

这块有个小困惑,我发现服务正常停止的情况下,没有打印日志,我怀疑是不是默认情况下不会主动释放,而是跟那种非正常的系统会自动关闭 socket 类似。

2.2.4  DruidDataSource下的关闭

(1)spring.datasource.hikari.idle-timeout 空闲时间设置,超过该设置会数据源会释放该连接,有个固定间隔的调度任务会来进行这项操作:

 (2)close 方法 关闭数据源:

这个关闭我看见日志了,说明有人调用了 close,但是但是但是我得说三个但是我没找到哪里调用的......debug走不到我的断点,打开debug日志也看不到是谁调用的....这有点让我难解,还望有大佬知道的话告知下。

2.3  主动方式(强停/非正常)

像上面 PGSQl 默认空闲超时是不限制的,那么比如我服务启动后我初始化了10条连接,然后我直接 kill -9,直接强制停掉,那这10个连接就一直占着了么,tcp不释放的么?

我抓了下包,发现强制停止,是会发送 RST 的标志,告诉服务器关闭连接的:

我觉得是不是进程停止的时候,是不是会主动释放该进程所关联的全部 socket呢?我问了下通义千问:

直接强制停止一个Spring Boot微服务(或任何使用TCP连接的应用程序)时,由于进程没有机会执行正常的清理和关闭连接的操作,操作系统会介入以终止这些连接。在这种情况下,操作系统通常会向对端发送带有RST(Reset)标志的TCP段,通知对方连接异常终止。

好吧,具体原理咱就不知道了,知道的是强制停止能关闭连接,那非常好。

3  小结  

好啦,这节主要是想看下服务停止的时候,数据源的连接是如何释放的,有理解不对的地方还请指点哈。

标签:SpringBoot,数据源,默认,关闭,超时,连接,空闲
From: https://www.cnblogs.com/kukuxjx/p/18207044

相关文章

  • 在springboot项目中,打包本地的外部jar包,到运行的jar包中
    1、配置依赖<dependency><groupId>com.genesyslab</groupId><artifactId>genesyslab</artifactId><version>1.0.0</version><scope>system</scope><systemPath>${project.basedir}/src/main/re......
  • springboot开启热部署
    一、依赖在SpringBoot中启用热部署通常涉及使用SpringBootDevTools依赖和配置。以下是如何在SpringBoot项目中启用热部署的步骤:在pom.xml中添加SpringBootDevTools依赖:<dependencies><!--其他依赖--><dependency><groupId>org.springframework.b......
  • springboot中执行完某些逻辑后,才算bean加载完,applicationContext才加载完毕
    核心思想实现InitializingBean接口,重写afterPropertiesSet方法范例代码importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.stereotype.Component;@Slf4j@ComponentpublicclassDemoimplementsI......
  • Hadoop集群启动与关闭
    启动集群点击查看代码[root@master~]#start-all.shStartingnamenodeson[master]StartingdatanodesStartingsecondarynamenodes[master]StartingresourcemanagerStartingnodemanagers!可以在master执行hive命令直接启动hive,无需在提供服务的clone1上首先启动......
  • springboot集成logback-spring.xml日志文件
    logback-spring.xml:<!--Logbackconfiguration.Seehttp://logback.qos.ch/manual/index.html--><configurationscan="true"scanPeriod="10seconds"><springPropertyscope="context"name="logLevel"s......
  • Java核心面试知识集—SpringBoot面试题
    概述什么是SpringBoot?SpringBoot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。SpringBoot有哪些优点?SpringBoot主要有如下优点:容易上手,提升开发效率,为Spring开发......
  • 【Springboot】复杂单元测试启动类-只测试OpenFeign
    复杂单元测试启动类-只测试OpenFeign背景随着springboot应用工程规模越来越大,集成了较多的自动配置的程序,例如SpringDataJPA,SpringCloudOpenFeign,ApacheDubbo有时会需要在本地运行测试,但要么因为数据库无法在办公网络环境连接,要么注册中心无法连接,这就导致本地完全无......
  • 关于线程池优雅关闭
    使用线程池的问题程序关闭时(eg.上线),线程池中的任务会丢失(内存中)。线程池优雅关闭利用Spring中ContextClosedEvent:关闭程序触发的事件,在使用线程池的地方,可以将线程池注册到ThreadPoolShutdownListener中,然后在程序关闭时,ThreadPoolShutdownListener会监听ContextClosedEvent事......
  • springboot 请求前自动给 参数的某个属性赋值
     springboot请求前自动给参数的某个属性赋值在SpringBoot中,可以通过自定义HandlerMethodArgumentResolver来在请求处理方法前自动给参数的某个属性赋值。以下是一个简单的例子:创建一个自定义注解来标记需要自动赋值的参数:  @Target(ElementType.PARA......
  • springboot使controller异步调用
    调用controller方法,遇到操作时间很长的情况下,不希望前端一直等待操作,而希望前端立马接收到操作启动的反馈,而真正的操作在后端执行,需要用到异步调用的方法。实现步骤如下:一、配置异步支持:首先,在应用程序的主类上添加@EnableAsync注解,以启用异步支持importorg.springfram......