工作中很多中间件不需要详细理解其技术原理,而是需要能够熟练使用,这里做一些记录。文章学习价值不大,仅作记录

对每个中间件应该从如下角度来进行概述,达到能够调试,代码熟练使用程度。

1
2
3
4
5
架构原理:机制、协议、数据结构与持久化。
API & SDK 设计:接口使用、配置项含义、异步/同步调用模式。
客户端内部机制:连接管理、缓冲队列、重试与容错。
运维与调试工具链:CLI 工具、监控指标(Metrics)、日志分析。
常见故障与边界场景:积压、阻塞、倾斜、一致性失效及应对方案。

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
2
3
4
5
6
7
db.movies.aggregate([
{ $match: { directors: { $exists: true, $ne: null } } },
{ $unwind: "$directors" },
{ $group: { _id: "$directors", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 3 }
])

➕ 创建与删除 (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
2
3
4
5
6
7
8
flowchart LR
Client[客户端] --> Coord[协调节点<br>(接收请求的任意节点)]
Coord -->|计算路由,转发请求| Primary[Primary Shard<br>(主分片)]
Primary -->|写入数据| Primary
Primary -->|同步数据| Replica[Replica Shard<br>(副本分片)]
Replica -->|同步完成| Primary
Primary -->|返回成功| Coord
Coord -->|返回结果| Client
1
2
3
4
5
6
7
8
flowchart LR
Client[客户端] --> Coord[协调节点]
Coord -->|轮询分发查询请求| Primary[Primary Shard<br>(主分片)]
Coord -->|轮询分发查询请求| Replica[Replica Shard<br>(副本分片)]
Primary -->|返回部分结果| Coord
Replica -->|返回部分结果| Coord
Coord -->|汇总合并结果| Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
flowchart TD
A[Elasticsearch Index<br>(逻辑概念,类似数据库的表)] --> B[Shard 1<br>(数据分片)]
A --> C[Shard 2]
A --> D[Shard N]

B --> E[Lucene Index<br>(物理实现,一个分片对应一个Lucene索引)]

E --> F[Commit Point<br>(清单文件,记录所有Segment)]
E --> G[Segment A<br>(不可变的倒排索引)]
E --> H[Segment B<br>(不可变的倒排索引)]
E --> I[Segment ...]

G --> J[倒排索引<br>(核心数据结构,实现快速搜索)]
J --> K[Term Index<br>(词项索引,用于快速定位)]
J --> L[Term Dictionary<br>(词项字典,存储所有词项)]
J --> M[Postings List<br>(倒排列表,记录文档ID及位置)]

E --> N[Doc Values<br>(列式存储,用于聚合与排序)]

集群

ES自身通过Zen discovery具备集群的语义,可以通过给每个节点配置相同的cluster.name来使其加入一个相同的集群。同时要注意不同环境使用不同的cluster.name。另外每个节点还可通过设置node.name来设置节点名称。
ES的发现机制也是基于Zendiscovery的,其中的master投票机制,脑裂现象都与常规的分布式情况差不太多。

映射

映射用于定义定ES 对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema 。但是不强制指定类型。如果不显示指定类型,那么为动态映射,反之为静态映射。
一个标准的映射是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PUT my_index   
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
"mappings": {
"_doc": {
"properties": {
"title": { "type": "text" },
"name": { "type": "text" },
"age": { "type": "integer" },
"created": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
}
}

使用

目前有如下常用的稳定的主版本: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{  
"name" : "U7fp3O9",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "-Rj8jGQvRIelGd9ckicUOA",
"version" : {
"number" : "6.8.1",
"build_flavor" : "default",
"build_type" : "zip",
"build_hash" : "1fad4e1",
"build_date" : "2019-06-18T13:16:52.517138Z",
"build_snapshot" : false,
"lucene_version" : "7.7.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}

如果需要检查集群健康状况,在kibana执行 GET /_cluster/health

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{  
"cluster_name" : "wujiajian",
"status" : "yellow", //绿色为健康,黄色为预警,红色为异常
"timed_out" : false,
"number_of_nodes" : 1,
"number_of_data_nodes" : 1,
"active_primary_shards" : 9,
"active_shards" : 9,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 5,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 64.28571428571429
}

工作流程

. 拆解(按用途分解)

  • 用于搜索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 事务 不支持事务
数据一致性 强一致性(可配置) 最终一致性
更新成本 高效,原地更新 ,需整个文档重新索引
写入性能 ,为高频写入优化 相对较低,索引构建有开销
检索能力 基础但够用 功能强大,专业级全文搜索

本站由 Edison.Chen 创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。