Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化

一、背景

线上程序连接mongos超时,mongo监控显示连接数已使用100%。

java程序报错信息:

org.mongodb.driver.connection: Closed connection [connectionId{localValue:1480}] to 192.168.10.16:3717 because there was a socket exception raised by this connection

org.springframework.data.mongodb.UncategorizedMongoDbException: Prematurely reached end of stream; nested exception is com.mongodb.MongoSocketReadException: Prematurely reached end of stream
	at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:138)
	at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2902)
	at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:2810)
	at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:2532)
	at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:2515)
	at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:876)

此时java应用程序的监控指标是,接口超时。

在这里插入图片描述

走过的弯路是,怀疑出现了慢查询,数据量剧增的同时没有索引。

所以,前期解决方向着重在优化Mongodb查询速度,增加索引。

但是,接口还是报错,超时;服务健康检测时,还是进入了不健康状态。

而进一步查看Mongodb数据库并没有很慢(超过500毫秒)的慢查询。

再查看Mongodb的内存、CPU、网络流量等指标本身也没有异常,唯独遗漏了连接数指标。

通过本文,希望读者也有同感,连接数指标很重要。

二、连接池配置

  • 最小连接数
  • 最大连接数
  • 连接的空闲时间
  • 连接的存活时间
  • 等待队列的长度
  • 等待可用的超时

参考链接:
mongo connection-string

因为不同语言的Mongo驱动实现不同,本文从java实现看一看其源码。

  • 阿里云数据库监控

在这里插入图片描述
在这里插入图片描述
从上图也可以看到,mongo数据库总共创建的连接数多达1189个,活跃的只有12个。

所以需要配置连接的空闲时间,及时释放连接,才不会导致有效请求无法连接mongodb。

而我们每个mongos能创建的连接数上限是2000,从监控信息可以看出,见下图:
在这里插入图片描述
当这里的连接使用率为100%时,程序后面想创建新的mongo连接,就会失败了。

既然知道这些指标重要,所以需要设置报警规则。

在这里插入图片描述

  • mongos配置及使用
    在这里插入图片描述
    购买的mongos,规格显示是最大3K,最后却只有2K。这是个大坑么?
    所以当我们的程序节点越来越多,只好购买多个mongos,截止目前,我们都已买了4个Mongos

在这里插入图片描述
在配置spring.data.mongodb.uri的值时,格式如下:

//指定连某个mongos
mongodb://{用户名}:{密码}@{域名信息}:3717/db_name

//配置多个mongos
mongodb://{用户名}:{密码}@{域名信息1}:3717,{域名信息2}:3717,{域名信息3}:3717,{域名信息4}:3717/db_name

三、源码spring.boot.autoconfigure

1、入口类MongoAutoConfiguration.java

见jar包spring.boot.autoconfigure-2.2.4.RELEASE.jar

在这里插入图片描述
主要代码:

    @Bean
	@ConditionalOnMissingBean(type = { "com.mongodb.MongoClient", "com.mongodb.client.MongoClient" })
	public MongoClient mongo(MongoProperties properties, ObjectProvider<MongoClientOptions> options,
			Environment environment) {
		return new MongoClientFactory(properties, environment).createMongoClient(options.getIfAvailable());
	}

使用MongoClientFactory工厂模式创建并实例化类MongoClient。

下一步看一看工厂类MongoClientFactory的主要实现。

2、工厂类MongoClientFactory.java

在这里插入图片描述
读取MongoProperties配置以及MongoClientOptions配置,前者是通过application.yaml配置,后者是通过uri追加参数的方式。

下面看一看这两个配置类里都有哪些配置项,着重分析是否有针对连接池相关的。

3、MongoProperties.java

在这里插入图片描述

这里就不一一贴出来,发现并没有连接池相关的配置。

那么进一步查看com.mongodb.MongoClientOptions.java类有哪些属性。

4、MongoClientOptions.java

在这里插入图片描述
可以看到,连接池配置相关参数,是在这个类中。

那么,他们是在什么哪里赋值的呢?

它们跟Mongodb驱动有关,让我们跳到jar包momgo-java-driver-3.11.2.jar

四、源码momgo-java-driver

数据库驱动使用jdni技术,避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。

在这里插入图片描述
找到类com.mongodb.client.jndi.MongoClientFactory.java
在这里插入图片描述

1、工厂类MongoClientFactory.java

package com.mongodb.client.jndi;

import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoException;
import com.mongodb.diagnostics.logging.Logger;
import com.mongodb.diagnostics.logging.Loggers;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

public class MongoClientFactory implements ObjectFactory {
    private static final Logger LOGGER = Loggers.getLogger("client.jndi");
    private static final String CONNECTION_STRING = "connectionString";

    public MongoClientFactory() {
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        String connectionString = null;
        if (environment.get("connectionString") instanceof String) {
            connectionString = (String)environment.get("connectionString");
        }

        if (connectionString == null || connectionString.isEmpty()) {
            LOGGER.debug(String.format("No '%s' property in environment.  Casting 'obj' to java.naming.Reference to look for a javax.naming.RefAddr with type equal to '%s'", "connectionString", "connectionString"));
            if (obj instanceof Reference) {
                Enumeration props = ((Reference)obj).getAll();

                while(props.hasMoreElements()) {
                    RefAddr addr = (RefAddr)props.nextElement();
                    if (addr != null && "connectionString".equals(addr.getType()) && addr.getContent() instanceof String) {
                        connectionString = (String)addr.getContent();
                        break;
                    }
                }
            }
        }

        if (connectionString != null && !connectionString.isEmpty()) {
            MongoClientURI uri = new MongoClientURI(connectionString);
            return new MongoClient(uri);
        } else {
            throw new MongoException(String.format("Could not locate '%s' in either environment or obj", "connectionString"));
        }
    }
}

这里引入了一个关键类MongoClientURI.java

2、MongoClientURI.java

它有一个属性:ConnectionString对象,也就是说,MongoClientURI是用来解析数据库连接参数。

在这里插入图片描述
见关键代码: new ConnectionString(uri)

3、连接参数类com.mongodb.ConnectionString.java

该类的代码行数比较多,首要看的是其构造函数。(写出了从mongo.uri中解析数据库连接池参数的全过程)

spring:
  data:
    mongodb:
      uri: mongodb://192.168.10.16:3717/db_name?maxPoolSize=50
  • 构造函数

主要围绕着解析数据库连接相关参数来说明,其他的可以自行看源码。

在这里插入图片描述

  • 解析配置项 private Map<String, List> parseOptions(String optionsPart)

在这里插入图片描述

  • 赋值给当前类ConnectionString的属性
   private void translateOptions(Map<String, List<String>> optionsMap) {
        boolean tlsInsecureSet = false;
        boolean tlsAllowInvalidHostnamesSet = false;
        Iterator var4 = GENERAL_OPTIONS_KEYS.iterator();

        while(var4.hasNext()) {
            String key = (String)var4.next();
            String value = this.getLastValue(optionsMap, key);
            if (value != null) {
                if (key.equals("maxpoolsize")) {
                    this.maxConnectionPoolSize = this.parseInteger(value, "maxpoolsize");
                } else if (key.equals("minpoolsize")) {
                    this.minConnectionPoolSize = this.parseInteger(value, "minpoolsize");
                } else if (key.equals("maxidletimems")) {
                    this.maxConnectionIdleTime = this.parseInteger(value, "maxidletimems");
                } else if (key.equals("maxlifetimems")) {
                    this.maxConnectionLifeTime = this.parseInteger(value, "maxlifetimems");
                } else if (key.equals("waitqueuemultiple")) {
                    this.threadsAllowedToBlockForConnectionMultiplier = this.parseInteger(value, "waitqueuemultiple");
                } else if (key.equals("waitqueuetimeoutms")) {
                    this.maxWaitTime = this.parseInteger(value, "waitqueuetimeoutms");
                } else if (key.equals("connecttimeoutms")) {
                    this.connectTimeout = this.parseInteger(value, "connecttimeoutms");
                } else if (key.equals("sockettimeoutms")) {
                    this.socketTimeout = this.parseInteger(value, "sockettimeoutms");
                } else if (key.equals("tlsallowinvalidhostnames")) {
                    this.sslInvalidHostnameAllowed = this.parseBoolean(value, "tlsAllowInvalidHostnames");
                    tlsAllowInvalidHostnamesSet = true;
                } else if (key.equals("sslinvalidhostnameallowed")) {
                    this.sslInvalidHostnameAllowed = this.parseBoolean(value, "sslinvalidhostnameallowed");
                    tlsAllowInvalidHostnamesSet = true;
                } else if (key.equals("tlsinsecure")) {
                    this.sslInvalidHostnameAllowed = this.parseBoolean(value, "tlsinsecure");
                    tlsInsecureSet = true;
                } else if (key.equals("ssl")) {
                    this.initializeSslEnabled("ssl", value);
                } else if (key.equals("tls")) {
                    this.initializeSslEnabled("tls", value);
                } else if (key.equals("streamtype")) {
                    this.streamType = value;
                    LOGGER.warn("The streamType query parameter is deprecated and support for it will be removed in the next major release.");
                } else if (key.equals("replicaset")) {
                    this.requiredReplicaSetName = value;
                } else if (key.equals("readconcernlevel")) {
                    this.readConcern = new ReadConcern(ReadConcernLevel.fromString(value));
                } else if (key.equals("serverselectiontimeoutms")) {
                    this.serverSelectionTimeout = this.parseInteger(value, "serverselectiontimeoutms");
                } else if (key.equals("localthresholdms")) {
                    this.localThreshold = this.parseInteger(value, "localthresholdms");
                } else if (key.equals("heartbeatfrequencyms")) {
                    this.heartbeatFrequency = this.parseInteger(value, "heartbeatfrequencyms");
                } else if (key.equals("appname")) {
                    this.applicationName = value;
                } else if (key.equals("retrywrites")) {
                    this.retryWrites = this.parseBoolean(value, "retrywrites");
                } else if (key.equals("retryreads")) {
                    this.retryReads = this.parseBoolean(value, "retryreads");
                }
            }
        }

        if (tlsInsecureSet && tlsAllowInvalidHostnamesSet) {
            throw new IllegalArgumentException("tlsAllowInvalidHostnames or sslInvalidHostnameAllowed set along with tlsInsecure is not allowed");
        } else {
            this.writeConcern = this.createWriteConcern(optionsMap);
            this.readPreference = this.createReadPreference(optionsMap);
            this.compressorList = this.createCompressors(optionsMap);
        }
    }

这个方法揭示了mongodb驱动所支持的全部参数,而且它读取的key字符都是小写字母。

而我们在实际配置mongodb.uri连接参数的时候,一般都会采用驼峰格式。

这是因为在方法parseOptions()解析的时候,强制把所有的key都转换为小写了。
在这里插入图片描述

五、参数的默认值

至此,我们已知道了mongodb连接支持哪些参数,但是,当缺省未配置时,它们的默认值分别是多少呢?

这就得看另一个jar包mongodb-driver-core-3.11.2.jar, package为com.mongodb.connection下,有一个类ConnectionPoolSettings采用builder构造模式,可以看到,在构建对象的时候有进行默认赋值。

在这里插入图片描述
所以,如果你没有对属性maxConnectionIdleTimeMS进行设置,默认是0,不会释放空闲连接。

前面4个属性都可以不管,属性maxConnectionIdleTimeMS是一定要设置的。

否则不活跃的连接都一直占据着mongo的连接,随着服务节点增多,就会影响到所有依赖Mongo集群的服务。

体现出来的报错就是连接超时,你还以为是服务的qps过高导致服务挂了呢。

而mongodb的慢查询又没有,服务的qps很低的时候,仍旧报连接mongo超时错误。(真的是要怀疑人生)

使出重启大法,服务也无法健康。

如果你想对节点扩容,那就离曙光越来越远了。

文末,我这里给出Mongo连接池相关的参数:

spring:
  data:
    mongodb:
      uri: mongodb://192.168.10.16:3717/db_name?maxPoolSize=50&minPoolSize=10&maxIdleTimeMS=60000

六、总结

本文的内容比较长,既描述了阿里云对mongodb数据库的监控(着重是连接数指标),以及Mongos的使用及购买的坑,也从Java语言的 Mongo驱动程序作为切入点,分析并总结了支持哪些数据库连接池的配置项。

本案例是基于生产实际中遇到的一个棘手问题,希望可以帮助到你。

通过本文,让我们对连接数这个指标有更深的体会,它是一个很冷的指标,却非常致命。

说它致命,是说我们在遇到程序报错的时候,极容易陷入平常思维,以为是有慢查询,或者程序QPS过高导致程序挂了。

当你想去扩容程序的节点数,或者创建数据库索引的时候,服务不健康的问题并不能得到丝毫解决。

当没有找到问题的根本时,就像一个病人感冒去看医生,结果CT和心电图等一大推检查,只会起到拖延的作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/922365.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程 引言 微信小程序作为一种新兴的轻量级应用,凭借其便捷性和丰富的功能受到了广泛的欢迎。在开发小程序的过程中,合理配置全局属性是提升用户体验的关键。本文将深入探讨小程序的全局配置中的window选项,重点介绍导…

YOLOv11融合[ECCV 2018]RCAN中的RCAB模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《Image Super-Resolution Using Very Deep Residual Channel Attention Networks》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/abs/1807…

linux ubuntu的脚本知

目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 N、其它可以参考的网址 脚本功能强大&#xff0c;用起来也…

前端:JavaScript (学习笔记)【2】

目录 一&#xff0c;数组的使用 1&#xff0c;数组的创建 [ ] 2&#xff0c;数组的元素和长度 3&#xff0c;数组的遍历方式 4&#xff0c;数组的常用方法 二&#xff0c;JavaScript中的对象 1&#xff0c;常用对象 &#xff08;1&#xff09;String和java中的Stri…

【Git】工作区、暂存区和版本库

目录 一、基本概念&#xff1a; 关系图&#xff1a; 1. 工作区&#xff08;Working Directory&#xff09; $ 1.1 工作区功能 $ 1.2 工作区特点 2. 暂存区&#xff08;Staging Area&#xff09; $ 2.1 暂存区功能 $ 2.2 暂存区特点 $ 2.3 常用命令 3. 版本库&#xff08…

【Linux | 计网】TCP协议详解:从定义到连接管理机制

目录 1.TCP协议的定义&#xff1a; 2.TCP 协议段格式 3.TCP两种通信方式 4.确认应答(ACK)机制 解决“后发先至”问题 5.超时重传机制 那么, 超时的时间如何确定? 6.连接管理机制&#xff1a; 6.1.三次握手&#xff1a; 为什么需要3次握手&#xff0c;一次两次不行吗…

Springboot系列之:创建Springboot项目,Springboot整合MyBatis-plus

Springboot系列之&#xff1a;创建Springboot项目&#xff0c;Springboot整合MyBatis-plus 一、快速创建Spring boot项目二、项目完整目录三、pom.xml四、application.yaml五、实体类六、mapper七、IService接口八、Service实现类九、配置类十、枚举十一、增删改查测试类十二、…

java基础面试题笔记(基础篇)

网上始终找不到令自己满意的面试题&#xff0c;所以我打算自己整理面试题&#xff0c;从简单的到难的&#xff0c;尽量简单准确描述回答降低大家理解和背的难度&#xff0c;有错误或者有更好的回答请在评论回复我&#xff0c;感谢大家。 什么是java&#xff1f; 回答&#xff…

编译 LLVM 源码,使用 Clion 调试 clang

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 1. LLVM 简介 LLVM 是一个开源的编译器基础架构&#xff0c;最初由 Chris Lattner 于 2000 年在伊利诺伊大学开发&#xff0c;后来成为一个广泛应用于编译器和…

[代码随想录打卡Day22] 理论基础 77. 组合 216.组合总和III 17.电话号码的字母组合

理论基础 有递归就有回溯。回溯搜索是一种纯暴力搜索算法。我们一层一层递归到最底层收获结果&#xff0c;比如下面我们最后一层1操作之后&#xff0c;我们只有撤销这个操作回退到上一个节点才能遍历该层的其他节点&#xff0c;这个回退撤销操作就是回溯。 回溯法&#xff0…

大模型工程化部署:使用FastChat部署基于OpenAI API兼容大模型服务

FastChat是加州大学伯克利分校LM-SYS发布的一个用于训练、服务和评估基于大型语言模型的聊天机器人的开放平台。 项目地址&#xff1a;https://github.com/lm-sys/FastChat.git 其核心功能包括&#xff1a; 最先进 LLM 模型的权重、训练代码和评估代码。 带有 WebUI 和与 Op…

102.【C语言】数据结构之用堆对数组排序

0.前置知识 向上调整: 向下调整: 1.对一个无序的数组排升序和降序 排升序问题 建大根堆还是小根堆? 错误想法 由小根堆的定义:树中所有的父节点的值都小于或等于孩子节点的值,这样排出来的数组时升序的,建小根堆调用向上调整函数即可(把画圈的地方改成<即可) arr未…

彻底理解微服务的作用和解决方案

一.微服务有什么好处&#xff1f; 微服务优点很多&#xff0c;但是我们通常说一个东西好肯定会跟另一个东西比较&#xff0c;通常说微服务好会和单体项目进行比较&#xff0c;通常情况下微服务都是从单体项目拆分而来的&#xff0c;但是对于有些大型公司&#xff0c;不差钱&…

Harbor安装、HTTPS配置、修改端口后不可访问?

Harbor安装、HTTPS配置、修改端口后不可访问&#xff1f; 大家好&#xff0c;我是秋意零。今天分享Harbor相关内容&#xff0c;安装部分可完全参考官方文档&#xff0c;写的也比较详细。 安装Harbor 官方文档&#xff1a;https://goharbor.io/docs/2.12.0/install-config/ …

表单校验规则

这里简单记录下vue使用表单时候&#xff0c;给表单添加校验规则&#xff0c;直接上代码 <script setup>import { ref } from vue// 定义表单对象const form ref({account: ,password: ,agree: true})// 定义表单验证规则const rules {account: [{required: true, mess…

spf算法、三类LSA、区间防环路机制/规则、虚连接

1.构建spf树&#xff1a; 路由器将自己作为最短路经树的树根根据Router-LSA和Network-LSA中的拓扑信息,依次将Cost值最小的路由器添加到SPF树中。路由器以Router ID或者DR标识。广播网络中DR和其所连接路由器的Cost值为0。SPF树中只有单向的最短路径,保证了OSPF区域内路由计管不…

电子电气架构 -- ASIL D安全实现策略

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

【Unity ShaderGraph实现流体效果之Function入门】

Unity ShaderGraph实现流体效果之Node入门&#xff08;一&#xff09; 前言Shader Graph NodePosition NodeSplit NodeSubtract NodeBranch Node 总结 前言 Unity 提供的Shader Graph在很大程度上简化了开发者对于编写Shader的工作&#xff0c;只需要拖拽即可完成一个视觉效果…

uniop触摸屏维修eTOP40系列ETOP40-0050

在现代化的工业与商业环境中&#xff0c;触摸屏设备已成为不可或缺的人机交互界面。UNIOP&#xff0c;作为一个知名的触摸屏品牌&#xff0c;以其高性能、稳定性和用户友好性&#xff0c;广泛应用于各种自动化控制系统、自助服务终端以及高端展示系统中。然而&#xff0c;即便如…

SpringMVC——简介及入门

SpringMVC简介 看到SpringMVC这个名字&#xff0c;我们会发现其中包含Spring&#xff0c;那么SpringMVC和Spring之间有怎样的关系呢&#xff1f; SpringMVC隶属于Spring&#xff0c;是Spring技术中的一部分。 那么SpringMVC是用来做什么的呢&#xff1f; 回想web阶段&#x…