“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情”
概述
ElasticSearch我们一般简称为es,在这里先贴出来官网地址: www.elastic.co/cn/
它是基于Lucene的分布式的Restful风格的搜索、存储和分析引擎 ,java开发。
它具有高性能、高可用、可伸缩、易维护的特点。体现在可以处理PB级数据,具有很高的容错性和比较全的文档,支持横向和纵向扩展。
应用领域
- 搜索引擎(全文检索、高亮、搜索推荐等)
- 用户行为日志(用户点击消费、浏览收藏、点赞评论等)
- BI系统,数据分析
- GItHub
- ELK(日志搜索)
……
核心概念
倒排索引
英文 inverted index
这里与正序和倒序排列这个作以区分,倒排索引指的是:可以简单理解为我们平时根据id去查内容的,倒排索引恰好相反,是根据内容去查id的。至于怎么去查就涉及到在将内容存进es中时所做的处理了,比如通过分词器分词作关联等。
倒排索引的概念是基于MySQL这样的正向索引而言的,倒排索引中有两个非常重要的概念:
- 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
- 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
创建倒排索引是对正向索引的一种特殊处理,流程如下:
- 将每一个文档的数据利用算法分词,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
如图:
倒排索引的搜索流程如下(以搜索"胡八一行政桌"为例):
1)用户输入条件"胡八一行政桌"进行搜索。
2)对用户输入内容分词,得到词条:胡八一、行政桌。
3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2。
4)拿着文档id到正向索引中查找具体文档。
虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
正排索引
英文 forward index
可以简单理解为通过id去查内容,例如常见的mysql中的索引。
cluster
集群
集群概述
es集群相关核心名词有:
- cluster_name:集群名称
- status:健康值状态 green 、yellow 、red
- timed_out:是否超时
- number_of_nodes:节点数量
- number_of_data_nodes:数据节点数量
- active_primary_shards:活跃的主分片数
- active_shards:活跃的总分片数
- relocating_shards:迁移中的分片数
- initializing_shards:初始化中的分片数
- unassigned_shards:未分配的分片数
- active_shards_percent_as_number:活跃分片的百分比
集群搭建
- 下载ElasticSearch的安装包
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.3-linux-x86_64.tar.gz
//解压文件
tar -zxvf elasticsearch-7.17.3-linux-x86_64.tar.gz -C /usr/local
- 修改 jvm.options 配置
-Xms1024m -Xmx1024m
- 配置 elasticsearch.yml 文件
//集群名称,三台集群,要配置相同的集群名称
cluster.name: my-application
//节点名称
node.name: node1
//是否有资格被选举为master,ES默认集群中第一台机器为主节点
node.master: true
//是否存储数据
node.data: true
//最⼤集群节点数,为了避免脑裂,集群节点数最少为 半数+1
node.max_local_storage_nodes: 3
//数据目录
path.data: /usr/local/node1/data
//log目录
path.logs: /usr/local/node1/logs
//修改 network.host 为 0.0.0.0,表示对外开放,如对特定ip开放则改为指定ip
network.host: 0.0.0.0
//设置对外服务http端口,默认为9200
http.port: 9200
//内部节点之间沟通端⼝
transport.tcp.port: 9300
//写⼊候选主节点的设备地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9300", "localhost:9301", "localhost:9302"]
//初始化⼀个新的集群时需要此配置来选举master
cluster.initial_master_nodes: ["node1", "node2","node3"]
//设置集群中N个节点启动时进行数据恢复,默认为1
gateway.recover_after_nodes: 3
//下面的两个配置在安装elasticsearch-head的时候会用到
//开启跨域访问支持,默认为false
http.cors.enabled: true
//跨域访问允许的域名地址,(允许所有域名)以上使用正则
http.cors.allow-origin: "*"
//关闭xpack
xpack.security.enabled: false
- 复制当前es构建其他数据节点
//拷贝三份文件
cp -r /usr/local/elasticsearch-7.17.3/ /usr/local/node1
cp -r /usr/local/elasticsearch-7.17.3/ /usr/local/node2
cp -r /usr/local/elasticsearch-7.17.3/ /usr/local/node3
- 修改node2和node3两个数据节点的配置文件,先修改node2数据节点
//集群名称,三台集群,要配置相同的集群名称
cluster.name: my-application
//节点名称
node.name: node2
//是否有资格被选举为master,ES默认集群中第一台机器为主节点
node.master: true
//是否存储数据
node.data: true
//最⼤集群节点数,为了避免脑裂,集群节点数最少为 半数+1
node.max_local_storage_nodes: 3
//数据目录
path.data: /usr/local/node2/data
//log目录
path.logs: /usr/local/node2/logs
//修改 network.host 为 0.0.0.0,表示对外开放,如对特定ip开放则改为指定ip
network.host: 0.0.0.0
//设置对外服务http端口,默认为9200,可更改端口不为9200
http.port: 9201
//内部节点之间沟通端⼝
transport.tcp.port: 9301
//写⼊候选主节点的设备地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9300", "localhost:9301", "localhost:9302"]
//初始化⼀个新的集群时需要此配置来选举master
cluster.initial_master_nodes: ["node1", "node2","node3"]
//设置集群中N个节点启动时进行数据恢复,默认为1
gateway.recover_after_nodes: 3
//下面的两个配置在安装elasticsearch-head的时候会用到
//开启跨域访问支持,默认为false
http.cors.enabled: true
//跨域访问允许的域名地址,(允许所有域名)以上使用正则
http.cors.allow-origin: "*"
//关闭xpack
xpack.security.enabled: false
- 再先修改node3数据节点:
//集群名称,三台集群,要配置相同的集群名称
cluster.name: my-application
//节点名称
node.name: node3
//是否有资格被选举为master,ES默认集群中第一台机器为主节点
node.master: true
//是否存储数据
node.data: true
//最⼤集群节点数,为了避免脑裂,集群节点数最少为 半数+1
node.max_local_storage_nodes: 3
//数据目录
path.data: /usr/local/node3/data
//log目录
path.logs: /usr/local/node3/logs
//修改 network.host 为 0.0.0.0,表示对外开放,如对特定ip开放则改为指定ip
network.host: 0.0.0.0
//设置对外服务http端口,默认为9200,可更改端口不为9200
http.port: 9202
//内部节点之间沟通端⼝
transport.tcp.port: 9302
//写⼊候选主节点的设备地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9300", "localhost:9301", "localhost:9302"]
//初始化⼀个新的集群时需要此配置来选举master
cluster.initial_master_nodes: ["node1", "node2","node3"]
//设置集群中N个节点启动时进行数据恢复,默认为1
gateway.recover_after_nodes: 3
//下面的两个配置在安装elasticsearch-head的时候会用到
//开启跨域访问支持,默认为false
http.cors.enabled: true
//跨域访问允许的域名地址,(允许所有域名)以上使用正则
http.cors.allow-origin: "*"
//关闭xpack
xpack.security.enabled: false
- 创建ES存储数据和log目录,根据之前每个节点的配置文件内配置path进行创建或修改
mkdir -p /usr/local/node1/data
mkdir -p /usr/local/node1/logs
mkdir -p /usr/local/node2/data
mkdir -p /usr/local/node2/logs
mkdir -p /usr/local/node3/data
mkdir -p /usr/local/node3/logs
- 创建普通用户,由于ES需要使用非root用户来启动,所以下面创建一个普通用户
//创建elasticsearch用户组
groupadd elasticsearch
//创建新用户elasticsearch ,设置用户组为elasticsearch ,密码123456
useradd elasticsearch -g elasticsearch -p 123456
//授权,更改/elasticsearch-7.17.3文件夹所属用户及用户组为elasticsearch:elasticsearch
chown -R elasticsearch:elasticsearch /usr/local/elasticsearch-7.17.3/
//切换用户elasticsearch
su elasticsearch
- 给节点文件授权
chown -R elasticsearch:elasticsearch /usr/local/node1
chown -R elasticsearch:elasticsearch /usr/local/node2
chown -R elasticsearch:elasticsearch /usr/local/node3
由es的集群搭建可以看出,es自己是支持集群的,不用再借助第三方技术来支持es的集群
node
节点
一个节点≠一台服务器主机
es的节点类型:
Master
主节点,每个集群都有且只有一个
尽量避免Master节点又是数据节点: node.data = true
Voting
投票节点 ode.voting_only = true(仅投票节点,即使配置了data.master = true,也不会参选, 但是仍然可以作为数据节点)。
Coordinating
协调节点每一个节点都隐式的是一个协调节点,如果同时设置了data.master = false和data.data=false,那么此节点将成为仅协调节点。
Master-eligible node
候选节点可以通过选举成为Master的节点
Data node
数据节点主要负责数据存储和查询服务
field
数据字段
- 字符串类型:
- text:一把被用于全文检索。 将当前Field进行分词。
- keyword:当前Field不会被分词。
-
数值类型:
- long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
- integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
- short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节
- byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节
- double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节
- float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节
- half_float:精度比float小一半。
- scaled_float:根据一个long和scaled来表达一个浮点型,long-345,scaled-100 -> 3.45
-
时间类型:
- date类型,针对时间类型指定具体的格式
-
布尔类型:
- boolean类型,表达true和false
-
二进制类型:
- binary类型暂时支持Base64 encode string
-
范围类型:
- long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
- integer_range:同上
- double_range:同上
- float_range:同上
- date_range:同上
- ip_range:同上
-
经纬度类型:
- geo_point:用来存储经纬度的
-
ip类型:
- ip:可以存储IPV4或者IPV6
document
文档
document的核心元数据
document的核心元数据有三个:_index、_type、_id
_index元数据
- 代表一个document存放在哪个index中
- 类似的数据放在一个索引中,非类似的数据放在不同的索引中:product index(包含了所有的商品)、sales index(包含了所有的商品销售数据)、inventory index(包含了所有库存的相关数据)
- index中包含了很多类似的document: 类似是什么意思呢,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了
- 索引名称必须是小写,不能用下划线开头,不包含逗号
_type元数据(7.x以后去掉了)
- 代表document属于index的哪个类别
- 一个索引通常会划分为多个type,逻辑上对index有些许不同的几类数据进行分类
- type名称可以是大写或者小写,但是同时不能用下划线开头,不能包含逗号
_id元数据
- 代表document的唯一标识,与_index和_type一起可以起唯一标识和定位一个document
- 我们可以手动指定document的id,也可以不指定,由es自动为我们创建一个id
type (7.x以后去掉了)
类型
index
索引
类似关系型数据库的数据库
mapping
mapping就是ES数据字段field的type元数据,ES在创建索引的时候,dynamic mapping会自动为不同的数据指定相应mapping,mapping中包含了字段的类型、搜索方式(exact value或者full text)、分词器等 mapping是对索引库中文档的约束。
索引库就类似数据库表,mapping映射就类似表的结构。
我们要向es中存储数据,必须先创建“库”和“表”。
-
type:字段数据类型,常见的简单类型有:
-
字符串:text(可分词的文本)、keyword(精确值,例如:产品、来源、ip地址)
keyword类型只能整体搜索,不支持搜索部分内容
-
数值:long、integer、short、byte、double、float、
-
布尔:boolean
-
日期:date
-
对象:object
-
shard
分片
一台服务器上无法存储大量数据,ES把一个index里面的数据分成多个shard分布式的存储在多个服务器上(对大的索引分片,拆成多个,分不到不同的节点上)。ES就是通过shard来解决节点的容量上限问题的,通过主分片可以将数据分布到集群内的所有节点上。主分片数是在索引创建时指定的,一般不允许修改,除非Reindex。一个索引中的数据保存在多个分片中(默认为一个)相当于水平分表。一个分片表示一个Lucene的实例,它本身就是一个完整的搜索引擎。我们的文档被存储和索引到分片内,这些对应用程序是透明的,即应用程序直接与索引交互而不是分片。
分词
我们在往es中存入document的时候会将 内容进行分词,es有自带的默认的分词器,但是如果是中文的话我们一般用ik分词器比较多,可以自定义词典,也就是自定义一些词语放在config里面,在分词的时候使用这些词语分词即可。 在查询的时候根据是否match,我们会将查询句子进行分词,需要看是关键字查询还是全文搜索,进行分词不分词匹配或者分词匹配。 所谓分词就是将一个句子拆成一个一个字或者一些词语。
语法
CRUD
- 创建索引:PUT /索引名称?pretty
- 查询索引:GET _cat/indices?v
- 删除索引:DELETE /msb_index?pretty
- GET /product/_doc/_search 查询所有
- GET /product/_doc/1 根据id查询
Searchtimeout
Timeout机制:假设用户查询结果有1W条数据,但是需要10″才能查询完毕,但是用户设置了1″的timeout,那么不管当前一共查询到了多少数据,都会在1″后ES讲停止查询,并返回当前数据。
用法:GET /_search?timeout=1s/ms/m
Query_string
- 查询所有:GET /product/_search
- 带参数:GET /product/_search?q=name:xiaomi
- 分页:GET /product/_search?from=0&size=2&sort=price:asc
Query DSL
Phrase search:短语搜索,和全文检索相反,“nfc phone”会作为一个短语去检索 。
基于短语(Term-based)的查询:
像term(在filter上下文中)或fuzzy一类的查询是低级查询,它们没有分析阶段。这些查询在单一的短语上执行。例如对单词food的term查询会在倒排索引里精确查找food这个词,并对每个包含这个单词的文档计算TF/IDF相关度_score(5.1版本升级中,elasticsearch将算法改进为BM25)
term查询只在倒排查询里精确低查找特定短语,而不会匹配短语的其它变形,如food或FOOD。不管短语怎样被加入索引,都只匹配倒排索引里的准确值。如果在一个设置了not_analyzed的字段为["Food", " Warter"]建索引,或者在一个用whitespace解析器解析的字段为Food Warter建索引,都会在倒排索引里加入两个索引Food和Warter。
query-term:不会被分词
GET /product/_search
{
"query": {
"term": {
"name": "nfc"
}
}
}
GET /product/_search
{
"query": {
"term": {
"name": "nfc phone" 这里因为没有分词,所以查询没有结果
}
}
}
全文(Full-text)检索
match和query_string这样的查询是高级查询,他们会对字段进行分析:
- 如果检索一个
date或integer字段,他们会把查询语句作为日期或者整数格式数据。 - 如果检索一个精确值(
not_analyzed)字符串字段,他们会把整个查询语句作为一个短语。 - 如果检索一个全文(
analyzed)字段,查询会先用适当的解析器解析查询语句,产生需要查询的短语列表。然后对列表中的每个短语执行低级查询,合并查询结果,得到最终的文档相关度。
GET /product/_search
{
"query": {
"match": {
"name": "xiaomi nfc zhineng phone"
}
}
}
#验证分词
GET /_analyze
{
"analyzer": "standard",
"text":"xiaomi nfc zhineng phone"
}
Query and filter:查询和过滤
bool:可以组合多个查询条件,bool查询也是采用more_matches_is_better的机制,因此满足must和should子句的文档将会合并起来计算分值。
Compound queries:复合查询
复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。常见的有两种:
- fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
- bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
Highlight search:高亮
注意:
- 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
- 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
- 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
使用场景:在百度等搜索后,会对结果中出现搜索字段的部分进行高亮处理。
高亮原理
高亮显示的实现分为两步:
- 给文档中的所有关键字都添加一个标签,例如<em>标签
- 页面给<em>标签编写CSS样式
语法
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": { //【要和上面的查询字段FIELD一致】
"pre_tags": "", // 用来标记高亮字段的前置标签
"post_tags": "" // 用来标记高亮字段的后置标签
}
}
}
}
Deep paging问题
原理:elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条
查询990~1000的数据,查询逻辑要这么写:
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 990, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
这里是查询990开始的数据,也就是 第990~第1000条 数据。
集群情况的深度分页:
针对深度分页,ES提供了两种解决方案,官方文档:
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。【官方推荐】
- scroll:原理将排序后的文档id形成快照,保存在内存。
Search After 分页方式是 ES 5 新增的一种分页查询方式,其实现的思路同 Scroll 分页方式基本一致,通过记录上一次分页的位置标识,来进行下一次分页数据的查询。相比于 Scroll 分页方式,它的优点是可以实时体现数据的变化,解决了查询快照导致的查询结果延迟问题。
scroll 分页方式类似关系型数据库中的 cursor(游标),首次查询时会生成并缓存快照,返回给客户端快照读取的位置参数(scroll_id),后续每次请求都会通过 scroll_id 访问快照实现快速查询需要的数据,有效降低查询和存储的性能损耗。
不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:
查询TOP1000,如果es是单点模式,这并无太大影响。
但是elasticsearch基本都是集群部署,例如我们集群有5个节点,需要查询TOP1000的数据,并不是每个节点查询200条就可以了。
因为节点A的TOP200,在另一个节点可能排到10000名以外了。
因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000。 那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?
当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求。