elasticsearch 是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。是整个 ELK 技术栈的核心,负责存储、搜索、分析数据。
elasticsearch 结合 kibana、Logstash、Beats,也就是 elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等。
一 、核心概念
1.倒排索引
对于传统的数据库是采用正向索引,一般以 id 创建索引,查询数据的效率比较差。而Elasticsearch 采用倒排索引。
倒排索引:对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档 id,而后获取到文档。
文档(document):每条数据就是一个文档。
词条(term):文档按照语义分成的词语。
elasticsearch 是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为 json 格式后存储在 elasticsearch 中。
索引(index):相同类型的文档的集合。
映射(mapping):索引中文档的字段约束信息,类似表的结构约束。

2.分词器
es 在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。
这里提供了 ik 分词器,ik 分词器支持中文的处理。
ik 分词器有两种模式:
ik_smart : 最少切分粗粒度占用内存少 搜索到的概率比较小。
ik_max_word : 最细切分细粒度 占用内存多 搜索到的概率大。
ik 分词器同时也提供了拓展词库的配置,只需要修改一个 ik 分词器目录中的 config 目录中的IkAnalyzer.cfg.xml 文件:
XML
<?xml version="1.O" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!-- 用户可以在这里配置自己的扩展字典 *** 添加扩展词典 -->
<entry key="ext_dbct">ext.dic</entry>
<!-- 用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典 -->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
这里需要创建 ext.dic 文件 和 stopword.dic 文件;
保存完配置后重启 es 。
二、DSL 语句
Elasticsearch 通过 DSL 语句来进行一系列的操作。
常用的操作有对索引库和文档的操作以及对数据的相关操作。
1. 索引库操作
mapping 属性:mapping 是对索引库中文档的约束,常见的 mapping 属性包括:
(1) type:字段数据类型,常见的简单类型有:
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
数值:long、integer、short、byte、double、float
布尔:boolean
日期:date
对象:object
(2) index:是否创建索引,默认为 true
(3) analyzer:使用哪种分词器
(4) properties:该字段的子字段
创建索引库操作:
css
PUT /索引库名称
{
"mappings":{
"properties":{
"字段名1":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": false
},
"字段名3":{
"properties":{
"子字段":{
"type": "keyword"
}
}
},
// ... 略
}}}
索引库其他的操作:
css
# 查看索引库
GET /索引库名
# 删除索引库
DELETE /索引库名
# es默认不支持索引库的修改
# 索引库和mapping一旦创建无法修改,但是可以添加新的字段
PUT /索引库名/_mapping
{
"properties":{
"新字段名":{
"type": "integer"
}
}}
2.文档操作
新增文档:
css
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1":"值3",
"子属性2": "值4"
},
// ..
}
其他文档操作:
css
# 查询文档
GET /"索引库名"/_doc/"文档id"
# 删除文档
DELETE /"索引库名"/_doc/"文档id"
修改文档:
css
# 方式一:全量修改,会删除旧文档,添加新文档
# 用 PUT 请求方式
PUT /"索引库名"/_doc/"文档id"
{
"字段1": "值1",
"字段2": "值2",
//...略
}
# 方式二:增量修改,修改指定字段值
# 用 POST 请求方式
POST /"索引库名"/_update/"文档id"
{
"doc":{
"字段名": "新的值",
// ...
}
}
3.数据操作
DSL 查询方式:
Elasticsearch 提供基于 JSON 的 DSL(Domain Specific Language) 来定义查询。常见的查询类型包括:
**1. 查询所有:**查询出所有的数据,一般测试用。例如:match_all
**2. 全文检索(full text)查询:**利用分词器对用户输入的内容分词,然后去倒排索引库中匹配。例如:match_query、multi_match_query
**3. 精确查询:**根据精确词条值查找数据,一般是查找 keyword、数值、日期、boolean 等类型字段。例如:ids、range、term
**4.地理(geo)查询:**根据经纬度查询。例如:geo_distance、geo_bounding_box
**5.复合(compound)查询:**复合查询可以将上述各种条件组合起来,合并查询条件。例如:
bool、function_score
查询的基本语法:
css
# 基本语法
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
# 查询所有
GET /indexName/_search
{
"query": {
"match_all": {
}
}
}
全文检索查询
推荐使用 match 查询,利用 copy_to 将多个字段整合到一个字段中。
css
# match查询:会对用户输入的内容进行分词,然后去倒排索引库检索
GET /indexName/_search
{
"query": {
"match": {
"FIELD": "TEXT" # FIELD为查询字段 TEXT为查询内容
}
}
}
// 示例:查询 title 字段中包含 "Elasticsearch 入门" 的文档
GET /goods/_search
{
"query": {
"match": {
"title": "Elasticsearch 入门"
}
}
}
# multi_match查询:允许同时查询多个字段
GET /indexName/_search
{
"query": {
"multi_match": {
"query": "TEXT",
"fields": ["FIELD1", "FIELD2"]
}
}
}
// 示例:在 title 和 content 字段中同时搜索 "Elasticsearch"
GET /goods/_search
{
"query": {
"multi_match": {
"query": "Elasticsearch",
"fields": ["title", "content"]
}
}
}
精确查询
一般是查找 keyword、数值、日期、bool,不会对搜索条件分词。
term:根据词条精确值查询。 range:根据值的范围查询。
css
# term查询
GET /indexName/_search
{
"query": {
"term": {
"FIELD": { # 所要查询的字段
"value": "VALUE"
}
}
}
}
// 示例:查询 status 字段值为 "ON_SALE" 的商品
GET /goods/_search
{
"query": {
"term": {
"status": {
"value": "ON_SALE"
}
}
}
}
# range 查询 gte是>= lte是<= gt是> lt是<
GET /indexName/_search
{
"query": {
"range": {
"FIELD": {
"gte": 10,
"lte": 20
}
}
}
}
// 示例:查询价格在 10 到 20 之间(含两端)的商品
GET /goods/_search
{
"query": {
"range": {
"price": {
"gte": 10,
"lte": 20
}
}
}
}
地理查询
**geo_bounding_box:**查询 geo_point 值落在某个矩形范围的所有文档
**geo_distance:**查询到指定中心点小于某个距离值的所有文档
css
# geo_bounding_box查询
GET /indexName/_search
{
"query": {
"geo_bounding_box": {
"FIELD": { # 此查询的字段
"top_left": { # 矩形左上角的纬度经度
"lat": 31.1,
"lon": 121.5
},
"bottom_right": { # 矩形右下角的纬度经度
"lat": 30.9,
"lon": 121.7
}
}
}
}
}
# 查询距离中心点(31.21,121.5)15公里内的店铺
GET /shop/_search
{
"query": {
"geo_distance": {
"distance": "15km",
"location": "31.21,121.5" # 指定纬度,经度
}
}
}
基于算分的查询:
相关性算分
当我们利用 match 查询时,文档结果会根据与搜索词条的关联度打分(score),返回结果时按照分值降序排列。

TF-IDF:在elasticsearch5.0之前,会随着词频增加而越来越大

BM25:在elasticsearch5.0之后,会随着词频增加而增大,但增长曲线会趋于水平

FunctionScoreQuery(复合查询)
使用 FunctionScoreQuery,可以修改文档的相关性算分(query score),根据新得到的算分排序。

BooleanQuery(复合查询)
布尔查询是一个或多个查询子句得组合。子查询的组合方式有:
注意:参与算分的条件越多,查询的性能就会越差。
**must:**必须匹配每个子查询,类似"与"
**should:**选择性匹配子查询,类似"或"
**must_not:**必须不匹配,不参与算分,类似"非"
**filter:**必须匹配,不参与算分
css
# 查询含义:查找上海地区、价格高于 500 元、评分≥45 分的酒店,
# 优先展示品牌为 "皇冠假日" 或 "华美达" 的结果(匹配这两个品牌的酒店得分更高,排在前面)
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{"term": {"city": "上海" }}
],
"should": [
{"term": {"brand": "皇冠假日" }},
{"term": {"brand": "华美达" }}
],
"must_not": [
{ "range": { "price": { "lte": 500 } }}
],
"filter": [
{ "range": {"score": { "gte": 45 } }}
]
}
}
}
搜索结果处理(排序、分页、高亮)
排序语法
elasticsearch 支持对搜索结果排序,默认是根据相关度算分(score)来排序。可以排序字段类型有:keyword 类型(字符串,字符顺序排序)、数值类型、地理坐标类型、日期类型等。
css
# 数字、日期、keyword
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [ # sort里可对多个字段排序
{
"score": "desc" # 排序字段和排序方式ASC、DESC
},
{
"price" : "asc"
}
]
}
# 地理坐标
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"FIELD": "纬度, 经度",
"order": "asc",
"unit": "km"
}
}
]
}
分页语法
elasticsearch 默认情况下只返回 Top 10 的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch 中通过修改 from、size 参数来控制要返回的分页结果:
css
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 990, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{
"price": "asc"
}
]
}
高亮显示
**高亮:**就是在搜索结果中把搜索关键字突出显示。
高亮原理:(ES 中实现的)
1)将搜索结果中的关键字用标签标记出来
2)在页面中给标签添加 css 样式
css
# 高亮查询,默认情况下,ES搜素字段必须与高亮字段一致
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" # 需指定为关键字
}
},
"highlight": {
"fields": {
"FIELD": { # 指定高亮字段
"pre_tags": "<em>", # 用来标记高亮字段的前置标签
"post_tags": "</em>", # 用来标记高亮字段的后置标签
"require_field_match" : "false" # 该字段用于 需不需要字段匹配(即高亮字段与搜索字段)
} # 比如搜索all 高亮字段为name 此时便可指定为false
}
}
}
Elasticsearch 的 DSL 语句其实就类比于 MySQL 的 SQL 语句嘛,也是易于理解的。而 SQL 语句可以写在 Java 中,那么 DSL 语句是否也可以写到 Java 中呢?