springboot第66集:字节跳动二面经,一文让你走出微服务迷雾架构周刊

科技   其他   2024-03-18 03:57   广东  

MongoDB 可以应对三高需求

具体的应用场景:

  • 社交场景, 使用 MongoDB 存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.
  • 游戏场景, 使用 MongoDB 存储游戏用户信息, 用户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.
  • 物流场景, 使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.
  • 物联网场景, 使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日志信息, 并对这些信息进行多维度的分析.
  • 视频直播, 使用 MongoDB 存储用户信息, 点赞互动信息等.
  • admin: 从权限角度考虑, 这是 root 数据库, 如果将一个用户添加到这个数据库, 这个用户自动继承所有数据库的权限, 一些特定的服务器端命令也只能从这个数据库运行, 比如列出所有的数据库或者关闭服务器
  • local: 数据永远不会被复制, 可以用来存储限于本地的单台服务器的集合 (部署集群, 分片等)
  • config: Mongo 用于分片设置时, config 数据库在内部使用, 用来保存分片的相关信息

线程池详解

线程池用来处理异步任务或者并发执行的任务

优点:

  1. 重复利用已创建的线程,减少创建和销毁线程造成的资源消耗
  2. 直接使用线程池中的线程,提高响应速度
  3. 提高线程的可管理性,由线程池同一管理

ThreadPoolExecutor

java中线程池使用ThreadPoolExecutor实现

corePoolSize:线程池的核心线程数量

maximumPoolSize:线程池允许创建的最大线程数量

keepAliveTime:线程活动保持时间

unit:线程活动保持时间的单位

workQueue:任务队列,用来保存等待执行任务的阻塞队列

创建线程有三种方式

  1. 继承Thread
  2. 实现Runnable接口
  3. 实现Callable接口

客户端负载均衡器的实现原理是通过注册中心,如 Nacos,将可用的服务列表拉取到本地(客户端),再通过客户端负载均衡器(设置的负载均衡策略)获取到某个服务器的具体 ip 和端口,然后再通过 Http 框架请求服务并得到结果

从库读主库写,一般不要采用双主或多主引入很多复杂性,同时目前很多拆分的解决方案同时也兼顾考虑了读写分离。

总体来讲,MyISAM 适合 SELECT 密集型的表,而 InnoDB 适合 INSERT 和 UPDATE 密集型的表

系统内有一张5000W+的大表。跟踪代码发现,该表是用于存储资金流水的表格,关联着众多功能点,同时也有众多的下游系统在使用这张表的数据。进一步的观察发现,这张表还在以每月600W+的数据持续增长,也就是说,不超过半年,这张表会增长到1个亿!

这个数据量,对于mysql数据库来说是绝对无法继续维护的了,因此在接手系统两个月后,我们便开起了大表拆分的专项工作。

  • 涉及到流水表流水的接口超时频发,部分接口基本不可用
  • 每日新增流水缓慢,主要是插入数据库的时候非常慢
  • 单表占用空间过大,DBA的数据库监控经常报警
  • 无法对表进行变更,任何alter操作都会引起主从的高延迟和长时间锁表
  • 将流水大表数据拆分至各个分表,保证每张分表数据在1000W左右(经验上看单表1000W的量对mysql来说没啥压力)
  • 在拆表的前提下,针对不同接口的查询条件进行优化,保证各个对外、对内接口的可用性。彻底杀死mysql慢查询。
  • 该表的数据可以说是整个财务系统最基础的数据,相关功能和下游系统非常多。这要求开发、测试和上线流程必须极其严密,任何小失误都会引起大问题。
  • 涉及的场景非常多。统计下来,一共有26个场景,需要改造32个mapper方法,具体需要改造的方法就更加无计其数了。
  • 数据量非常大,迁移数据的过程必须保证系统稳定。
  • 用户较多且功能重要。分表功能上线时,必须尽量压缩系统无法使用时长,同时需要保证系统可用性。这要求团队必须设计完整可靠的上线流程、数据迁移方案、回滚方案、降级策略。
  • 表的拆分势必带来部分接口的变化,接口的变化又会带来其他系统的改造。如何推动其他系统进行改造,如何协调多方合作的开发、测试和上线是另一个难点。
image.png

采用sharding-jdbc作为分表插件。其优势如下:1、支持多种分片策略,自动识别=或in判断具体在哪张分表里。2、轻量级,作为maven依赖引入即可,对业务的侵入性极低。

为提升查询速度,在整个项目的初期,团队成员考虑引入ES存储流水以提升查询速度。

多表的分页问题 拆表一定会引起分页查询的难度增加。由于各个表查出来的数据量不等,原始的sql语句limit不再适用,需要设计一个新方法便捷的获取分页信息。

单表优化

除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。

而事实上很多时候 MySQL 单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量。

字段

  • 尽量使用 TINYINTSMALLINTMEDIUM_INT 作为整数类型而非 INT,如果非负则加上 UNSIGNED
  • VARCHAR 的长度只分配真正需要的空间
  • 使用枚举或整数代替字符串类型
  • 尽量使用 TIMESTAMP 而非 DATETIME
  • 单表不要有太多字段,建议在 20 以内
  • 避免使用 NULL 字段,很难查询优化且占用额外索引空间
  • 用整型来存 IP
  • 索引并不是越多越好,要根据查询有针对性的创建,考虑在 WHEREORDER BY 命令上涉及的列建立索引,可根据 EXPLAIN 来查看是否用了索引还是全表扫描
  • 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段
  • 字符字段只建前缀索引
  • 字符字段最好不要做主键
  • 不用外键,由程序保证约束
  • 尽量不用 UNIQUE,由程序保证约束
  • 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引
  • 可通过开启慢查询日志来找出较慢的 SQL
  • 不做列运算: SELECT id WHERE age+1=10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
  • sql 语句尽可能简单:一条 sql 只能在一个 cpu 运算;大语句拆小语句,减少锁时间;一条大sql 可以堵死整个库
  • 不用 SELECT *
  • OR 改写成 INOR 的效率是 n 级别, IN 的效率是 log(n) 级别,IN 的个数建议控制在 200 以内
  • 不用函数和触发器,在应用程序实现
  • 避免 %xxx 式查询
  • 少用 JOIN
  • 使用同类型进行比较,比如用 '123' 和 '123' 比, 123 和 123 比
  • 尽量避免在 WHERE 子句中使用 !=<> 操作符,否则将引擎放弃使用索引而进行全表扫描
  • 对于连续数值,使用 BETWEEN 不用 INSELECT id FROM t WHERE num BETWEEN 1 AND 5
  • 列表数据不要拿全表,要使用 LIMIT 来分页,每页数量也不要太大

InnoDB 在 MySQL 5.5 后成为默认索引,它的特点是:

  • 支持行锁,采用 MVCC 来支持高并发
  • 支持事务
  • 支持外键
  • 支持崩溃后的安全恢复
  • 不支持全文索引

MySQL大文本存储压缩


select
  table_name as '表名',
  table_rows as '记录数',
  truncate(data_length/1024/1024, 2) as '数据容量(MB)',
  truncate(index_length/1024/1024, 2) as '索引容量(MB)',
  truncate(DATA_FREE/1024/1024, 2) as '碎片占用(MB)'
from
  information_schema.tables
where
  table_schema=${数据库名}
order by
  data_length desc, index_length desc;

什么是分布式事务问题?

单体应用

单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。

image.png

负载均衡通器常有两种实现手段,一种是服务端负载均衡器,另一种是客户端负载均衡器,而我们今天的主角 Ribbon 就属于后者——客户端负载均衡器。

服务端负载均衡器的问题是,它提供了更强的流量控制权,但无法满足不同的消费者希望使用不同负载均衡策略的需求,而使用不同负载均衡策略的场景确实是存在的,所以客户端负载均衡就提供了这种灵活性。 然而客户端负载均衡也有其缺点,如果配置不当,可能会导致服务提供者出现热点,或者压根就拿不到任何服务的情况

Ribbon 介绍

Ribbon 是 Spring Cloud 技术栈中非常重要的基础框架,它为 Spring Cloud 提供了负载均衡的能力,比如 Fegin 和 OpenFegin 都是基于 Ribbon 实现的,就连 Nacos 中的负载均衡也使用了 Ribbon 框架。

Ribbon 框架的强大之处在于,它不仅内置了 7 种负载均衡策略,同时还支持用户自定义负载均衡策略,所以其开放性和便利性也是它得以流行的主要原因。

mongodb-driver是mongo官方推出的java连接mongoDB的驱动包,相当于JDBC驱动。我们通过一个入门的案例来了解mongodb-driver 的基本使用。

秒杀场景下的业务梳理——Redis分布式锁的优化

商品秒杀本质上其实还是商品购买,所以我们需要准备一张订单表来记录对应的秒杀订单。

这里就涉及到了一个订单 id 的问题了,我们是否可以像其他表一样使用数据库自身的自增 id 呢?

锁有两种:

一,悲观锁: 认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。例如Synchronized、Lock都属于悲观锁;

二,乐观锁: 认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。

  • 官方驱动说明和下载:mongodb.github.io/mongo-java-…[1]
  • 官方驱动示例文档:mongodb.github.io/mongo-java-…[2]

5.3.2 SpringDataMongoDB

SpringData家族成员之一,用于操作MongoDB的持久层框架,封装了底层的mongodb-driver。

官网主页: projects.spring.io/spring-data…[3]

在 MongoDB 中, 数据库和集合都不需要手动创建, 当我们创建文档时, 如果文档所在的集合或者数据库不存在, 则会自动创建数据库或者集合

  • 数据库 (database)
    • 数据库是一个仓库, 存储集合 (collection)
  • 集合 (collection)
    • 类似于数组, 在集合中存放文档
  • 文档 (document)
    • 文档型数据库的最小单位, 通常情况, 我们存储和操作的内容都是文档

单机搭建mongodb分布式集群(副本集 + 分片集群)

MongoDB是一个开源, 高性能, 无模式的文档型数据库, 当初的设计就是用于简化开发和方便扩展, 是NoSQL数据库产品中的一种.是最 像关系型数据库(MySQL)的非关系型数据库. 它支持的数据结构非常松散, 是一种类似于 JSON 的 格式叫BSON, 所以它既可以存储比较复杂的数据类型, 又相当的灵活. MongoDB中的记录是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档类似于JSON对象, 即一个文档认 为就是一个对象.字段的数据类型是字符型, 它的值除了使用基本的一些类型外, 还可以包括其他文档, 普通数组和文档数组.

“最像关系型数据库的 NoSQL 数据库” . MongoDB 中的记录是一个文档, 是一个 key-value pair. 字段的数据类型是字符型, 值除了使用基本的一些类型以外, 还包括其它文档, 普通数组以及文档数组

MySQL主从复制集群搭建—binlog二进制文件方式

binlog 简介

Mysql中有一个binlog二进制日志,这个日志会记录下主服务器所有修改了的SQL语句,从服务器把主服务器上的binlog二进制日志,在指定的位置开始复制主服务器所有修改的语句,在从服务器上执行一遍。

简而言之就是,主服务器会把create、update、delete语句都记录到一个二进制文件中(binlog),从服务器读取这个文件,执行一遍文件中记录的create、update、delete语句。从而实现主从数据同步。

配置主从库 my.ini 或者 my.cnf 文件

my.ini是Windows系统的,my.cnf是Linux系统的

在 111 和 222 的 my.ini 中的[mysqld]节点下配置

  • server-id = 唯一ID:主服务器唯一 ID,一般设置为机器 IP 地址后三位
  • log-bin = 二进制日志文件存放路径:这个是启动并记录 binlog 日志
  • log-err = 错误日志路径(可选):启动错误日志
  • read-only = 0:0是读写都行(主库),1是只读(从库)
  • binlog-lgnore-db= 数据库名(可选):设置不要主从复制的数据库
  • binlog-do-db = 数据库名(可选):需要复制的数据库名
  • 三台服务器:192.168.216.111、192.168.216.222、192.168.216.333

当主库和从库都配置完 my.ini 文件之后,还需要主库建立一个授权用户,让从库能通过这个用户登录到主库上。

grant replication slave on *.* TO '用户名'@'从机IP' identified by '密码';(建立授权用户)

flush privileges;(刷新mysql的系统权限相关表)

-   在从机上试试可否连接上主机

222从库执行: mysql -h 主机IP -usally -pilovesally
  • 如果连接失败,看看是不是防火墙的原因,配置防火墙的 IP 规则

开始主从复制

  • 查看 master 111 主机状态
show master status;

主要看FilePosition两个参数,File代表从哪个日志文件里同步数据,Position代表从这个文件的什么位置开始同步数据,binlog-do-db 和 binlog-lgnore-db 意思为同步哪几个数据库和不同步哪几个数据库。

  • 从库登录主库设置同步数据文件

如果之前做过同步数据,那么请先停止(stop slave;),否则会报错。

222执行: MASTER_HOST='主机IP', MASTER_USER='主机用户名', MASTER_PASSWORD='主机密码', MASTER_LOG_FILE='File名字', MASTER_LOG_POS=Position数字;
  • 启动从库复制功能
start slave;
  • 查看从库同步状态
show slave status\G;

auto_increment_increment=2 #步长值auto_imcrement。一般有n台主MySQL就填n

auto_increment_offset=1 #起始值。一般填第n台主MySQL

mysql数据同步:canal搭建主从|集群架构

从架构方式上出发,我们用来保证服务高可用的手段主要是主从架构、集群架构。有时我们也把主从归结到集群架构中,但严格意义上讲,集群架构是指多节点同时运行,而主从架构同一时刻只有一个节点运行,另一个节点作为备用,只有当主节点宕机时,备用节点才会启用。

我们首先要理解canal实现数据同步的依赖于binlog,也依赖于mysql dump指令,binlog本身的特性就要求数据原子性、隔离性,有序性,同时mysql dump指令是比较占用mysql服务器资源的,所以要尽可能少的避免,为此canal服务端同一时刻只能有一个节点来读取binlog进行同步。

因此在这样的基础之上,canal的集群模式实际上就是主从模式。那么我们要进行搭建的也就是主从。

我们知道canal中有服务端deployer和客户端adapter,服务端负责从mysql中读取binlog,而客户端负责从服务端读取同步过来的binlog数据,处理后将同步数据发送到目标服务端,比如redis、es或其他的关系性数据库等

zookeeper搭建

采用docker搭建zk

docker run -d -e TZ="Asia/Shanghai" -p 2181:2181 --name zookeeper zookeeper

服务端deployer搭建

1、查询数据源mysql服务的binlog位置

# 源mysql服务器中登陆mysql执行
show binary logs;
image.png

直接在服务器上通过wget指令下载

wget https://github.com/alibaba/canal/releases/download/canal-1.1.6/canal.deployer-1.1.6.tar.gz

解压安装包

tar -zxvf canal.deployer-1.1.6.tar.gz

ElasticSearch是一个基于Lucene的搜索服务器,其实就是对Lucene进行封装,提供了 REST API 的操作接口. ElasticSearch作为一个高度可拓展的开源全文搜索和分析引擎,可用于快速地对大数据进行存储,搜索和分析。 ElasticSearch主要特点:分布式、高可用、异步写入、多API、面向文档 。 ElasticSearch核心概念:近实时,集群,节点(保存数据),索引,分片(将索引分片),副本(分片可设置多个副本) 。它可以快速地储存、搜索和分析海量数据。 ElasticSearch使用案例:维基百科、Stack Overflow、Github 等等。

ElasticSearch集群安装表格:

在安装ElasticSearch之前,我们需要对Linux的环境做一些调整,防止在后续过程中出现一些问题!

1、修改最大内存限制

修改sysctl.conf文件

vim /etc/sysctl.conf

在末尾增加如下配置:

vm.max_map_count = 655360
vm.swappiness=1

然后保存退出,输入以下命令使其生效

   sysctl -p

注:不同的linux服务器90-nproc.conf可能文件名不一样,建议先在/etc/security/limits.d/查看文件名确认之后再来进行更改。

masternode的elasticsearch.yml文件配置如下:

cluster.name: xx
node.name: master
path.data: /home/elk/masternode/data
path.logs: /home/elk/masternode/logs
network.host: 0.0.0.0
network.publish_host: 192.169.0.23
transport.tcp.port: 9301
http.port: 9201
discovery.zen.ping.unicast.hosts: ["192.169.0.23:9301","192.169.0.24:9301","192.169.0.25:9301"]
node.master: true 
node.data: false
node.ingest: false 
index.number_of_shards: 5
index.number_of_replicas: 1
discovery.zen.minimum_master_nodes: 1
bootstrap.memory_lock: true
http.max_content_length: 1024mb

elasticsearch.yml文件参数配置说明:

jvm.options配置Xms和Xmx改成2G,配置如下:

-Xms2g
-Xmx2g

将jvm.options配置Xms和Xmx改成8G,配置如下:

-Xms8g
-Xmx8g

谈谈电商系统中的商品模块设计

  • product_detail 商品详情

商品的详情属于存储字符比较多,所以单独处理,也可以利用这个表扩展相关商品描述字段

  • product_spec 商品规格表
  • product_spec_item 商品属性表

如何根据业务做出合适的商品模块?

上面数据表只是设计的最基础的商品的存储,如果我们要设计出好的模块,先要确定几个点:

  • 平台还是自营
  • 现在和未来经营的类目是标品还是非标品
  • 公司经营的商品种类
  • 是否有分仓发货业务,库存是否要绑定

电商自营和电商平台的区别

电商自营,意味着所有的 itemsku 都是由己方的运营控制,供货商也稳定为自营的一家,所以从常规意义上来说,电商平台一定需要 SPU 的模块,而自营电商不一定需要 SPU 模块,例如某东。

SPU 主要是用来解决相同商品多 item 的规格和描述信息统一的问题,并且方便运营做整个平台的商品管理。后期的实际业务中,如果平台运营走的是类似天猫这样对商家控制比较严格的路子,商家在进行商品管理,也需要对商品可操作的 SPU 进行授权管理,以及分润比例控制。这些都是可以基于 SPU 进行的。

经营类目是标品还是非标品

如果加入了 SPU 的模块,则需要了解经营类目是标品还是非标品。如果是标品,则可以在 SPU 层面加入 SPU 模版,以达到进一步对商家可能发布的 SKU 的控制,确保平台的商品可靠性。例:已知 iPhone X 只有 银色64G银色256G灰色64G灰色256G这四种规格,那么平台是可以在设置 iPhone XSPU 时,直接设置好 iPhone X下的这四个规格的属性。这样商家在创建时可以直接结用这四个属性的信息进行商品创建,规范了平台商品的同时也达到了减少商家工作量的目的。

公司经营的商品种类

这个一般不会对 SPUSKU 的结构关系造成影响,但是也是我们前期需要考虑的点。因为不同的商品种类可能会对之后的交易订单履约流程造成影响。简要归纳商品种类包含如下:

  • 实物商品服务(打车,团购)
  • 虚拟资产(话费,游戏币)
  • 信息(卡密、简历、数据)

一般电商平台我们常见的商品种类主要还是实物商品和虚拟资产,而服务类商品多见于线下 O2O 项目,信息类产品大多是垂直领域。所以如果可能涉及到多种类的商品交易,则需要在设计 SPUitem 时就考虑到商品种类的设置。如果是在现有的系统基础上去新增商品类型,其实也可以通过指定后台类目商品类型的方式进行后续的订单履约业务。

Redis 集群结构搭建

Redis集群架构图:

6000(master) 6003(slave)

6001(master) 6004(slave)

6002(master) 6005(slave)

image.png

MongoDB分为免费社区版、收费企业版,虽说前者功能有所阉割,但好在免费,并且可以满足大多数项目需求,这里咱们先去MongoDB官网-社区版[4]选择对应的操作系统、版本下载安装包,同Redis一样,版本号的第二位数,为奇数代表开发版,为偶数代表是稳定版,我这里选择下载CentOS7-x64-6.0.8-tgz版的安装包。

❶创建MongoDB目录,并通过工具上传安装包至服务器(或虚拟机):

[root@~]# mkdir /soft && mkdir /soft/mongodb/
[root@~]# cd /soft/mongodb/

const categories = (function () {
  let now = new Date();
  let res = [];
  let len = 10;
  while (len--) {
    res.unshift(now.toLocaleTimeString().replace(/^\D*/, ''));
    now = new Date(+now - 2000);
  }
  return res;
})();
const categories2 = (function () {
  let res = [];
  let len = 10;
  while (len--) {
    res.push(10 - len - 1);
  }
  return res;
})();
const data: number[] = (function () {
  let res = [];
  let len = 10;
  while (len--) {
    res.push(Math.round(Math.random() * 1000));
  }
  return res;
})();
const data2: number[] = (function () {
  let res = [];
  let len = 0;
  while (len < 10) {
    res.push(+(Math.random() * 10 + 5).toFixed(1));
    len++;
  }
  return res;
})();

option = {
  title: {
    text: 'Dynamic Data'
  },
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type'cross',
      label: {
        backgroundColor: '#283b56'
      }
    }
  },
  legend: {},
  toolbox: {
    show: true,
    feature: {
      dataView: { readOnly: false },
      restore: {},
      saveAsImage: {}
    }
  },
  dataZoom: {
    show: false,
    start: 0,
    end: 100
  },
  xAxis: [
    {
      type'category',
      boundaryGap: true,
      data: categories
    },
    {
      type'category',
      boundaryGap: true,
      data: categories2
    }
  ],
  yAxis: [
    {
      type'value',
      scale: true,
      name: 'Price',
      max: 30,
      min: 0,
      boundaryGap: [0.2, 0.2]
    },
    {
      type'value',
      scale: true,
      name: 'Order',
      max: 1200,
      min: 0,
      boundaryGap: [0.2, 0.2]
    }
  ],
  series: [
    {
      name: 'Dynamic Bar',
      type'bar',
      xAxisIndex: 1,
      yAxisIndex: 1,
      data: data
    },
    {
      name: 'Dynamic Line',
      type'line',
      data: data2
    }
  ]
};

app.count = 11;
setInterval(function () {
  let axisData = new Date().toLocaleTimeString().replace(/^\D*/, '');

  data.shift();
  data.push(Math.round(Math.random() * 1000));
  data2.shift();
  data2.push(+(Math.random() * 10 + 5).toFixed(1));

  categories.shift();
  categories.push(axisData);
  categories2.shift();
  categories2.push(app.count++);

  myChart.setOption<echarts.EChartsOption>({
    xAxis: [
      {
        data: categories
      },
      {
        data: categories2
      }
    ],
    series: [
      {
        data: data
      },
      {
        data: data2
      }
    ]
  });
}, 2100);


image.png

MongoDB的数据存储格式非常松散,采用“无模式、半结构化”的思想,通过BSON格式来存储数据。

[BSON]的全称为Binary JSON,翻译过来是指二进制的JSON格式,在存储和扫描效率高于原始版JSON,但是对空间的占用会更高。

加群联系作者vx:xiaoda0423

仓库地址:https://github.com/webVueBlog/JavaGuideInterview

参考资料
[1]

http://mongodb.github.io/mongo-java-driver/: https://link.juejin.cn?target=http%3A%2F%2Fmongodb.github.io%2Fmongo-java-driver%2F

[2]

http://mongodb.github.io/mongo-java-driver/3.8/driver/getting-started/quick-start/: https://link.juejin.cn?target=http%3A%2F%2Fmongodb.github.io%2Fmongo-java-driver%2F3.8%2Fdriver%2Fgetting-started%2Fquick-start%2F

[3]

https://projects.spring.io/spring-data-mongodb/: https://link.juejin.cn?target=https%3A%2F%2Fprojects.spring.io%2Fspring-data-mongodb%2F

[4]

https://www.mongodb.com/try/download/community: https://link.juejin.cn?target=https%3A%2F%2Fwww.mongodb.com%2Ftry%2Fdownload%2Fcommunity

算法猫叔
程序员:进一寸有一寸的欢喜
 最新文章