ElasticSearch:

认识和安装:
Elasticsearch 是由 elastic 公司开发的一套搜索引擎技术,它是 elastic 技术栈中的一部分;完整的技术栈包括:
-
Elasticsearch:用于数据存储、计算和搜索
-
Logstash/Beats:用于数据收集
-
Kibana:用于数据可视化
整套技术栈被称为 ELK,经常用来做日志收集、系统监控和状态分析等等:

整套技术栈的核心就是用来存储 、搜索 、计算的 Elasticsearch,因此接下来学习的核心也是Elasticsearch
要安装的内容:
-
elasticsearch:存储、搜索和运算
-
kibana:图形化展示
elasticsearch 作为核心组件,主要承担数据存储、检索与数据分析的核心能力;它对外统一提供 Restful 风格接口,所有数据读写、聚合分析等操作,均可通过 HTTP 请求实现;但接口请求方式、路由地址及请求体参数格式约束严格,日常开发与调试记忆成本较高,因此搭配 kibana 可视化工具使用,可直观便捷地调用接口、执行查询语句,大幅简化运维与开发调试工作
Kibana 是 elastic 公司提供的用于操作 Elasticsearch 的可视化控制台;它的功能非常强大,包括:
-
对 Elasticsearch 数据的搜索、展示
-
对 Elasticsearch 数据的统计、聚合,并形成图形化报表、图形
-
对 Elasticsearch 的集群状态监控
-
它还提供了一个开发控制台(DevTools),在其中对 Elasticsearch 的 Restful 的 API 接口提供了语法提示
安装 Elasticsearch:
通过下面的 Docker 命令即可安装单机版本的 elasticsearch:
bash
docker network create es-net
bash
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
或者直接导入镜像 tar 包:
bash
docker load -i /root/es.tar
安装完成后,访问 9200 端口,即可看到响应的 Elasticsearch 服务的基本信息:

安装 Kibana:
通过下面的 Docker 命令,即可部署 Kibana:
bash
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
或者直接导入镜像 tar 包:
bash
docker load -i /root/kibana.tar

安装完成后,访问 5601 端口,即可看到控制台页面:

选择 Explore on my own 之后,进入主页面:

然后选中 Dev tools,进入开发工具页面:


倒排索引:
Elasticsearch 之所以有如此高性能的搜索表现,正是得益于底层的倒排索引技术,而倒排索引的概念是基于 MySQL 这样的正向索引而言的
正向索引:
例如有一张名为 tb_goods 的表:
| id | title | price |
|---|---|---|
| 1 | 苹果手机 | 9999 |
| 2 | 华为手机 | 7999 |
| 3 | 华为苹果充电器 | 49 |
| 4 | 苹果手环 | 1299 |
| ... | ... | ... |
其中的 id 字段已经创建了索引,由于索引底层采用了 B+ 树结构,因此根据 id 搜索的速度会非常快;但是其他字段例如 title,只在叶子节点上存在,因此要根据 title 搜索的时候只能遍历树中的每一个叶子节点,判断 title 数据是否符合要求
根据 id 精确匹配时,可以走索引,查询效率较高;而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差
因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配;而倒排索引恰好解决的就是根据部分词条模糊匹配的问题
倒排索引:
倒排索引中有两个非常重要的概念:
-
文档(Document):用来搜索的数据,其中的每一条数据就是一个文档;例如一个网页、一个商品信息
-
词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条;例如:只因你太美,就可以分为:只因、你、你太美、太美、美这样的几个词条
创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
-
将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
-
创建表,每行数据包括词条、词条所在文档 id、位置等信息
-
因为词条唯一性,可以给词条创建正向索引
此时形成的这张以词条为索引的表,就是倒排索引表:
| 词条(索引) | 文档id |
|---|---|
| 苹果 | 1,3,4 |
| 手机 | 1,2 |
| 华为 | 2,3 |
| 充电器 | 3 |
| 手环 | 4 |
倒排索引的搜索流程如下(以搜索 "华为手机" 为例):
流程描述:
(1)用户输入条件 "华为手机" 进行搜索。
(2)对用户输入条件分词,得到词条:华为、手机。
(3)拿着词条在倒排索引中查找(由于词条有索引,查询效率很高),即可得到包含词条的文档id:1、2、3
(4)拿着文档 id 到正向索引中查找具体文档即可(由于 id 也有索引,查询效率也很高)
正向和倒排:
-
正向索引是最传统的,根据id索引的方式;但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
-
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的 id,然后根据 id 获取文档;是根据词条找文档的过程
正向索引:
-
优点:可以给多个字段创建索引;根据索引字段搜索、排序速度非常快
-
缺点:根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描
倒排索引:
-
优点:根据词条搜索、模糊搜索时,速度非常快
-
缺点:只能给词条创建索引,而不是字段;无法根据字段做排序
基础概念:
文档和字段:
Elasticsearch 是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息;文档数据会被序列化为 json 格式后存储在 Elasticsearch 中:
bash
{
"id": 1,
"title": "苹果手机",
"price": 9999
}
{
"id": 2,
"title": "华为手机",
"price": 7999
}
{
"id": 3,
"title": "华为苹果充电器",
"price": 49
}
{
"id": 4,
"title": "苹果手环",
"price": 1299
}
因此,原本数据库中的一行数据就是 Elasticsearch 中的一个 json 文档;而数据库中每行数据都包含很多列,这些列就转换为 JSON 文档中的字段(Field)
索引和映射:
随着业务发展,需要在 Elasticsearch 中存储的文档也会越来越多,比如有商品的文档、用户的文档、订单文档等等
所有文档都散乱存放显然非常混乱,也不方便管理;因此,要将类型相同的文档集中在一起管理,称为索引(Index);例如:
商品索引:
bash
{
"id": 1,
"title": "苹果手机",
"price": 9999
}
{
"id": 2,
"title": "华为手机",
"price": 7999
}
{
"id": 3,
"title": "三星手机",
"price": 3999
}
用户索引:
bash
{
"id": 101,
"name": "张三",
"age": 21
}
{
"id": 102,
"name": "李四",
"age": 24
}
{
"id": 103,
"name": "王五",
"age": 18
}
订单索引:
bash
{
"id": 10,
"userId": 101,
"goodsId": 1,
"totalFee": 294
}
{
"id": 11,
"userId": 102,
"goodsId": 2,
"totalFee": 328
}
每个类型的文档,可以组织在一起,称为对应的索引;
因此,可以把索引当做是数据库中的表
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束
对比:
| MySQL | Elasticsearch | 说明 |
|---|---|---|
| Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
| Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是 json 格式 |
| Column | Field | 字段(Field),就是 json 文档中的字段,类似数据库中的列(Column) |
| Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束;类似数据库的表结构(Schema) |
| SQL | DSL | DSL 是 elasticsearch 提供的 json 风格的请求语句,用来操作 elasticsearch,实现 CRUD |
-
Mysql:擅长事务类型操作,可以确保数据的安全和一致性
-
Elasticsearch:擅长海量数据的搜索、分析、计算
IK 分词器:
Elasticsearch 的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,而分词则需要高效、精准的分词算法,IK 分词器就是这样一个中文分词算法
安装:
bash
docker exec -it es ./bin/elasticsearch-plugin install -b https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip
再重启容器:
bash
docker restart es

使用:
IK 分词器包含两种模式:
-
ik_smart:智能语义切分
-
ik_max_word:最细粒度切分
在 Kibana 的 DevTools 上来测试分词器,先测试 Elasticsearch 官方提供的标准分词器:
bash
POST /_analyze
{
"analyzer": "standard",
"text": "只因你太美"
}
bash
{
"tokens" : [
{
"token" : "只",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "因",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "你",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "太",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "美",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
}
]
}
可以看到标准分词器只能 1 字 1 词条,无法正确对中文做分词
再测试 IK 分词器:

拓展词典:
IK 分词器提供了扩展词汇的功能
进入 ES 容器:
bash
docker exec -it es bash
创建 IK 配置目录:
bash
mkdir -p /usr/share/elasticsearch/config/analysis-ik
创建扩展词典:
bash
printf "%s\n" "只因你太美" > /usr/share/elasticsearch/config/analysis-ik/ext.dic
创建/修改 IK 配置文件:
bash
cat > /usr/share/elasticsearch/config/analysis-ik/IKAnalyzer.cfg.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<entry key="ext_dict">ext.dic</entry>
<entry key="ext_stopwords"></entry>
</properties>
EOF
退出容器:
bash
exit
重启 ES:
bash
docker restart es

索引库操作:
Index 类似数据库表,Mapping 映射类似表的结构;要向 es 中存储数据,必须先创建 Index 和Mapping
Mapping 映射属性:
Mapping 是对索引库中文档的约束,常见的 Mapping 属性包括:
-
type:字段数据类型,常见的简单类型有:
-
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip 地址)
-
数值:long、integer、short、byte、double、float
-
布尔:boolean
-
日期:date
-
对象:object
-
-
index:是否创建索引,默认为 true
-
analyzer 使用哪种分词器
-
properties:该字段的子字段
例如:
bash
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "黑子程序员",
"email": "mz@zhu.cn",
"score": [91.0, 27.6, 78.6],
"name": {
"firstName": "木",
"lastName": "朱"
}
}
对应的每个字段映射(Mapping):
|----------------|---------|-----------|--------|--------|--------|
| 字段名 | 字段类型 | 类型说明 | 是否参与搜索 | 是否参与分词 | 分词器 |
| age | integer | 整数 | 是 | 否 | ------ |
| weight | float | 浮点数 | 是 | 否 | ------ |
| isMarried | boolean | 布尔 | 是 | 否 | ------ |
| info | text | 字符串,但需要分词 | 是 | 是 | IK |
| email | keyword | 字符串,但是不分词 | 否 | 否 | ------ |
| score | float | 只看数组中元素类型 | 是 | 否 | ------ |
| name.firstName | keyword | 字符串,但是不分词 | 是 | 否 | ------ |
| name.lastName | keyword | 字符串,但是不分词 | 是 | 否 | ------ |
增删改查:
由于 Elasticsearch 采用的是 Restful 风格的 API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用 JSON 风格
直接基于 Kibana 的 DevTools 来编写请求做测试
创建索引库和映射:
基本语法:
-
请求方式:PUT
-
请求路径:/索引库名,可以自定义
-
请求参数:mapping 映射
格式:
bash
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
示例:
bash
# PUT /heizi
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": "false"
},
"name":{
"properties": {
"firstName": {
"type": "keyword"
}
}
}
}
}
}
查询索引库:
基本语法:
-
请求方式:GET
-
请求路径:/索引库名
-
请求参数:无
格式:
bash
GET /索引库名
示例:
bash
GET /heizi
修改索引库:
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引;因此索引库一旦创建,无法修改 mapping
虽然无法修改 mapping 中已有的字段,但是却允许添加新的字段到 mapping 中,因为不会对倒排索引产生影响;因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性
语法说明:
bash
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
示例:
bash
PUT /heizi/_mapping
{
"properties": {
"age":{
"type": "integer"
}
}
}
删除索引库:
语法:
-
请求方式:DELETE
-
请求路径:/索引库名
-
请求参数:无
格式:
bash
DELETE /索引库名
示例:
bash
DELETE /heizi
文档操作:
Elasticsearch 中的数据其实就是 json 风格的文档
新增文档:
语法:
bash
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
}
示例:
bash
POST /heizi/_doc/1
{
"info": "黑子程序员",
"email": "mz@zhu.cn",
"name": {
"firstName": "木",
"lastName": "朱"
}
}
查询文档:
语法:
bash
GET /{索引库名称}/_doc/{id}
示例:
bash
GET /heizi/_doc/1
删除文档:
语法:
bash
DELETE /{索引库名}/_doc/id值
示例:
bash
DELETE /heizi/_doc/1
修改文档:
修改有两种方式:
-
全量修改:直接覆盖原来的文档
-
局部修改:修改文档中的部分字段
全量修改:
全量修改是覆盖原来的文档,其本质是两步操作:
-
根据指定的 id 删除文档
-
新增一个相同 id 的文档
注意:如果根据 id 删除时,id 不存在,第二步的新增也会执行,也就从修改变成了新增操作了
语法:
bash
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
示例:
bash
PUT /heizi/_doc/1
{
"info": "黑子程序员",
"email": "mz@zhu.cn",
"name": {
"firstName": "木",
"lastName": "朱"
}
}
局部修改:
局部修改是只修改指定 id 匹配的文档中的部分字段
语法:
bash
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
示例:
bash
POST /heizi/_update/1
{
"doc": {
"email": "zhu@mz.cn"
}
}
批处理:
批处理采用 POST 请求,基本语法如下:
bash
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
其中:
-
index 代表新增操作
-
_index:指定索引库名
-
_id 指定要操作的文档id
-
{ "field1" : "value1" }:则是要新增的文档内容
-
-
delete 代表删除操作
-
_index:指定索引库名
-
_id 指定要操作的文档id
-
-
update 代表更新操作
-
_index:索引库名
-
_id 要操作的文档id
-
{ "doc" : {"field2" : "value2"} }:要更新的文档字段
-