当前位置: 首页 > news >正文

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 connectionorg.springframework.data.mongodb.UncategorizedMongoDbException: Prematurely reached end of stream; nested exception is com.mongodb.MongoSocketReadException: Prematurely reached end of streamat 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和心电图等一大推检查,只会起到拖延的作用。

相关文章:

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

一、背景 线上程序连接mongos超时&#xff0c;mongo监控显示连接数已使用100%。 java程序报错信息&#xff1a; org.mongodb.driver.connection: Closed connection [connectionId{localValue:1480}] to 192.168.10.16:3717 because there was a socket exception raised by…...

使用ufw配置防火墙,允许特定范围IP访问

文章目录 1. 安装 UFW&#xff08;如果尚未安装&#xff09;2. 允许特定 IP 地址访问 22 端口3. 允许特定子网访问 22 端口4. 启用 UFW5. 检查 UFW 状态6. 重新加载 UFW&#xff08;如果需要&#xff09;7. 删除规则&#xff08;如果需要&#xff09; 在ubuntu上使用 ufw&#…...

实现 UniApp 右上角按钮“扫一扫”功能实战教学

实现 UniApp 右上角按钮“扫一扫”功能实战教学 需求 点击右上角扫一扫按钮(onNavigationBarButtonTap监听)&#xff0c;打开扫一扫页面(uni.scanCode) 扫描后&#xff0c;以网页的形式打开扫描内容(web-view组件)&#xff0c;限制只能浏览带有执行域名的网站&#xff0c;例如…...

【2024亚太杯亚太赛APMCM C题】数学建模竞赛|宠物行业及相关产业的发展分析与策略|建模过程+完整代码论文全解全析

第一个问题是&#xff1a;请基于附件 1 中的数据以及你的团队收集的额外数据&#xff0c;分析过去五年中国宠物行业按宠物类型的发展情况。并分析中国宠物行业发展的因素&#xff0c;预测未来三年中国宠物行业的发展。 第一个问题&#xff1a;分析中国宠物行业按宠物类型的发展…...

ubtil循环函数调用

什么是until until循环是一种控制流结构。它与while循环相反&#xff0c;while循环是在条件为真时执行循环体&#xff0c;而until循环是在条件为假时执行循环体&#xff0c;直到条件为真时才停止循环。 until代码示例&#xff1a; i0 do until [ ! $i -lt 10 ] echo $…...

使用EFK收集k8s日志

首先我们使用EFK收集Kubernetes集群中的日志&#xff0c;本次实验讲解的是在Kubernetes集群中启动一个Elasticsearch集群&#xff0c;如果企业内已经有了Elasticsearch集群&#xff0c;可以直接将日志输出至已有的Elasticsearch集群。 文章目录 部署elasticsearch创建Kibana创建…...

聚水潭与MySQL数据集成案例分享

聚水潭数据集成到MySQL的技术案例分享 在现代数据驱动的业务环境中&#xff0c;如何高效、可靠地实现不同系统之间的数据对接成为企业关注的焦点。本次案例将详细介绍如何通过轻易云数据集成平台&#xff0c;将聚水潭的数据无缝集成到MySQL数据库中&#xff0c;实现从“聚水谭…...

Python 版本的 2024详细代码

2048游戏的Python实现 概述&#xff1a; 2048是一款流行的单人益智游戏&#xff0c;玩家通过滑动数字瓷砖来合并相同的数字&#xff0c;目标是合成2048这个数字。本文将介绍如何使用Python和Pygame库实现2048游戏的基本功能&#xff0c;包括游戏逻辑、界面绘制和用户交互。 主…...

SpringCloud框架学习(第四部分:Gateway网关)

目录 十一、Gateway新一代网关 1.概述 2.Gateway三大核心 3.工作流程 4.入门配置 5.路由映射 &#xff08;1&#xff09;8001 外部添加网关 &#xff08;2&#xff09;服务间调用添加网关 &#xff08;3&#xff09;存在问题 6.Gateway高级特性 &#xff08;1&#x…...

C++ 类和对象 (上 )

学习本身就是一件很快乐的事情 一. 面向对象和面向过程 我们在学习计算机的过程中经常会听到xxx是一门面向对象的语言 xxx是一门面向过程的语言 那么到底什么是面向对象 什么是面向过程呢&#xff1f; 简单介绍下 面向过程 面向过程关注的是过程 分析出求解问题的步骤&…...

HAProxy面试题及参考答案(精选80道面试题)

目录 什么是 HAProxy? HAProxy 主要有哪些功能? HAProxy 的关键特性有哪些? HAProxy 的主要功能是什么? HAProxy 的作用是什么? 解释 HAProxy 在网络架构中的作用。 HAProxy 与负载均衡器之间的关系是什么? HAProxy 是如何实现负载均衡的? 阐述 HAProxy 的四层…...

探索PyCaret:一个简化机器学习的全栈库

探索PyCaret&#xff1a;一个简化机器学习的全栈库 机器学习领域充满了挑战&#xff0c;从数据预处理、特征工程到模型训练与评估&#xff0c;再到模型部署。对于数据科学初学者或者时间有限的开发者&#xff0c;这一流程可能显得繁琐且复杂。幸运的是&#xff0c;PyCaret 提供…...

英语写作中“联系、关联”associate correlate 及associated的用法

似乎是同义词的associate correlate 实际上意思差别明显&#xff0c;associate 是人们把两者联系在一起&#xff08;主观联系&#xff09;&#xff0c;而correlate 指客观联系。 例如&#xff1a; We always associate sports with health.&#xff08;我们总是将运动和健康联…...

深度学习之目标检测的技巧汇总

1 Data Augmentation 介绍一篇发表在Big Data上的数据增强相关的文献综述。 Introduction 数据增强与过拟合 验证是否过拟合的方法&#xff1a;画出loss曲线&#xff0c;如果训练集loss持续减小但是验证集loss增大&#xff0c;就说明是过拟合了。 数据增强目的 通过数据增强…...

【Flask+Gunicorn+Nginx】部署目标检测模型API完整解决方案

【Ubuntu 22.04FlaskGunicornNginx】部署目标检测模型API完整解决方案 文章目录 1. 搭建深度学习环境1.1 下载Anaconda1.2 打包环境1.3 创建虚拟环境1.4 报错 2. 安装flask3. 安装gunicorn4. 安装Nginx4.1 安装前置依赖4.2 安装nginx4.3 常用命令 5. NginxGunicornFlask5.1 ng…...

Spark核心组件解析:Executor、RDD与缓存优化

Spark核心组件解析&#xff1a;Executor、RDD与缓存优化 Spark Executor Executor 是 Spark 中用于执行任务&#xff08;task&#xff09;的执行单元&#xff0c;运行在 worker 上&#xff0c;但并不等同于 worker。实际上&#xff0c;Executor 是一组计算资源&#xff08;如…...

“AI玩手机”原理揭秘:大模型驱动的移动端GUI智能体

作者&#xff5c;郭源 前言 在后LLM时代&#xff0c;随着大语言模型和多模态大模型技术的日益成熟&#xff0c;AI技术的实际应用及其社会价值愈发受到重视。AI智能体&#xff08;AI Agent&#xff09;技术通过集成行为规划、记忆存储、工具调用等机制&#xff0c;为大模型装上…...

离散数学【关系】中的一些特殊关系

在数学中&#xff0c;关系是描述集合之间元素间关系的方式。以下是对一些常见关系的详细分析及举例&#xff1a; 1. 空关系 (Empty Relation) 空关系是指在一个集合中&#xff0c;没有任何元素之间存在关系。即对于集合中的所有元素&#xff0c;空关系都不包含任何有序对。 …...

docker 配置代理

创建 Docker 服务配置文件&#xff1a; sudo mkdir -p /etc/systemd/system/docker.service.d sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf添加代理配置&#xff1a; [Service] Environment"HTTP_PROXYhttp://<proxy-address>:<port>&q…...

Dockerfile详解:构建简单高效的容器镜像

引言 在容器化技术日益普及的今天&#xff0c;Dockerfile 成为了构建 Docker 镜像的核心工具。通过编写 Dockerfile&#xff0c;开发者可以将应用程序及其依赖打包成一个可移植、可复用的镜像&#xff0c;从而简化部署和运维工作。本文将详细介绍 Dockerfile 的基本概念、常用指…...

RHCD-----shell

要求&#xff1a; 通过shell脚本分析部署nginx网络服务 1.接收用户部署的服务名称 2.判断服务是否安装 ​ 已安装&#xff1b;自定义网站配置路径为/www&#xff1b;并创建共享目录和网页文件&#xff1b;重启服务 ​ 没有安装&#xff1b;安装对应的软件包 3.测试 判断服务是…...

<硬件有关> 内存攒机认知入门,内存的选择 配置 laptop PC 服务器

原因 这不是黑五吗&#xff0c;给我儿子买了台最便宜 ($300) DELL laptop&#xff0c;CPU 是 i5-1235U&#xff0c;但只有 8GB 内存。升级内存吧。 如何选择内存&#xff1a;家用范围 这里不考虑品牌&#xff0c;在我眼里&#xff0c;区别就是价格&#xff0c;还有所谓的物理…...

基于springboot的来访管理系统的设计与实现

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的来访管理系统的设计与实…...

window11编译pycdc.exe

一、代码库和参考链接 在对python打包的exe文件进行反编译时&#xff0c;会使用到uncompyle6工具&#xff0c;但是这个工具只支持python3.8及以下&#xff0c;针对更高的版本的python则不能反编译。 关于反编译参考几个文章&#xff1a; Python3.9及以上Pyinstaller 反编译教…...

11.22.2024 面试后记

Watching those fucking ap’s paper is bullshit and wasting your time. you’d mother fucker directly say I’m not qualified. if I’m qualified, how could I see u at this place. your dad is alread being rich and enjoy the world. 抽了一周时间去看那些教授的文章…...

Bug Fix 20241122:缺少lib文件错误

今天有朋友提醒才突然发现 gitee 上传的代码存在两个很严重&#xff0c;同时也很低级的错误。 因为gitee的默认设置不允许二进制文件的提交&#xff0c; 所以PH47框架下的库文件&#xff08;各逻辑层的库文件&#xff09;&#xff0c;以及Stm32Cube驱动的库文件都没上传到Gi…...

Pinia 实战教程:构建高效的 Vue 3 状态管理系统

前言 在前端开发中&#xff0c;状态管理已成为必不可少的一部分&#xff0c;Vue.js 生态系统中提供了多种状态管理解决方案。Pinia 是 Vue 3 推出的一种全新的状态管理库&#xff0c;旨在取代 Vuex&#xff0c;提供更简洁的 API、更优雅的 TypeScript 支持以及更高效的性能表现…...

springboot3如何集成knife4j 4.x版本及如何进行API注解

1. 什么是Knife4j knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案, 取名knife4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍!knife4j的前身是swagger-bootstrap-ui,swagger-bootstrap-ui自1.9.6版本后,正式更名为knife4j为了契合微服务的架构发展,由于原来…...

区块链讲解

区块链技术是一种分布式账本技术&#xff0c;其应用场景和优势可以总结如下&#xff1a; 金融服务&#xff1a;区块链可以用于支付、跨境汇款、证券交易、贷款等金融服务领域&#xff0c;通过去中心化的方式实现快速、低成本、安全的交易。 物联网&#xff1a;区块链可以用于物…...

使用eclipse构建SpringBoot项目

我这里用eclipse2018版本做演示&#xff0c;大家有需要的可以下载Eclipse Downloads | The Eclipse Foundation 1.打开eclipse&#xff0c;选择存放代码的位置 2.选择 file >> new >> project >> 选择springboot文件下的 spring starter project 2.这里选择N…...