概述
Elasticsearch 是一个开源的分布式搜索和分析引擎,专为速度、扩展和 AI 应用而打造。
作为一个检索平台,它可以实时存储结构化、非结构化和向量数据,
提供快速的混合和向量搜索,支持可观测性与安全分析,并以高性能、高准确性和高相关性实现 AI 驱动的应用。
文档地址:Search use case | Elastic Docs
引入
lucene
Apache Lucene是一个高性能、功能齐全的全文搜索引擎库
地址:Apache Lucene - Welcome to Apache Lucene
优点:
- 超高效的索引和搜索能力
- 强大的查询语法
- 多种语言的分析器支持
- 拼写检查与纠错功能
- 排序与过滤能力
- 可扩展性极强
ElasticsSearch在其基础上重写得来
ELK
Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分。
完整的技术栈包括:
- Elasticsearch:用于数据存储、计算和搜索
- Logstash/Beats:用于数据收集
- Kibana:用于数据可视化
整套技术栈被称为ELK,经常用来做日志收集、系统监控和状态分析等

kibana
Kibana是elastic公司提供的用于操作Elasticsearch的可视化控制台。它的功能非常强大,包括:
- 对Elasticsearch数据的搜索、展示
- 对Elasticsearch数据的统计、聚合,并形成图形化报表、图形
- 对Elasticsearch的集群状态监控
- 它还提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示
由于es通过发送http请求来完成操作,
而http请求的方式、路径、还有请求参数的格式都有严格的规范,
因此使用kibana可以方便操作
原理
正向索引
例如有一张名为tb_goods的表:
| id | title | price |
|---|---|---|
| 1 | 小米手机 | 3499 |
| 2 | 华为手机 | 4999 |
| 3 | 华为小米充电器 | 49 |
| 4 | 小米手环 | 49 |
| ... | ... | ... |
其中的id字段已经创建了索引,由于索引底层采用了B+树结构,因此我们根据id搜索的速度会非常快。但是其他字段例如title,只在叶子节点上存在。
因此要根据title搜索的时候只能遍历树中的每一个叶子节点,判断title数据是否符合要求。
比如用户的SQL语句为:
SQL
select * from tb_goods where title like '%手机%';
此时会对表进行全表扫描,筛选出符合条件的信息。
即:
- 检查到搜索条件为
like '%手机%',需要找到title中包含手机的数据 - 逐条遍历每行数据(每个叶子节点),比如第1次拿到
id为1的数据 - 判断数据中的
title字段值是否符合条件 - 如果符合则放入结果集,不符合则丢弃
- 回到步骤1
故:根据id精确匹配时,可以走索引,查询效率较高。
而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。
因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。
而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。
倒排索引
倒排索引中有两个非常重要的概念:
- 文档(
Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息 - 词条(
Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
- 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建正向索引
此时形成的这张以词条为索引的表,就是倒排索引表,两者对比如下:
正向索引
| id(索引) | title | price |
|---|---|---|
| 1 | 小米手机 | 3499 |
| 2 | 华为手机 | 4999 |
| 3 | 华为小米充电器 | 49 |
| 4 | 小米手环 | 49 |
| ... | ... | ... |
倒排索引
| 词条(索引) | 文档id |
|---|---|
| 小米 | 1,3,4 |
| 手机 | 1,2 |
| 华为 | 2,3 |
| 充电器 | 3 |
| 手环 | 4 |
流程描述:
- 用户输入条件
"华为手机"进行搜索。 - 对用户输入条件分词 ,得到词条:
华为、手机。 - 拿着词条在倒排索引中查找(由于词条有索引 ,查询效率很高),即可得到包含词条的文档id:
1、2、3。 - 拿着文档
id到正向索引中查找具体文档即可(由于id也有索引,查询效率也很高)。

虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
类似添加二级索引并进行回表
分词器
Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,
而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法。
在搜索一句话时
搜索引擎不会直接搜索整句话,而是:
- 先对文档分词
- 再对搜索关键词分词
- 然后匹配词项
ik分词器提供了基本词典
我们可以自己拓展词典
xml
<?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.dict</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">ext_stopwords.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
在config文件中包含着字典位置
我们可以配置扩展字典或停止词,ik分词器也会访问这些文件进行分词
核心概念
文档
elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。
文档数据会被序列化为json格式后存储在elasticsearch中:
JSON
{
"id": 1,
"title": "小米手机",
"price": 3499
}
{
"id": 2,
"title": "华为手机",
"price": 4999
}
{
"id": 3,
"title": "华为小米充电器",
"price": 49
}
{
"id": 4,
"title": "小米手环",
"price": 299
}
字段
原本数据库中的一行数据就是ES中的一个JSON文档;
而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)。
json
{
"id": 3,
"title": "华为小米充电器",
"price": 49
}
索引
将类型相同的文档集中在一起管理,称为索引(Index)。例如:
商品索引
JSON
{
"id": 1,
"title": "小米手机",
"price": 3499
}
{
"id": 2,
"title": "华为手机",
"price": 4999
}
{
"id": 3,
"title": "三星手机",
"price": 3999
}
用户索引
JSON
{
"id": 101,
"name": "张三",
"age": 21
}
{
"id": 102,
"name": "李四",
"age": 24
}
{
"id": 103,
"name": "麻子",
"age": 18
}
订单索引
JSON
{
"id": 10,
"userId": 101,
"goodsId": 1,
"totalFee": 294
}
{
"id": 11,
"userId": 102,
"goodsId": 2,
"totalFee": 328
}
映射
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。
因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
json
{
"id":
"userId":
"goodsId":
"totalFee":
}
属性包括:
type:字段数据类型,常见的简单类型有:- 字符串:
text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址) - 数值:
long、integer、short、byte、double、float、 - 布尔:
boolean - 日期:
date - 对象:
object
- 字符串:
index:是否创建索引,默认为trueanalyzer:使用哪种分词器properties:该字段的子字段
对比mysql
mysql与elasticsearch的概念做一下对比:
| 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实现:
Mysql:擅长事务类型操作,可以确保数据的安全和一致性
-
对查询性能要求较高的搜索需求,使用elasticsearch实现:
Elasticsearch:擅长海量数据的搜索、分析、计算
操作es
索引库操作
mapping映射属性
Mapping是对索引库中文档的约束,常见的Mapping属性包括:
type:字段数据类型,常见的简单类型有:- 字符串:
text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址) - 数值:
long、integer、short、byte、double、float、 - 布尔:
boolean - 日期:
date - 对象:
object
- 字符串:
index:是否创建索引,默认为trueanalyzer:使用哪种分词器properties:该字段的子字段
例:
| 字段名 | 字段类型 | 类型说明 | 是否参与搜索 | 是否参与分词 | 分词器 | |
|---|---|---|---|---|---|---|
| age | integer |
整数 | ------ | |||
| weight | float |
浮点数 | ------ | |||
| isMarried | boolean |
布尔 | ------ | |||
| info | text |
字符串,但需要分词 | IK | |||
keyword |
字符串,但是不分词 | ------ | ||||
| score | float |
只看数组中元素类型 | ------ | |||
| name | firstName | keyword |
字符串,但是不分词 | ------ | ||
| lastName | keyword |
字符串,但是不分词 | ------ |
索引库的crud
由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。
我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。
创建索引库和映射
基本语法:
- 请求方式:
PUT - 请求路径:
/索引库名,可以自定义 - 请求参数:
mapping映射
格式:
JSON
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
查询索引库
基本语法:
- 请求方式:GET
- 请求路径:/索引库名
- 请求参数:无
格式:
Plain
GET /索引库名
修改索引库
只可新增,不可修改
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。
因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。
语法说明:
JSON
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
删除索引库
语法:
- 请求方式:DELETE
- 请求路径:/索引库名
- 请求参数:无
格式:
Plain
DELETE /索引库名
总结
- 创建索引库:PUT /索引库名
- 查询索引库:GET /索引库名
- 删除索引库:DELETE /索引库名
- 修改索引库,添加字段:PUT /索引库名/_mapping
例
增
json
PUT /user
{
"mappings": {
"properties": {
"username":{
"type": "text",
"analyzer": "ik_smart"
},
"realname":{
"type": "object",
"properties": {
"firstname":{"type":"keyword"},
"lastname":{"type":"keyword"}
}
},
"age":{
"type":"byte"
},
"phonenumber":{
"type": "keyword"
, "index": false
},
"isMarried":{
"type": "boolean"
}
}
}
}
查
json
GET /user
改
json
PUT /user/_mapping
{
"properties":{
"email":{
"type":"keyword"
}
}
}
删
json
DELETE /user
文档操作
新增
json
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
}
例:
json
POST /user/_doc/1
{
"username":"jack",
"realname":{
"firstname":"jason",
"lastname":"mike"
},
"age":21,
"phonenumber":"123",
"isMarried":true
}
查询
JSON
GET /{索引库名称}/_doc/{id}
例:
json
GET /user/_doc/1
删除
json、
DELETE /{索引库名}/_doc/id值
例:
json
DELETE /user/_doc/1
修改
全量修改
全量修改是覆盖原来的文档,其本质是两步操作:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
JSON
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
例:
json
Put /user/_doc/1
{
"username":"jack",
"realname":{
"firstname":"jason",
"lastname":"huang"
},
"age":21,
"phonenumber":"123",
"isMarried":true
}
局部修改
修改文档中的部分字段
JSON
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
例:
json
POST /user/_update/1
{
"doc":{"isMarried":false}
}
批处理
批处理采用POST请求,基本语法如下:
Java
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"} }
其中:
create代表创建文档操作(文档已存在会报错)-
_index:指定索引库名_id:指定要操作的文档 id{ "field1" : "value1" }:则是要新增的文档内容
index代表新增或覆盖操作(存在则覆盖,不存在则新增)-
_index:指定索引库名_id:指定要操作的文档 id{ "field1" : "value1" }:则是要新增或覆盖的文档内容
delete代表删除操作-
_index:指定索引库名_id指定要操作的文档id
update代表更新操作-
_index:指定索引库名_id指定要操作的文档id{ "doc" : {"field2" : "value2"} }:要更新的文档字段
例:
JSON
#批量增
POST /_bulk
{"index": {"_index":"user", "_id": "2"}}
{"username":"jack","realname":{"firstname":"jason","lastname":"juddy"},"age":21,"phonenumber":"123","isMarried":true}
{"index": {"_index":"user", "_id": "3"}}
{"username":"jack","realname":{"firstname":"jason","lastname":"jenny"},"age":21,"phonenumber":"123","isMarried":true}
#批量删
POST /_bulk
{"delete":{"_index":"user", "_id": "2"}}
{"delete":{"_index":"user", "_id": "3"}}