工作中很多中间件不需要详细理解其技术原理,而是需要能够熟练使用,这里做一些记录。文章学习价值不大,仅作记录
对每个中间件应该从如下角度来进行概述,达到能够调试,代码熟练使用程度。
1 | 架构原理:机制、协议、数据结构与持久化。 |
mogoDB
存储结构
- 文档(Document) :MongoDB 中最基本的单元,由BSON键值对(key-value)组成,类似于关系型数据库中的行(Row)。
- 集合(Collection) :一个集合可以包含多个文档,类似于关系型数据库中的表(Table)。
- 数据库(Database) :一个数据库中可以包含多个集合,可以在 MongoDB 中创建多个数据库,类似于关系型数据库中的数据库(Database)。
| SQL 术语 | MongoDB 术语 | 说明 |
|---|---|---|
| Database (数据库) | Database (数据库) | 概念相同,用于存放数据。 |
| Table (表) | Collection (集合) | 一组文档的容器,类似“表”,但无固定结构。 |
| Row (行) | Document (文档) | 一条数据记录,以 BSON (二进制 JSON) 格式存储。 |
| Column (列) | Field (字段) | 文档中的一个键值对。 |
| Primary Key (主键) | _id (主键) | 每个文档的唯一标识符,MongoDB 会自动生成。 |
特点
- 数据记录被存储为文档:MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。
- 模式自由:集合的概念类似 MySQL 里的表,但它不需要定义任何模式,能够用更少的数据对象表现复杂的领域模型对象。
- 支持多种查询方式:MongoDB 查询 API 支持读写操作 (CRUD) 以及数据聚合、文本搜索和地理空间查询。
- 支持 ACID 事务:NoSQL 数据库通常不支持事务,为了可扩展和高性能进行了权衡。不过也有例外,MongoDB 就支持事务,并且与关系型数据库一样具有 ACID 特性。MongoDB 单文档原生支持原子性;4.0 加入了对多文档事务的支持,但事务的作用域限制在一个副本集内;4.2 引入了分布式事务,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
- 高效的二进制存储:存储在集合中的文档以键值对的形式存在,键用于唯一标识一个文档,一般是 ObjectId 类型,值以 BSON 形式存在。BSON = Binary JSON,是在 JSON 基础上加了一些类型及元数据描述的格式。
- 自带数据压缩功能:存储同样的数据所需的资源更少。
- 支持 mapreduce:通过分治的方式完成复杂的聚合任务。不过从 MongoDB 5.0 开始,map-reduce 已经不被官方推荐使用了,替代方案是聚合管道,它提供比 map-reduce 更好的性能和可用性。
- 支持多种类型的索引:包括单字段索引、复合索引、多键索引、哈希索引、文本索引、地理位置索引等,每种类型的索引有不同的使用场合。
- 支持 failover:提供自动故障恢复的功能,主节点发生故障时,自动从从节点中选举出一个新的主节点,确保集群的正常使用,这对于客户端来说是无感知的。
- 支持分片集群:MongoDB 支持集群自动切分数据,让集群存储更多的数据,具备更强的性能。在数据插入和更新时,能够自动路由和存储。
- 支持存储大文件 :MongoDB 的单文档存储空间要求不超过 16MB。对于超过 16MB 的大文件,MongoDB 提供了 GridFS 来进行存储,通过 GridFS,可以将大型数据进行分块处理,然后将这些切分后的小文档保存在数据库中。
语法
在 MongoDB 的 mongosh Shell 中,语法主要围绕 CRUD(增删改查)、聚合管道 和 索引管理 这几个核心领域展开。
📖 查询文档 (Read)
查询操作主要使用 db.collection.find() 方法。
基本语法:db.collection.find( <query>, <projection> )
<query>: 筛选条件,决定返回哪些文档。空文档{}表示返回所有文档。<projection>: (可选) 指定返回的字段,1 表示包含,0 表示排除。
1. 简单查询- 查询所有:
db.movies.find(),类似 SQL 的SELECT * FROM movies。 - 等值查询:
db.movies.find({ "title": "Titanic" }),类似 SQL 的 `WHERE title = “Titanic” - 嵌套字段查询: 使用点号
.访问嵌套字段。db.movies.find({ "imdb.rating": { $gte: 7 } })。
2. 使用查询操作符 - 比较操作符:
$eq(等于),$ne(不等于),$gt(大于),$gte(大于等于),$lt(小于),$lte(小于等于)。$in: 匹配数组中的任一值。例如,查询分级为 “PG” 或 “PG-13” 的电影:db.movies.find({ rated: { $in: [ "PG", "PG-13" ] } })
- 逻辑操作符:
- 隐式
$and: 在find()中直接组合多个条件。例如,查询在墨西哥上映 且 IMDB 评分 ≥ 7 的电影:db.movies.find({ countries: "Mexico", "imdb.rating": { $gte: 7 } }) - 显式
$or: 满足任一条件即可。
3. 查询修饰
- 隐式
limit(): 限制返回文档数。skip(): 跳过指定数量的文档。sort(): 对结果排序,1 为升序,-1 为降序。
✍️ 更新文档 (Update)
更新操作需要指定筛选条件和更新操作两部分。
主要方法:
db.collection.updateOne(<filter>, <update>): 更新第一个匹配的文档。db.collection.updateMany(<filter>, <update>): 更新所有匹配的文档。
更新操作符语法:{ <operator1>: { <field1>: <value1>, ... }, <operator2>: { ... } }
常用更新操作符:
$set: 设置字段的值。如果字段不存在则创建。db.users.updateOne( { name: "Alice" }, { $set: { age: 26 } } )$unset: 删除字段。$inc: 将字段的值增加指定的量。$rename: 重命名字段。
数组更新操作符:$push: 向数组末尾添加一个元素。$pull: 从数组中删除所有匹配的值。$addToSet: 向数组中添加元素,但仅当该元素不存在时。$pop: 删除数组的第一项或最后一项。
🗂️ 聚合操作 (Aggregation)
聚合用于对数据进行分组、统计和复杂转换,核心是聚合管道。
基本语法:db.collection.aggregate( [ { <stage1> }, { <stage2> }, ... ] )
数据依次流经管道中的每个阶段(Stage)进行处理。
常用阶段 (Stage) 操作符:
$match: 过滤文档,类似find()的查询条件。$group: 按指定字段分组,并执行统计(如求和、平均值)。$project: 重塑文档,选择、计算或重命名字段。$sort: 对文档排序。$limit/$skip: 限制或跳过文档数量。$unwind: 拆分数组字段,为每个元素生成一个文档。$lookup: 执行类似于 SQL 的多表关联查询。$out/$merge: 将聚合结果写入一个新集合或合并到现有集合中。
示例: 统计每位导演的电影数量,并找出执导电影最多的前3位。
1 | db.movies.aggregate([ |
➕ 创建与删除 (Create & Delete)
- 插入文档:
db.collection.insertOne( <document> )db.collection.insertMany( [ <document1>, <document2>, ... ] )
- 删除文档:
db.collection.deleteOne( <filter> )db.collection.deleteMany( <filter> )
📇 索引管理 (Index)
索引能极大提升查询效率。
- 创建索引:
db.collection.createIndex( keys, options )keys: 指定索引字段和排序方向,1为升序,-1为降序。options: (可选) 如{ unique: true }创建唯一索引。- 单字段索引:
db.collection.createIndex( { age: 1 } ) - 复合索引:
db.collection.createIndex( { "name": 1, "age": -1 } )
- 查看索引:
db.collection.getIndexes() - 删除索引:
db.collection.dropIndex( "indexName" )db.collection.dropIndexes()
es
存储结构
索引,在es中的索引概念不同于sql中的索引。其更倾向于强调从非结构化数据中提取并重新组织的信息。这种方式的主要工作量在于前期索引的创建。Lucene是一个Apache的工具包,提供了建立索引,高效搜索,分词处理等操作。但是其并不是一个完整的搜索引擎,基于Lucene创建的搜索引擎主要有Solr和Es。ES 本身就具有分布式的特性和易安装使用的特点,而 Solr 的分布式需要借助第三方来实现,例如通过使用 ZooKeeper 来达到分布式协调管理)。ES并不仅仅是Lucene,也不仅仅是一个搜索引擎,
- 其是一个分布式的实时文档存储,每个字段可以背索引与搜索。
- 是一个分布式的实时搜素引擎。
- 能够胜任上百个服务节点的扩展
在创建索引的过程中,实际上就是记录词条与到排表。写索引只能在主分片上,然后同步到副本分片。 - 词条(Term): 索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
- 词典(Term Dictionary): 或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
- 倒排表(Post list): 一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。
- 倒排文件(Inverted File): 所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件存储倒排索引的物理文件。索引文档被拆分成多个字文件的形式被存储在磁盘,每个字文件叫做段,段被写入磁盘后会产出一个提交点,提交点是一个用来记录所有提交后段信息的文件,一旦提交点生成,那么段变为只读。那么对于索引文件的分段进行crud做葱如下:
- 新增,新增很好处理,由于数据是新的,所以只需要对当前文档新增一个段就可以了。
- 删除,由于不可修改,所以对于删除操作,不会把文档从旧的段中移除而是通过新增一个 .del 文件,文件中会列出这些被删除文档的段信息。这个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。
- 更新,不能修改旧的段来进行反映文档的更新,其实更新相当于是删除和新增这两个动作组成。会将旧的文档在 .del 文件中标记删除,然后文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就会被移除。
段
段概念是ES中的核心之一,每当数据写入es时,es会执行refresh操作,将缓冲区内容生成一个新的段,分为提交点前和后,提交点前只写,提交点后只读。随着段的数量增加es会在后台执行段合并,物理删除那些旧的文件。
架构
es从上至下可以被划为集群->节点->分片,下图描述了 3 个节点的集群,共拥有 12 个分片,其中有 4 个主分片(S0、S1、S2、S3)和 8 个副本分片(R0、R1、R2、R3),每个主分片对应两个副本分片,节点 1 是主节点(Master 节点)负责整个集群的状态。
![[Pasted image 20260821173646.png]]
读写数据会根据特定值(默认为文档id)做哈希然后取余,决定其被分配到哪个主分片中,然后同步到其他副本分片。其中通过乐观并发控制数据的冲突。一旦所有的副本分片都报告成功,则节点 ES3 将向协调节点报告成功,协调节点向客户端报告成功。
1 | flowchart LR |
1 | flowchart LR |
1 | flowchart TD |
集群
ES自身通过Zen discovery具备集群的语义,可以通过给每个节点配置相同的cluster.name来使其加入一个相同的集群。同时要注意不同环境使用不同的cluster.name。另外每个节点还可通过设置node.name来设置节点名称。
ES的发现机制也是基于Zendiscovery的,其中的master投票机制,脑裂现象都与常规的分布式情况差不太多。
映射
映射用于定义定ES 对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema 。但是不强制指定类型。如果不显示指定类型,那么为动态映射,反之为静态映射。
一个标准的映射是
1 | PUT my_index |
使用
目前有如下常用的稳定的主版本:2.x,5.x,6.x,7.x(current)。你可能会发现没有 3.x 和 4.x,ES 从 2.4.6 直接跳到了 5.0.0。其实是为了 ELK(ElasticSearch,Logstash,Kibana)的版本统一,免的给用户带来混乱。
运行bin/elasticsearch即可运行程序,默认在9200下运行。请求 curl http://localhost:9200/ 或者浏览器输入 http://localhost:9200, 得到一个 JSON 对象,其中包含当前节点、集群、版本等信息。
1 | { |
如果需要检查集群健康状况,在kibana执行 GET /_cluster/health
1 | { |
工作流程
. 拆解(按用途分解)
- 用于搜索:
productName被分词器切成小米、14、拍照、手机,构建倒排索引(词→文档ID映射)。 - 用于聚合排序:
price按文档ID顺序排列,构建 Doc Values(列式存储)。 - 用于展示:原始 JSON 被压缩,准备存为
_source。
2. 分装(写入不同文件)
- 倒排索引写入
.tim/.tip文件。 - Doc Values 写入
.dvd/.dvm文件。 _source写入.fdt/.fdx文件。- 三者并行写入文件系统缓存,不是直接落盘。
3. 封存(Refresh 触发)
- 默认每秒一次,Lucene 将上述三份数据原子性地打包,分配一个段名(如
_1u)。 - 更新分片的提交点(Commit Point),在清单中注册新段。
结果:段立即变为可搜索,且从此不可修改(只能被标记删除,等待后台合并时物理回收)。
mongodb与es
- 选择 MongoDB,如果:你的应用是以数据存储和事务为核心,例如电商平台的订单系统、用户信息管理、内容管理系统(CMS)等。它需要处理频繁的读写,并保证数据的一致性和完整性。
- 选择 Elasticsearch,如果:你的应用是以搜索和分析为核心,例如网站/App 的站内搜索、日志分析平台(如 ELK)、实时监控系统等。它需要处理海量文本数据,并提供极快的搜索响应。
- 两者结合使用(最佳实践):在现代复杂的应用中,MongoDB + Elasticsearch 是一种非常流行的组合拳。
- MongoDB 作为主数据源,负责处理所有的事务性写入和核心业务逻辑,保证数据可靠。
- Elasticsearch 作为辅助索引,通过数据同步工具(如
mongo-connector)将需要被检索的数据实时同步到 ES 中,为前台应用提供高性能的搜索服务。
| 对比维度 | MongoDB | Elasticsearch |
|---|---|---|
| 核心定位 | 通用型文档数据库 | 分布式搜索与分析引擎 |
| 数据模型 | 文档型,BSON 格式 | 文档型,JSON 格式 |
| Schema 模式 | 动态 Schema,灵活,无需预定义 | 强 Schema(Mapping),需预先定义 |
| 索引结构 | B-Tree 索引 | 倒排索引、BKD 树 |
| 查询优化 | 复杂查询、聚合、事务 | 全文搜索、实时聚合、相关性排序 |
| 事务支持 | 支持多文档 ACID 事务 | 不支持事务 |
| 数据一致性 | 强一致性(可配置) | 最终一致性 |
| 更新成本 | 高效,原地更新 | 高,需整个文档重新索引 |
| 写入性能 | 高,为高频写入优化 | 相对较低,索引构建有开销 |
| 检索能力 | 基础但够用 | 功能强大,专业级全文搜索 |