WebFlux实战——R2DBC连接池(r2dbc-pool)配置

3 篇文章 0 订阅
3 篇文章 0 订阅

背景

最近在排查问题时,发现有些流会超时(在最终submit的地方配置了.timeout()),但是这些流本身运行逻辑很简单。通过在不同的位置增加.log()进行记录,最终发现,在数据库操作部分超时了。由于本身业务SQL并不是慢查询,是一个非常简单的SQL,所以在R2DBC的配置上,增加了connectTimeout属性,最终确定是连接数据库超时导致的。报错如下:

17:11:59.665 [ERROR]  [parallel-5] ReactorNettyClient |-> Exchange cancelled while exchange is active. This is likely a bug leading to unpredictable outcome.

到这里,突然想起来,R2DBC的配置没有使用连接池!

关于连接池

我们来看看Druid下的一些熟悉的连接池配置(具体可见官方文档

  • maxActive
  • maxIdle
  • minIdle
  • validationQuery

对!这些配置就是在Druid的DruidDataSource中的最基础配置,用于配置连接池大小,以及如何进行探活。

而在r2dbc-mysql官方GitHub的WiKi中,并没有对应的Demo,以至于笔者最开始在使用R2DBC的时候,没有使用连接池技术。

后来尝试去寻找一些资料,但是无论在google还是百度上,都没有找到R2dbc的pool配置。靠人不如靠自己,还是自己动手吧。

环境

  • SpringBoot 2.4
  • r2dbc-bom:Arabba-SR9
  • r2dbc-mysql
  • spring-boot-starter-data-r2dbc

R2DBC连接池配置

结论

根据惯例,先给出结论,给到希望直接打到DEMO的小伙伴
我们在WebFlux中使用R2DBC的时候,需要构建出ConnectionFactory,它是SQL访问和构建其它工具类(如R2dbcEntityTemplate)的基础。

引用部分

仅仅构建连接池,并不是全部引用都能用到,我就是懒,都复制过来了

import dev.miku.r2dbc.mysql.MySqlConnectionConfiguration;
import dev.miku.r2dbc.mysql.MySqlConnectionFactory;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.ConnectionPoolConfiguration;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.dialect.MySqlDialect;

import java.time.Duration;

常量部分

这部分的配置根据自己的需求进行调整

private final static Duration DEFAULT_TCP_TIMEOUT = Duration.ofSeconds(5);

private final static int DEFAULT_INITIAL_SIZE = 5;
private final static int DEFAULT_MAX_SIZE = 20;
private final static Duration DEFAULT_MAX_ACQUIRE_TIME = Duration.ofSeconds(10);
private final static Duration DEFAULT_MAX_CREATE_CONNECTION_TIME = Duration.ofSeconds(5);
private final static Duration DEFAULT_BACKGROUND_EVICTION_INTERVAL = Duration.ofMinutes(20);
private final static int DEFAULT_ACQUIRE_RETRY = 2;

ConnectionFactory

    /**
     * 创建MySQL的ConnectionFactory的公共方法
     *
     * @param mySqlConfig mySQL连接配置
     * @param dbName      用于日志记录的Bean的描述,通常可以写成DB信息
     * @return 返回MySQL的ConnectionFactory
     */
    protected ConnectionFactory createMySqlConnectionFactory(DatabaseConnectionProperties.MySql mySqlConfig, String dbName) {
    	//记录日志
        log.info("Begin to create connection factory for {}.Get the Configuration :{}", dbName, mySqlConfig.toString());
        //配置基础连接参数
        MySqlConnectionConfiguration configuration = MySqlConnectionConfiguration.builder()
                .host(mySqlConfig.getHost())
                .port(mySqlConfig.getPort())
                .username(mySqlConfig.getUsername())
                .password(mySqlConfig.getPassword())
                .database(mySqlConfig.getDatabase())
                .connectTimeout(DEFAULT_TCP_TIMEOUT)
                .tcpNoDelay(true)
                .tcpKeepAlive(true)
                .build();
        log.info("The MySQL connection configuration for {} has been created:{}", dbName, configuration);
        //构建MySQL的ConnectionFactory
        ConnectionFactory factory = MySqlConnectionFactory.from(configuration);
        //【重点】建立连接池
        ConnectionPoolConfiguration poolConfiguration = ConnectionPoolConfiguration.builder(factory)
                //启动时默认连接池数量
                .initialSize(DEFAULT_INITIAL_SIZE)
                //最长空闲时间
                .maxIdleTime(DEFAULT_BACKGROUND_EVICTION_INTERVAL)
                //连接池最大大小
                .maxSize(DEFAULT_MAX_SIZE)
                //获取连接超时时间
                .maxAcquireTime(DEFAULT_MAX_ACQUIRE_TIME)
                //创建连接超时时间
                .maxCreateConnectionTime(DEFAULT_MAX_CREATE_CONNECTION_TIME)
                //获取连接重试次数
                .acquireRetry(DEFAULT_ACQUIRE_RETRY)
                //连接池名称
                .name("r2dbc_pool_" + dbName)
                //用于测试连接是否可用的语句
                .validationQuery("SELECT 1")
                //是否注册JMX
                .registerJmx(true)
                .build();
        ConnectionPool connectionPool = new ConnectionPool(poolConfiguration);
        log.info("The factory creative option for {} has been done.", dbName);
        return connectionPool;
    }

官方文献

r2dbc-mysql

其实r2dbc-mysql的官方文档中并不是完全没有提及Pool配置,之前也忽略了这一点,在文档中有个Pooling节点,提到了连接池:
Pooling
是的,就2个单词。

r2dbc-pool

R2DBC使用的连接池实现即是r2dbc-pool,在官方文档中给出了我们熟悉的配置的说明
相关配置
但可惜的是没有给出MySQL的相关DEMO。但是可以根据r2dbc-mysql和r2dbc-pool的文档综合起来看。

r2dbc-pool的Demo

完全copy官方文档

// Creates a ConnectionFactory for the specified DRIVER
ConnectionFactory connectionFactory=ConnectionFactories.get(ConnectionFactoryOptions.builder()
        .option(DRIVER,"postgresql")
        .option(HOST,"…")
        .option(PORT,"…")
        .option(USER,"…")
        .option(PASSWORD,"…")
        .option(DATABASE,"…")
        .build());

// Create a ConnectionPool for connectionFactory
        ConnectionPoolConfiguration configuration=ConnectionPoolConfiguration.builder(connectionFactory)
        .maxIdleTime(Duration.ofMillis(1000))
        .maxSize(20)
        .build();

        ConnectionPool pool=new ConnectionPool(configuration);

        Mono<Connection> connectionMono = pool.create();

// later

Connection connection =;
Mono<Void> release = connection.close(); // released the connection back to the pool

// application shutdown
pool.dispose();

注意到了吗?这里ConnectionPoolConfigurationbuilder的入参是一个ConnectionFactory,而r2dbc-mysql的Demo中使用到的MySqlConnectionFactory最终构建出来的就是一个ConnectionFactory
到此,2个组件的相互关系就已经连上了。

相关配置

由于配置说明都是英文的, 我大概挑选了几个关键的说一下自己的理解

配置项说明
initialSize启动时连接池大小,默认10
maxIdleTime最长空闲时间。如果配置为负数,则连接不会因为空闲等待而被释放。默认30分钟。注意:这个配置和backgroundEvictionInterval重复用途,这个后面说明
maxSize连接池最大大小,默认10
maxAcquireTime获取连接的最长时间。也就是说,sql请求过来后,会向连接池请求一个连接,当连接全忙或者需要发起新的连接的时候,请求会处于等待状态。这个值配置最长等待多久,默认没有等待超时时间,如果获取不到会一直等下去。建议配置一下,避免流被卡住
maxCreateConnectionTime创建连接的超时时间,默认不会超时
acquireRetry请求连接的重试次数,默认为1
name连接池名称
validationQuery探活SQL
registerJmx是否注册JMX
关于backgroundEvictionInterval和maxIdleTime

这两个配置根据文档说明,作用有重叠的地方。所以为了探究他俩的左右,我专门去找了一下源码。先说一下结论:实际效果都作用在同一个地方,配置任意一个即可,无需2个都配置。

首先可以看到源码中的用法,在ConnectionPool中的216行位置

Duration backgroundEvictionInterval = configuration.getBackgroundEvictionInterval();

if (!backgroundEvictionInterval.isZero()) {
    if (!backgroundEvictionInterval.isNegative()) {
        builder.evictInBackground(backgroundEvictionInterval);
    } else if (!configuration.getMaxIdleTime().isNegative()) {
        builder.evictInBackground(configuration.getMaxIdleTime());
    }
}

而在测试用例中,也能看到其中的区别。详见测试用例
测试用例

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
要在Spring WebFlux中使用R2DBC连接MySQL并集成Flyway,可以按照以下步骤进行操作: 1. 在pom.xml中添加所需的依赖项: ```xml <!-- Spring WebFlux --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- R2DBC MySQL 驱动 --> <dependency> <groupId>dev.miku</groupId> <artifactId>r2dbc-mysql</artifactId> <version>0.8.2.RELEASE</version> </dependency> <!-- Flyway --> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency> ``` 2. 在application.properties中配置R2DBC连接和Flyway: ```properties # R2DBC MySQL 连接配置 spring.r2dbc.url=r2dbc:mysql://localhost:3306/test spring.r2dbc.username=root spring.r2dbc.password=root # Flyway 配置 flyway.locations=classpath:db/migration flyway.clean-disabled=false flyway.baseline-on-migrate=true ``` 3. 创建数据库迁移脚本文件,存放在`src/main/resources/db/migration`目录下。例如,创建一个名为`V1__init.sql`的脚本文件,用于初始化数据库: ```sql CREATE TABLE `user` ( `id` INT PRIMARY KEY, `name` VARCHAR(50) NOT NULL, `age` INT NOT NULL ); ``` 4. 在Spring Boot应用程序中创建一个数据访问对象(DAO)来处理与数据库的交互。可以使用R2DBC提供的`DatabaseClient`或Spring Data R2DBC来简化数据库访问。 5. 启动应用程序,Flyway将自动执行数据库迁移脚本,初始化数据库。 注意:R2DBC是非阻塞的数据库访问方式,与传统的JDBC和Spring Data JPA不同,需要使用异步的方式进行操作。确保代码中的异步操作正确处理。 希望对你有所帮助!如有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术流奶爸奶爸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值