文章目录
相关链接:
ElasticSearch官网地址:https://www.elastic.co/cn/
Lucene官网地址:https://lucene.apache.org/
IK分词器官网地址:https://github.com/infinilabs/analysis-ik
Canal官网地址:https://github.com/alibaba/canal
一、为什么选用ElasticSearch
ElasticSearch是一款非常强大的开源的分布式搜索引擎,具备从海量数据中快速找到需要内容的功能,可以用来实现搜索、日志统计、分析、系统监控等功能。
在实际项目开发中,我们经常将Mysql作为业务数据库,ES作为查询数据库,一是可以用来实现读写分离,使项目的架构有更好的扩展性。二是可以缓解Mysql数据库的查询压力,应对海量数据的复杂查询。
ES 几个显著的特点,能够有效补足 MySQL 在企业级数据操作场景的缺陷,而这也是我们将其选择作为下游数据源重要原因。
核心特点:支持分词检索,多维筛选性能好,支持海量数据查询。
-
文本搜索能力:ES 是基于倒排索引实现的搜索系统,配合多样的分词器,在文本模糊匹配搜索上表现得比较好,业务场景广泛。
-
多维筛选性能好:亿级规模数据使用宽表预构建(消除 join),配合全字段索引,使 ES 在多维筛选能力上具备压倒性优势,而这个能力是诸如 CRM, BOSS, MIS 等企业运营系统核心诉求,加上文本搜索能力,独此一家。
-
开源和商业并行:ES 开源生态非常活跃,具备大量的用户群体,同时其背后也有独立的商业公司支撑,而这让用户根据自身特点有了更加多样、渐进的选择。
ElasticSearch的发展历史:
2004年Shay Banon基于Lucene开发了Compass。
2010年Shay Banon重写了Compass,并取名为Elasticsearch。
后端服务系统集成ElasticSearch的好处:
- 强大的搜索功能,支持大数据量的模糊搜索功能。比如:设备、测点等。
- 一定程度上做到了读写分离,减轻MySQL数据库的读写压力。
- 也方便后续的ELK搭建,为服务日志提供搜索功能。
二、ElasticSearch基本概念
ElasticSearch官网地址:https://www.elastic.co/cn/
1、文档和字段
ES是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中。
ES中的文档和字段对应MySQL中行记录和列属性。
2、索引和映射
索引(Index),就是相同类型的文档的集合。
数据库中的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
ES中的索引和映射对应MySQL中的表和约束。
为避免大家对这两个概念比较陌生,这里给出ES的索引和映射的更具象化的列子:在ES中获取索引和映射。
- 方式1:基于HTTP请求获取
# 获取索引的全部信息
curl -X GET 10.0.0.101:9200/mybook?pretty=true
# 获取索引的映射信息
curl -X GET 10.0.0.101:9200/mybook/_mapping?pretty=true
- 方式2:在kibana中基于API获取
# 获取索引的全部信息
GET /mybook
# 获取索引的映射信息
GET /mybook/_mapping
无法是哪种获取方式,都是在组装DSL并通过Restful接口进行调用【这也是ES可被任何语言调用的原因】。
这里获取结果如下:
# mybook索引的全部信息
{
"mybook" : {
"aliases" : {
},
"mappings" : {
"dynamic" : "false",
"properties" : {
"author" : {
"type" : "text"
},
"id" : {
"type" : "long"
},
"isbn" : {
"type" : "text"
},
"publisherName" : {
"type" : "text"
},
"title" : {
"type" : "text"
}
}
},
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "1",
"provided_name" : "mybook",
"creation_date" : "1735287860250",
"number_of_replicas" : "1",
"uuid" : "eAT8Z7V9ThiPyRu5rnVQLQ",
"version" : {
"created" : "7130499"
}
}
}
}
}
# mybook索引的映射信息
{
"mybook" : {
"mappings" : {
"dynamic" : "false",
"properties" : {
"author" : {
"type" : "text"
},
"id" : {
"type" : "long"
},
"isbn" : {
"type" : "text"
},
"publisherName" : {
"type" : "text"
},
"title" : {
"type" : "text"
}
}
}
}
}
- MySQL基本概念和ES基本概念对比
Elasticsearch | 说明 | MySQL |
---|---|---|
Index | 索引(index),就是文档的集合,类似数据库的表(table) | Table |
Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 | Row |
Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) | Column |
Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) | Schema |
DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD | SQL |
3、倒排索引、文档和词条
倒排索引的概念比较奇特,它是根据MySQL的索引(也即正向索引)概念而命名的。
理解倒排索引需要先理解两个基本概念:文档和词条。
文档就是用来搜索的数据,每一条数据就是一个文档。
词条,即term,也称词项。就是对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。
还是来举一个具体的例子来说明:“华为小米充电器” 就是文档,它可以分为:华为、小米、充电器等这样的几个词条。
倒排索引就是以词条为核心,记录哪些文档包含了该词条。
实际上,倒排索引除了记录哪些文档包含该词条,还记录了词条的词频,词条在文本里的偏移量等信息。
正向索引 V.S 倒排索引
正向索引是最传统的,根据id索引的方式。所以当需要根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
正向索引—优点:
可以给多个字段创建索引;根据索引字段搜索、排序速度非常快。
正向索引—缺点:
根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
倒排索引—优点:
根据词条搜索、模糊搜索时,速度非常快。
倒排索引—缺点:
只能给词条创建索引,而不是字段;无法根据字段做排序。
4、分词器
分词器有两个作用:一是创建倒排索引时对文档分词,二是用户搜索时,对输入的内容进行分词。
IK 分词器(IK Analyzer)是专门为 ElasticSearch 设计开发的一款中文分词插件。主要用于对中文文本进行准确且合理的分词处理,以便更好地实现搜索、索引等功能。
IK 分词原理:IK 分词器基于词典和一些特定的规则来对文本进行切分。它内部有一个较为丰富的中文词典,里面包含了大量常见的中文词汇、短语等内容。在对文本进行分词时,会根据词典中的词条以及一些预设的规则去识别和划分词语。
IK分词有两种分词模式:
- ik_max_word:这是一种细粒度的分词模式。例如对于文本 “中华人民共和国”,它会被切分成 “中华”“中华人民”“中华人民共和国”“华人”“人民”“人民共和国”“共和”“共和国” 等多个词语,尽可能多地拆分出不同组合的词语,这种模式适用于需要更全面捕捉文本语义的场景,比如搜索引擎的索引构建,能让更多的词语组合参与到搜索匹配中。
- ik_smart:属于粗粒度的分词模式。同样对于 “中华人民共和国”,可能就直接切分成 “中华人民共和国” 这样一个完整的词语,相对而言拆分得较为 “简洁”,比较适合在一些对文本理解不需要特别细致拆分的场景中使用,例如对文档做简单分类等情况。
下载:可以从官方 GitHub 仓库或者相关的软件下载站点获取对应的 IK 分词器版本,要确保其版本和所使用的 ElasticSearch 版本兼容。
安装到 ElasticSearch:将下载好的 IK 分词器压缩包解压后,把整个目录复制到 ElasticSearch 安装目录下的 plugins
文件夹中(如果没有 plugins
文件夹就新建一个)。然后重启 ElasticSearch,它就会自动加载 IK 分词器插件。
三、ElasticSearch工作原理
相关链接:https://mp.weixin.qq.com/s/RUQXIyN95hvi2wM3CyPI9w
1、Term Dictionary、Term index
文档可以通过ik分词器分为多个词条(又称词项,即Term)。词条term会按字典排好序形成Term Dictionary(用于二分查找)。将 Term Dictionary 的部分词条的前缀信息提取出来构建出一个精简的目录树。目录树的节点中存放这些词条在磁盘中的偏移量,也就是指向磁盘中的位置。这个目录树结构,体积小,适合放内存中,它就是所谓的 Term Index。用它可以加速搜索。
这样当需要查找某个词项的时候,只需要搜索 Term Index,就能快速获得词项在 Term Dictionary 中的大概位置。再跳转到 Term Dictionary,通过少量的检索,定位到词条内容。
2、Stored Fields
倒排索引,搜索到的是文档 id,我们还需要拿着这个 id 找到文档内容本身,才能返回给用户。因此还需要有个地方,存放完整的文档内容,它就是 Stored Fields(行式存储)。
3、Docs Values
后端的业务经常需要根据某个字段排序文档,比如按时间排序或商品价格排序。但问题就来了,这些字段散落在文档里。也就是说,我们需要先获取 Stored Fields 里的文档,再提取出内部字段进行排序。也不是说不行,但其实有更高效的做法。我们可以用空间换时间的思路,再构造一个列式存储结构,将散落在各个文档的某个字段,集中存放,当我们想对某个字段排序的时候,就只需要将这些集中存放的字段一次性读取出来,就能做到针对性地进行排序。这个列式存储结构,就是所谓的 Doc Values。
4、Segment
倒排索引用于搜索,Term Index 用于加速搜索,Stored Fields 用于存放文档的原始信息,以及 Doc Values 用于排序和聚合。这些结构共同组成了一个复合文件,也就是所谓的"segment", 它是一个具备完整搜索功能的最小单元。
5、Lucene
多个文档记录可以用来生成一份 segment,如果新增文档时,还是写入到这份 segment,那就得同时更新 segment 内部的多个数据结构,这样并发读写时性能肯定会受影响。所以规定:segment 一旦生成,则不能再被修改。如果还有新的文档要写入,老的segment已经写满,那就生成新的 segment。这样老的 segment 只需要负责读,写则生成新的 segment。同时保证了读和写的性能。
随着数据量增大,segment文件数会变多,这是就可以并发同时读多个 segment。当然segment文件数也不能无限制的变多,程序会不定期的合并多个小的 segment, 也就是段合并(segment merging) 。这就是有名的Lucene,一个单机文本检索库。
6、高性能、高扩展性、高可用
ElasticSearch就是基于单机检索库Lucene构建了一个高性能、高扩展性、高可用的强大的搜索引擎。
①高性能
当多个调用方同时读写同一个 lucene 必然导致争抢计算资源。 所以ES首先将不同类型的数据写入到了不同的Lucene中,这样在读取数据时,根据需要搜索不同的 Index Name,这就大大降低了单个 lucene 的压力。其次,ES还将某些Index Name内数据可能过多的单个lucene 拆成好几份,每份都是一个 shard 分片,每个 shard 分片本质上就是一个独立的 lucene 库。这样就可以将读写操作分摊到多个 分片 中去,大大降低了争抢,提升了系统性能。
②高扩展性
随着 分片 变多,如果 分片 都在同一台机器上的话,就会导致单机 cpu 和内存过高,影响整体系统性能。
在ES中,可以使用更多的机器,将 分片 分散部署在多台机器上,这每一台机器,就是一个 Node。通过增加 Node 缓解机器 cpu 过高带来的性能问题。
③高可用
高可用问题基本都是通过副本解决。ES中也是一样, 通过给 分片 多加几个副本。将 分片 分为 Primary shard 和 Replica shard,也就是主分片和副本分片 。主分片会将数据同步给副本分片,副本分片既可以同时提供读操作,还能在主分片挂了的时候,升级成新的主分片让系统保持正常运行,提高性能的同时,还保证了系统的高可用。
7、ES架构
从架构角度来看,ES给了一套方案,让一个单机系统 lucene 变成一个高性能、高扩展、高可用的分布式系统。
在ES集群中,分为三类node角色。
- 主节点(Master Node), 负责管理集群。
- 协调节点(Coordinate Node),负责存储管理数据。
- 数据节点(Data Node),负责接受客户端请求。
集群规模小的时候,一个 Node 可以同时充当多个角色,随着集群规模变大,可以让一个 Node 一个角色。
ES集群中的节点之间基于类似一致性算法 Raft 的方式,在节点间互相同步数据,让所有 Node 看到的集群数据状态都是一致的。这样,集群内的 Node 就能参与选主过程,还能了解到集群内某个 Node 是不是挂了等信息。
- ES架构 V.S Kafka架构 V.S RocketMQ架构
确实:很多优秀的开源项目架构都是相似的。
架构 | ES | Kafka | RocketMQ |
---|---|---|---|
消息分类 | index name | topic | topic |
数据分片 | Shard | Partition | msgqueue |
节点 | node | broker | broker |
高可用 | 多副本 | 多副本 | master-slave |
数据一致性 | 类似Raft协议 | ISR和ack机制 | Raft协议 |
元数据 | coordinate node | zookeeper | nameserver |
8、ES写入流程
ES 对外提供 http
接口,任何语言的客户端都可以通过 HTTP 接口接入 es,实现对数据的增删改查。
- 当客户端应用发起数据写入请求,请求会先发到集群中协调节点。
- 协调节点根据 hash 路由,判断数据该写入到哪个数据节点里的哪个分片(Shard),找到主分片并写入。分片底层是 lucene,所以最终是将数据写入到 lucene 库里的 segment 内,将数据固化为倒排索引和 Stored Fields 以及 Doc Values 等多种结构。
- 主分片 写入成功后会将数据同步给 副本分片。
- 副本分片 写入完成后,主分片会响应协调节点一个 ACK,意思是写入完成。
- 最后,协调节点响应客户端应用写入完成。
9、ES搜索流程
ES 的搜索流程分为两个阶段:分别是查询阶段(Query Phase)和获取阶段(Fetch Phase)
Query Phase:
- 当客户端应用发起搜索请求,请求会先发到集群中的协调节点。
- 协调节点根据 index name 的信息,可以了解到 index name 被分为了几个 分片,以及这些分片 分散哪个数据节点上,将请求转发到这些数据节点的 分片 上面。
- 搜索请求到达分片后,分片 底层的 lucene 库会并发搜索多个 segment,利用每个 segment 内部的倒排索引获取到对应文档 id,并结合 doc values 获得排序信息。分片将结果聚合返回给协调节点。
- 协调节点对多个分片中拿到的数据进行一次排序聚合,舍弃大部分不需要的数据。
Fetch Phase:
- 协调节点再次拿着文档 id 请求数据节点里的 分片,分片 底层的 lucene 库会从 segment 内的 Stored Fields 中取出完整文档内容,并返回给协调节点。
- 协调节点最终将数据结果返回给客户端。完成整个搜索过程。
四、如何使用ElasticSearch
1、安装部署
通过docker-compose一键部署一套es+kibana容器服务。【第七章节有详细部署步骤】
vim docker-compose-es.yml
version: '3'
services:
#elasticsearch服务
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.13.4
container_name: elasticsearch
user: root
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- TZ=Asia/Shanghai
volumes:
- ./data/elasticsearch/data:/usr/share/elasticsearch/data
restart: always
ports:
- 9200:9200
- 9300:9300
networks:
- looklook_net
#查看elasticsearch数据
kibana:
image: docker.elastic.co/kibana/kibana:7.13.4
container_name: kibana
environment:
- elasticsearch.hosts=http://elasticsearch:9200
- TZ=Asia/Shanghai
restart: always
networks:
- looklook_net
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
looklook_net:
driver: bridge
ipam:
config:
- subnet: 172.16.0.0/16
- 一键启动
docker-compose -f docker-compose-es.yml up -d
2、CRUD操作
有多种方式可以对ES进行CRUD操作,比如:shell命令行操作、kibana界面操作、基于各种开发语言的客户端调用操作等等。这些操作的本质都一样:即通过编写组装DSL语句,基于Restful请求通过http接口调用发送给ES服务端。
① index索引操作
-
查看当前节点的所有 Index
# 如何是shell命令操作 curl -X GET localhost:9200/_cat/indices?v curl -X GET http://localhost:9200/_cat/indices?v curl -X GET "http://localhost:9200/_cat/indices?v" # 等同于在kibana界面操作 GET /_cat/indices?v
可以看出shell命令不大方便,所有基本都推荐使用 Kibana。后面统一使用Kibana编写DSL的方式来演示。
-
创建Index
PUT /mybook { "mappings": { "dynamic": false, "properties": { "id": { "type": "keyword" //使用 keyword 类型,适合精确匹配 }, "title": { "type": "text", // 使用 text 类型,支持全文搜索 "analyzer": "standard" // 使用标准分析器 }, "isbn": { "type": "text" }, "author": { "type": "text" }, "created_at": { "type": "date", // 使用 date 类型,处理日期和时间 "format": "yyyy-MM-dd'T'HH:mm:ss" // 日期格式 }, "publisherName": { "type": "text" } } } }
常见的mapping属性包括:
-
type:字段数据类型,常见的简单类型有:
-
- 字符串:text(可分词的文本)、keyword(精确值,例如:唯一id、品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float
- 布尔:boolean
- 日期:date
- 对象:object
-
index:是否创建索引,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
-
-
删除Index
DELETE /mybook
-
修改Index
索引库一旦创建,无法修改mapping。这是因为虽然倒排索引结构并不复杂,但是一旦索引数据结构有改变(比如改变了分词器),就需要重新创建倒排索引。 因此索引修改只允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
PUT /mybook/_mapping { "properties" : { "description" : { "type" : "text" } } }
-
查询Index
GET /mybook # 等同于 curl -X GET localhost:9200/mybook curl -X GET localhost:9200/mybook?pretty=true
② 文档操作
索引创建完毕之后,可以开始文档的操作。
## 创建索引
PUT /books
## 查看索引
GET /books
## 查看索引映射【这时索引映射是空的】
GET /books/_mapping
## 创建文档
POST /books/_doc
{
"name": "Snow Crash liuwen",
"author": "Neal Stephenson",
"release_date": "1992-06-01",
"page_count": 470
}
## 查看索引映射【索引映射是可以根据文档自动生成的】
GET /books/_mapping
POST /books/_doc
{
"name": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"release_date": "1925-04-10",
"page_count": 180,
"language": "EN"
}
GET /books/_mapping
POST /_bulk
{
"index" : {
"_index" : "books" } }
{
"name": "Revelation Space", "author": "Alastair Reynolds", "release_date": "2000-03-15"