MongoDB7.0--SpringBoot配置mongodb连接池

摘要

  • 本文介绍如何使用SpringBoot配置MongoDB7.0的连接池

  • SpringBoot版本3.2.4,MongoDB版本7.0.6

  • 关于springboot与mongodb的集成可以参考:MongoDB7.0--SpringBoot单集合操作,本文只讨论连接池相关内容。

spring-boot 的 MongoDB 连接池

  • 当我们在springboot的配置文件中配置mongodb的相关属性时,并没有发现和连接池相关的属性,查看MongoProperties中的属性也可以证实这一点,这就很奇怪,按理说springboot应该不会这么弱鸡。

  • 于是我打算看看springboot的源码,看看springboot为我们自动创建的MongoClient到底有没有连接池的配置。

  • 我们就从mongodb的自动配置类MongoAutoConfiguration开始,该类中定义了PropertiesMongoConnectionDetailsMongoClient

1
2
3
4
5
6
7
8
9
10
11
12
//这个类主要用于ConnectionString对象的封装,后面会讲到
@Bean
@ConditionalOnMissingBean({MongoConnectionDetails.class})
PropertiesMongoConnectionDetails mongoConnectionDetails(MongoProperties properties) {
return new PropertiesMongoConnectionDetails(properties);
}

@Bean
@ConditionalOnMissingBean({MongoClient.class})
public MongoClient mongo(ObjectProvider<MongoClientSettingsBuilderCustomizer> builderCustomizers, MongoClientSettings settings) {
return (MongoClient)(new MongoClientFactory(builderCustomizers.orderedStream().toList())).createMongoClient(settings);
}
  • 可以看到,创建MongoClient的bean时,依赖两个参数,一个是ObjectProvider<MongoClientSettingsBuilderCustomizer>,一个是MongoClientSettings,前者会将spring上下文中所有类型为MongoClientSettingsBuilderCustomizer的bean封装到该对象中(springboot默认只提供了一个StandardMongoClientSettingsBuilderCustomizer)

  • MongoAutoConfiguration缺省为我们提供了MongoClientSettingsStandardMongoClientSettingsBuilderCustomizer,这两个类都很重要,连接池参数就和它们有关

1
2
3
4
5
6
7
8
9
@Bean
MongoClientSettings mongoClientSettings() {
return MongoClientSettings.builder().build();
}
// 需要的参数connectionDetails刚好是上面的PropertiesMongoConnectionDetails
@Bean
StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties, MongoConnectionDetails connectionDetails, ObjectProvider<SslBundles> sslBundles) {
return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(), properties.getUuidRepresentation(), properties.getSsl(), (SslBundles)sslBundles.getIfAvailable());
}
  • 我们先看MongoClientSettings,其中就定义一个连接池的配置对象ConnectionPoolSettings,而ConnectionPoolSettings其内部就定义了连接池的配置属性,MongoClientSettings通过ConnectionPoolSettings的构造器创建出了ConnectionPoolSettings对象,其缺省值如下:

1
2
3
4
5
6
private int maxSize = 100;
private int minSize;
private long maxWaitTimeMS = 1000 * 60 * 2;
private long maxConnectionLifeTimeMS;
private long maxConnectionIdleTimeMS;
private int maxConnecting = 2;
  • 我们看另一个和创建MongoClient相关的bean,这个StandardMongoClientSettingsBuilderCustomizer,创建这个bean时需要一个MongoConnectionDetails对象,并调用其getConnectionString方法,这个对象MongoAutoConfiguration也为我们提供了(上面可以看到)

  • 查看PropertiesMongoConnectionDetails的源码,发现connectionDetails.getConnectionString()的功能就是将MongoProperties的属性封装为uri并传递给ConnectionString的构造器创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public ConnectionString getConnectionString() {
if (this.properties.getUri() != null) {
return new ConnectionString(this.properties.getUri());
} else {
StringBuilder builder = new StringBuilder("mongodb://");
if (this.properties.getUsername() != null) {
builder.append(this.encode(this.properties.getUsername()));
builder.append(":");
if (this.properties.getPassword() != null) {
builder.append(this.encode(this.properties.getPassword()));
}

builder.append("@");
}

builder.append(this.properties.getHost() != null ? this.properties.getHost() : "localhost");
if (this.properties.getPort() != null) {
builder.append(":");
builder.append(this.properties.getPort());
}

if (this.properties.getAdditionalHosts() != null) {
builder.append(",");
builder.append(String.join(",", this.properties.getAdditionalHosts()));
}

builder.append("/");
builder.append(this.properties.getMongoClientDatabase());
List<String> options = this.getOptions();
if (!options.isEmpty()) {
builder.append("?");
builder.append(String.join("&", options));
}
// 将MongoProperties转换为uri格式后调用构造器创建ConnectionString
return new ConnectionString(builder.toString());
}
}
  • 这个ConnectionString对象的初始化过程就很有意思了,其会对这个uri进行解析,解析过程中会调用parseOptions(connectionStringQueryParameters)方法,其会将uri中的参数转换为Map,之后调用translateOptions(combinedOptionsMaps)方法遍历这个map,而在遍历的过程中就会完成对ConnectionString对象的连接池相关属性的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void translateOptions(final Map<String, List<String>> optionsMap) {
boolean tlsInsecureSet = false;
boolean tlsAllowInvalidHostnamesSet = false;

for (final String key : GENERAL_OPTIONS_KEYS) {
String value = getLastValue(optionsMap, key);
if (value == null) {
continue;
}
switch (key) {
// 连接池相关属性,注意这里的属性名称,后面我们配置连接池时会用到
case "maxpoolsize":
maxConnectionPoolSize = parseInteger(value, "maxpoolsize");
break;
case "minpoolsize":
minConnectionPoolSize = parseInteger(value, "minpoolsize");
break;
case "maxidletimems":
maxConnectionIdleTime = parseInteger(value, "maxidletimems");
break;
case "maxlifetimems":
maxConnectionLifeTime = parseInteger(value, "maxlifetimems");
break;
case "maxconnecting":
maxConnecting = parseInteger(value, "maxConnecting");
break;
case "waitqueuetimeoutms":
maxWaitTime = parseInteger(value, "waitqueuetimeoutms");
break;
case "connecttimeoutms":
connectTimeout = parseInteger(value, "connecttimeoutms");
break;
……………………………………………………………………………………
  • 其实此时我们就已经知道应该如何配置连接池参数了,就是将连接池相关的参数追加到uri的参数部分,但是前面我们提到负责连接池的类是ConnectionPoolSettings,那么ConnectionString对象中的连接池属性是如何被传递给ConnectionPoolSettings的呢,我们接着往下看。

  • 参数准备好后就开始创建MongoClient了,其会调用MongoClientFactorycreateMongoClient方法,其会对MongoClientSettings对象做进一步的封装,并最终基于封装后的MongoClientSettings对象创建出MongoClient对象

1
2
3
4
5
6
7
8
9
10
public T createMongoClient(MongoClientSettings settings) {
Builder targetSettings = MongoClientSettings.builder(settings);
customize(targetSettings);
return this.clientCreator.apply(targetSettings.build(), driverInformation());
}
private void customize(Builder builder) {
for (MongoClientSettingsBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
}
  • 而在进一步封装MongoClientSettings对象时,会调用系统中所有被spring管理的MongoClientSettingsBuilderCustomizer,实际上也就是StandardMongoClientSettingsBuilderCustomizercustomize方法,其又会调用MongoClientSettings.builder()applyConnectionSettings方法,而在该方法中调用connectionPoolSettingsBuilder.applyConnectionString(connectionString);就完成了将ConnectionString对象中的连接池属性传递给ConnectionPoolSettings对象的过程,当然,如果属性没有获取到就会使用默认值,所以我们可以基于需要配置相关的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public Builder applyConnectionString(final ConnectionString connectionString) {
Integer maxConnectionPoolSize = connectionString.getMaxConnectionPoolSize();
if (maxConnectionPoolSize != null) {
maxSize(maxConnectionPoolSize);
}

Integer minConnectionPoolSize = connectionString.getMinConnectionPoolSize();
if (minConnectionPoolSize != null) {
minSize(minConnectionPoolSize);
}

Integer maxWaitTime = connectionString.getMaxWaitTime();
if (maxWaitTime != null) {
maxWaitTime(maxWaitTime, MILLISECONDS);
}

Integer maxConnectionIdleTime = connectionString.getMaxConnectionIdleTime();
if (maxConnectionIdleTime != null) {
maxConnectionIdleTime(maxConnectionIdleTime, MILLISECONDS);
}

Integer maxConnectionLifeTime = connectionString.getMaxConnectionLifeTime();
if (maxConnectionLifeTime != null) {
maxConnectionLifeTime(maxConnectionLifeTime, MILLISECONDS);
}

Integer maxConnecting = connectionString.getMaxConnecting();
if (maxConnecting != null) {
maxConnecting(maxConnecting);
}

return this;
}
  • 大体过程就是这样,很绕,简单点说就是springboot已经为我们提供了mongodb的连接池,我们也可以基于需要,在配置mongodb-uri时,增加相关的连接池参数,springboot就会按照我们配置的参数为我们创建连接池,并自动管理连接池。

1
2
3
4
5
spring:
data:
mongodb:
uri: mongodb://user:password@127.0.0.1:27040,127.0.0.1:27041,127.0.0.1:27042/mytest?authSource=admin&readPreference=primaryPreferred&maxpoolsize=100&minpoolsize=10&maxidletimems=0&maxlifetimems=0&maxconnecting=5&waitqueuetimeoutms=60000

  • 参数说明

1
2
3
4
5
6
maxpoolsize: 100 # 池中允许的最大连接数。一旦池耗尽,任何需要连接的操作都将在阻塞队列中等待直到获取到连接, 默认: 100
minpoolsize: 10 # 池中允许的最小连接数。这些连接在空闲时将保留在池中,并且池将确保它至少包含这个最小数量 默认: 0
maxidletimems: 0 # 池连接的最大空闲时间。零值表示对空闲时间没有限制。超过其空闲时间的池连接将被关闭并在必要时由新连接替换。单位为毫秒
maxlifetimems: 0 # 池连接可以存活的最长时间。零值表示寿命没有限制。超过其生命周期的池连接将被关闭并在必要时由新连接替换。单位为毫秒
maxconnecting: 5 # 同一时刻允许的最大并行连接数。默认值是: 2。
waitqueuetimeoutms: 60000 # 阻塞队列中请求的最大等待时间,超过这个时间的请求将被拒绝。单位为毫秒,默认为: 120s

如何查看连接池是否生效了?

  • 很简单,我们只需要注入MongoClient并查看其settings属性,就可以看到连接池的配置信息了`

1
2
3
4
5
6
7
@Autowired
MongoClient mongoClient;

// 打印连接池配置信息
System.out.println(((MongoClientImpl) mongoClient).getSettings().getConnectionPoolSettings());
// 输出结果
ConnectionPoolSettings{maxSize=100, minSize=10, maxWaitTimeMS=60000, maxConnectionLifeTimeMS=0, maxConnectionIdleTimeMS=0, maintenanceInitialDelayMS=0, maintenanceFrequencyMS=60000, connectionPoolListeners=[], maxConnecting=5}

后记

  • springboot为什么没有将mongodb的连接池属性也封装到MongoProperties中呢,也许是因为和mongodb相关的链接参数太多了吧,而且我们在配置mongodb时,一般都会使用mongodb-uri的方式,所以springboot就直接将连接池参数放到mongodb-uri中,这样配置起来就比较方便了。

  • 另外我们发现,在MongoClientSettings中除了连接池的配置,还有很多配置项也是基于mongodb-uri的,我们可以基于需要进行配置,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private final ReadPreference readPreference;
private final WriteConcern writeConcern;
private final boolean retryWrites;
private final boolean retryReads;
private final ReadConcern readConcern;
private final MongoCredential credential;
private final TransportSettings transportSettings;
private final LoggerSettings loggerSettings;
private final ClusterSettings clusterSettings;
private final SocketSettings socketSettings;
private final SocketSettings heartbeatSocketSettings;
private final ConnectionPoolSettings connectionPoolSettings;
private final ServerSettings serverSettings;
private final SslSettings sslSettings;