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

news2024/11/25 21:20:04

一、背景

线上程序连接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和心电图等一大推检查,只会起到拖延的作用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive离线数仓结构分析

Hive离线数仓结构 首先&#xff0c;在数据源部分&#xff0c;包括源业务库、用户日志、爬虫数据和系统日志&#xff0c;这些都是数据的源头。这些数据通过Sqoop、DataX或 Flume 工具进行提取和导入操作。这些工具负责将不同来源的数据传输到基于 Hive 的离线数据仓库中。 在离线…

MySQL1——基本原理和基础操作

文章目录 Mysql数据库1——基本原理和基础操作1. 基本概念2. Mysql体系结构2.1 连接层2.2 服务层2.3 存储引擎层 3. 三级范式与反范式4. 完整性约束4.1 实体完整性约束4.2 参照完整性约束 5. CRUDDDLDMLDCLDQL 6. 高级查询基础查询条件查询分页查询查询结果排序分组聚合查询联表…

【Ubuntu24.04】服务部署(虚拟机)

目录 0 背景1 安装虚拟机1.1 下载虚拟机软件1.2 安装虚拟机软件1.2 安装虚拟电脑 2 配置虚拟机2.1 配置虚拟机网络及运行初始化脚本2.2 配置服务运行环境2.2.1 安装并配置JDK172.2.2 安装并配置MySQL8.42.2.3 安装并配置Redis 3 部署服务4 总结 0 背景 你的服务部署在了你的计算…

Redis 常用数据类型插入性能对比:循环插入 vs. 批量插入

Redis 是一款高性能的键值数据库&#xff0c;其支持多种数据类型&#xff08;String、Hash、List、Set、ZSet、Geo&#xff09;。在开发中&#xff0c;经常会遇到需要插入大量数据的场景。如果逐条插入&#xff0c;性能会显得较低&#xff0c;而采用 Pipeline 批量插入 能大幅提…

uniapp页面样式和布局和nvue教程详解

uniapp页面样式和布局和nvue教程 尺寸单位 uni-app 支持的通用 css 单位包括 px、rpx px 即屏幕像素。rpx 即响应式px&#xff0c;一种根据屏幕宽度自适应的动态单位。以750宽的屏幕为基准&#xff0c;750rpx恰好为屏幕宽度。屏幕变宽&#xff0c;rpx 实际显示效果会等比放大…

Spring AI 框架使用的核心概念

一、模型&#xff08;Model&#xff09; AI 模型是旨在处理和生成信息的算法&#xff0c;通常模仿人类的认知功能。通过从大型数据集中学习模式和见解&#xff0c;这些模型可以做出预测、文本、图像或其他输出&#xff0c;从而增强各个行业的各种应用。 AI 模型有很多种&…

(Keil)MDK-ARM各种优化选项详细说明、实际应用及拓展内容

参考 MDK-ARM各种优化选项详细说明、实际应用及拓展内容 本文围绕MDK-ARM优化选项,以及相关拓展知识(微库、实际应用、调试)进行讲述,希望对你今后开发项目有所帮助。 1 总述 我们所指的优化,主要两方面: 1.代码大小(Size) 2.代码性能(运行时间) 在MDK-ARM中,优…

python成绩分级 2024年6月python二级真题 青少年编程电子学会编程等级考试python二级真题解析

目录 python成绩分级 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python成绩分级 2024年6月 python编程等级考试二级编程题 一、题目要求 …

苹果Siri将搭载大型语言模型,近屿智能抢占AIGC大模型人才培养高地

据媒体报道&#xff0c;苹果公司正在研发一款全新升级、更加智能且对话能力显著提升的Siri&#xff0c;意在超越OpenAI的ChatGPT及其他语音服务。 报道指出&#xff0c;新一代Siri将搭载更为先进的大型语言模型&#xff08;LLM&#xff09;&#xff0c;苹果期望其能够进行连续…

Ubuntu,openEuler,MySql安装

文章目录 Ubuntu什么是Ubuntu概述Ubuntu版本简介桌面版服务器版 部署系统新建虚拟机安装系统部署后的设置设置root密码关闭防火墙启用允许root进行ssh安装所需软件制作快照 网络配置Netplan概述配置详解配置文件DHCP静态IP设置 软件安装方法apt安装软件作用常用命令配置apt源 d…

【C++动态规划】1411. 给 N x 3 网格图涂色的方案数|1844

本文涉及知识点 C动态规划 LeetCode1411. 给 N x 3 网格图涂色的方案数 提示 你有一个 n x 3 的网格图 grid &#xff0c;你需要用 红&#xff0c;黄&#xff0c;绿 三种颜色之一给每一个格子上色&#xff0c;且确保相邻格子颜色不同&#xff08;也就是有相同水平边或者垂直…

小鹏汽车智慧材料数据库系统项目总成数据同步

1、定时任务处理 2、提供了接口 小鹏方面提供的推送的数据表结构&#xff1a; 这几个表总数为100多万&#xff0c;经过条件筛选过滤后大概2万多条数据 小鹏的人给的示例图&#xff1a; 界面&#xff1a; SQL: -- 查询车型 select bmm.md_material_id, bmm.material_num, bm…

【解决方案】VITE 忽略指定路径的资源

前言 问题起因是因为项目需要引入服务器端的网络图片 而在编写配置时发现&#xff0c;Vite并不支持排除指定前缀的资源 唯一可以排外的只有 Rollup 的 external 选项可以排除外部依赖&#xff0c;但他只能排除外部依赖&#xff0c;不支持指定路径资源或指定前缀的资源&#…

自然语言处理: RAG优化之Embedding模型选型重要依据:mteb/leaderboard榜

本人项目地址大全&#xff1a;Victor94-king/NLP__ManVictor: CSDN of ManVictor git地址&#xff1a;https://github.com/opendatalab/MinerU 写在前面: 笔者更新不易&#xff0c;希望走过路过点个关注和赞&#xff0c;笔芯!!! 写在前面: 笔者更新不易&#xff0c;希望走过路…

疑难Tips:NextCloud域名访问登录时卡住,显示违反内容安全策略

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] 1使用域名访问Nextcloud用户登录时卡住,显示违反内容安全策略 我使用官方Docker镜像来部署NextCloud 28.0.5,并通过Openresty反向代理Nextcloud,但是在安装后无法稳定工作,每次登录后,页面会卡死在登录界面,无法…

SpringBoot 集成 html2Pdf

一、概述&#xff1a; 1. springboot如何生成pdf&#xff0c;接口可以预览可以下载 2. vue下载通过bold如何下载 3. 一些细节&#xff1a;页脚、页眉、水印、每一页得样式添加 二、直接上代码【主要是一个记录下次开发更快】 模板位置 1. 导入pom包 <dependency><g…

java实现小程序接口返回Base64图片

文章目录 引言I java 接口返回Base64图片接口设计获取验证码图片-base64字符串获取验证码图片-二进制流arraybufferII 小程序端代码过期代码: 显示文件流图片(arraybuffer)知识扩展:微信小程序下载后端返回的文件流引言 场景: 图形验证码 背景: 接口返回arraybuffer的格式…

Pytorch自定义算子反向传播

文章目录 自定义一个线性函数算子如何实现反向传播 有关 自定义算子的实现前面已经提到&#xff0c;可以参考。本文讲述自定义算子如何前向推理反向传播进行模型训练。 自定义一个线性函数算子 线性函数 Y X W T B Y XW^T B YXWTB 定义输入M 个X变量&#xff0c;输出N个…

ajax (一)

什么是 AJAX [ˈeɪdʒks] &#xff1f; 概念&#xff1a;AJAX是浏览器与服务器进行 数据通信 的技术&#xff0c;动态数据交互 怎么用AJAX? 1. 先使用 axios [k‘sio ʊ s] 库&#xff0c; 与服务器进行 数据通信 ⚫ 基于 XMLHttpRequest 封装、代码简单、月下载量在 1…

URL在线编码解码- 加菲工具

URL在线编码解码 打开网站 加菲工具 选择“URL编码解码” 输入需要编码/解码的内容&#xff0c;点击“编码”/“解码”按钮 编码&#xff1a; 解码&#xff1a; 复制已经编码/解码后的内容。