Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎,基于Lucene的搜索服务器,采用Java语言编写,开发者可以通过RESTful API轻松实现搜索功能。
优点:分布式、全文检索、近实时搜索和分析、高可用、模式自由、RESTful API。
应用:站内搜索、NoSQL数据库、日志分析等。
ELK
基于Elasticsearch衍生出来了一系列开源软件,统称为Elastic Stack,主要包括分布式搜索引擎Elasticsearch
、日志采集与解析工具Logstacsh
、可视化分析平台Kibana
,简称ELK Stack,是非常流行的集中式日志解决方案。Logstash既可以作为日志收集器又能解析日志,但是Logstash会消耗较多的CPU和内存资源,容易造成服务器性能下降。后来Elastic公司推出了Beats家族,在数据收集方面使用Beats取代Logstash,解决了Logstash在各服务器节点上占用系统资源高的问题。另外Beats支持SSL/TLS加密传输客户端双向认证,保证了信息安全。
什么是ELK
Lucene
Lucene是Java语言编写的全文搜索框架,用于处理纯文本的数据,但它只是一个库,提供建立索引、执行搜索等接口,但不包含分布式服务,这些正是ES做的。
Elasticsearch 基于Lucene,使用Lucene构建索引、提供搜索功能,但是Lucene只是一个由Java语言编写的库,Elasticsearch在Lucene的基础上做了更多的改进,提供了多语言的接口。Elasticsearch底层使用的仍然是Lucene的API,Lucene专注于底层搜索的建设,Elasticsearch专注于企业应用。
倒排索引是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射,它是文档检索系统中最常用的数据结构。
假设有2个文档,文档和词语的包含关系如下(正排索引):
文档 | 词语 |
---|---|
doc1 | 中国、美国、韩国 |
doc2 | 英国、中国、美国、德国 |
词语所属的文档关系,也就是倒排索引如下:
词语 | 文档 |
---|---|
中国 | doc1、doc2 |
美国 | doc1、doc2 |
韩国 | doc1 |
英国 | doc2 |
德国 | doc2 |
集群 cluster
代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。
节点 node
集群中的一个服务器是一个节点。
索引 index
一个索引就是一个拥有几分相似特征的文档的集合,索引的结构仍然是倒排索引。
类型 type
一个类型是索引的一个逻辑上的分类或分区,其语义由你来定。ES6可以自定义type,ES7使用默认的_doc作为type。
docValues是Lucene 4.X版本以后新增的重要特性,我们知道Lucene是使用倒排索引来达到快速检索的目的,但是也存在一定的缺陷,假如我们需要对数据做一些聚合操作,例如排序、分组时,Lucene内部会遍历提取所有出现在文档集合的排序字段,然后再次构建一个最终的排好序的文档集合,这个过程在内存中操作,如果数据量巨大,容易造成内存溢出和性能缓慢。DocValues是Lucene在构建索引时额外建立一个document => filed/value的映射列表、在构建索引时会对开启docvalues的字段构建一个已经排好序的文档到字段级别的一个列式存储映射,它减轻了在排序和分组时对内存的依赖,而且大大提升了这个过程的性能,当然它也会耗费一定的内存空间。
文档 document
一个文档是一个可被索引的基础信息单元。文档都是JSON格式。一个文档由index、type、docID唯一确定。
索引分片 shard
es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
索引副本 shard replicas
es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。
文件系统
,支持多种文件类型。Local FileSystem是存储在本地的文件系统,Shared FileSystem是共享存储,HDFS是Hadoop的分布式存储,Amazon S3是Amazon的云存储服务(同阿里云的OSS)。Lucene框架
。Elasticsearch底层API由Lucene提供,每一个节点都有一个Lucene引擎的支持。Index Module(索引模块
),Search Module(搜索模块)
,Mapping(映射解析模块)
等。River相当于第三方插件,用来导入第三方数据源,在2.X之后已经不再使用。节点发现模块
,不同机器上的Elasticsearch节点要组成集群需要进行消息通信,集群内部需要选举master节点,这些工作都是由Discovery模块完成的。Scripting用来支持JavaScript、Python等多种语言,可以在查询语句中嵌入,使用Script语句性能稍低。Elasticsearch也支持多种第三方插件。传输模块
和JMX。传输模块支持Thrift、Memcached、HTTP,默认使用HTTP传输。JMX是Java的管理框架,用来管理Elasticsearch应用。RESTful API
和Elasticsearch集群进行交互。以索引名为blog为例,以下都为DSL语句。
新建索引
PUT blog
{
//settings定义配置参数,如副本数、分片数等等
"settings": {
"number_of_shards": 12,
"number_of_replicas": 0
},
//mappings定义索引字段
"mappings": {
"properties": {
"commentId": {
"type": "keyword"
},
"desc": {
"type": "text"
},
"videoId": {
"type": "keyword"
},
"diggCount": {
"type": "integer"
},
"createTime": {
"type": "long"
},
"lastUpdateTime": {
"type": "long"
}
}
}
}
返回代表成功
{
"acknowledged": true,
"shards_acknowledged": true
}
修改索引参数
ES不支持修改索引字段,以及mappings中的分片数,可以修改settings中的配置参数。如果需要修改索引字段可以使用创建中间索引,用_reindex
来实现:新增一个新的索引,使用_reindex复制索引,然后删除旧的索引,再用_aliases重命名新的索引为原索引名。
PUT blog/_settings
{
"number_of_replicas": 2
}
查看索引
GET blog
GET blog/_mappings
GET blog/_settings
删除索引
DELETE blog
复制索引
可以复制完整的数据,或者通过type和query限制复制数据。
POST _reindex
{
"source":{"index": "blog"},
"dest":{"index": "blog_news"}
}
索引别名
可以新增或删除索引别名。
POST /_aiiases
{
"actions":[
{"add":"index": "test1", "alias": "alias1"}
]
}
还有打开/关闭索引、收缩索引等方法
新建文档
使用PUT方法,uri为index/type/id,如果不指定id,Elasticsearch会自动生成随机字符串id
PUT blog/doc/1
{
"id": 1,
"title": "Git简介",
"posttime": "2022-5-1",
"content": "Git是一款免费、开源的分布式版本控制系统"
}
获取文档
GET blog/doc/1
更新文档
POST blog/doc/1
{
"posttime": "2022-7-1"
}
也可以通过查询条件指定更新文档
POST blog/_update_by_query
{
"query": {
"term":{"title": "Git简介"}
}
}
删除文档
DELETE blog/doc/1
也可以通过查询条件指定删除文档
批量操作
Bulk API允许使用单一请求来实现多个文档的create、index、update或delete。
例如:
curl -XPOST 'localhost:9200/indexName/_bulk?pretty' --data-binary@account.json
JSON文件内容如下:
{"index": {"_index": "blog", "_type":"article", "id": 1}}
{"title": "blog title"}
版本控制
Elasticsearch使用乐观锁的机制来解决并发修改数据的问题。文档每被修改一次,文档版本号会自增一次。Elasticsearch使用_version字段确保所有的更新都有序进行。
路由机制
Elasticsearch是一个分布式系统,当索引一个文档时会被存储到master节点上的一个主分片上。Elasticsearch的路由机制是通过哈希算法:
shard = hash(routing) % number_of_primary_shards
Elasticsearch默认将文档id作为routing值。
查询过程:
mappings
Mappings用来定义一个文档以及所包含的字段如何被存储和索引,可以在映射中事先定义字段的数据类型、分词器等属性。
映射可以分为动态映射和静态映射, 动态映射不需要在创建索引的时候设置字段类型,在写入文档时自动识别文档的字段和类型,并将该字段添加到映射中。静态映射需要在创建索引的时候设置字段类型,不会自动增加字段。
由dynamic
字段设置,true为动态映射,false为静态映射。
{
mappings:{
"dynamic": true//映射类型
"properties":{//定义字段属性
"title":{//字段名称
"type":"text",//字段类型
"analyzer": "ik_max_keyword"//分词器
},
"publish_data":{"type":"date"}
}
}
}
字段类型
一级分类 | 二级分类 |
---|---|
核心类型 | text、keyword、long、integer、double、date、boolean… |
复合类型 | array、object、nested |
地理类型 | geo_point、geo_shape |
特殊类型 | ip、completion、token_count、attachment、percolate |
text、keyword都为字符串类型,text是使用分词器,keyword不分词
。
analyzer
analyzer指定文本字段的分词器,对索引和查询都有效。Lucene自带的分词器包括StopAnalyzer(停用词分词器)、StandardAnalyzer(标准分词器)、WhitespaceAnalyzer(空格分词器)、SimpleAnalyzer(简单分词器)等等。中文的分词器一般使用IK Analyzer,需要外部引入。
IK分词器
全文查询
match query
会对查询语句信息分词,分词后查询语句中的任何一个词项被匹配,文档就被搜索到。
GET books/_search
{
"query":{
"match":{
"title" : {
"query":"java 编程思想",
"operator":"or"
}
}
}
}
match_phrase query
首先会把query内容分词,分词器可以自定义,同时文档还要满足以下两个条件才会被搜索到:
match_phrase_prefix query
支持最后一个term前缀,
multi_match query
是match的升级,用于搜索多个字段。
common_terms query
是一种在不牺牲性能的情况下替代停用词提高搜索准确率和召回率的方案。
query_string query
允许在一个查询语句中使用多个特殊条件关键词(AND|OR|NOT)对多个字段进行查询。
simple_query_string`是一种适合直接暴露给用户,并且具有非常完善的查询语法的查询语句,接受Lucene的查询语法。
词项查询
term query
用来查找指定字段中包含给定单词的文档,term查询不被解析,只有查询词和文档中的词精确匹配才会被搜索到,应用场景为人名、地名等需要精准匹配的需求。
GET books/_search
{
"from": O,
"size" : 100,
"query": {
"term" :{
"title": "思想"
}
}
}
terms query
是term query的升级版,可以用来查询文档中包含多个词的文档。
range query
用于范围查询
exists query
返回字段中至少有一个非空值的文档。
prefix query
前缀匹配
wildcard query
通配符查询
regexp query
表达式查询
fuzzy query
模糊查询
type query
查询文档type
ids query
查询文档id
复合查询
复查查询就是把一些简单查询组合在一起实现更复杂的查询需求,除此之外复合查询还可以控制另一个查询的行为。
constant_score query
可以包装一个其他类型的查询,并返回匹配过滤器中的查询条件且具有相同评分的文档。
bool query
可以把任意多个简单查询组合在一起,使用must、should、must_not、filter选项来表示简单查询之间的逻辑。
must相当于AND,should相当于OR、must_not与must相反,filter是过滤,和must一样匹配选项才会返回,但是filter不评分。
{
"query": {
"bool": {
"minimum_should_match": 1,
"must": {
"match": {
"title": "java"
}
},
"should": [{
"match": {
"description": "虚拟机"
}
}],
"must_not": {
"range": {
"price": {
"gte": 70
}
}
}
}
}
}
dis_max query
与bool query也有一定联系,支持多并发查询,可返回与任意查询条件子句匹配的任何文档类型,dis_max query只使用最佳匹配查询条件的分数。
function_score query
可以修改查询的文档得分,可以改用过滤器加自定义评分函数的方式来取代传统的评分方式。使用function_score query,用户需要定义一个查询和一至多个评分函数,评分函数会对查询到的每个文档分别计算得分。可以修改boost_mode或使用script脚本定义评分公式。
boosting query
查询用于需要对两个查询的评分进行调整的场景,boosting查询会把两个查询封装在一起并降低其中一个查询的评分。包括positive(评分保持不变)、negative(降低文档评分)和negative_boost(指明nagetive中的降低权值)三部分。
indices query
适用于需要在多个索引之间进行查询的场景,它允许指定一个索引名字列表和内部查询。包括query和no_match_query两部分。
嵌套查询
在Elasticsearch 样的分布式系统中执行全 SQL 风格的连接查询代价昂贵,是不可行的。相应地,为了实现水平规模地扩展, Elasticsearch 提供了以下两种形式的 join:
nested qeury(嵌套查询)
:文档中可能包含嵌套类型的字段,这些字段用来索引 些数组对象,每个对象都可以作为一条独立的文档被查询出来。
新增嵌套字段
PUT /my_index
{
"mappings":{
"type1": {
"properties":{
"obj1":{
"type":"nested"
}
}
}
}
}
嵌套查询
POST my_index/_search
{
"query":{
"nested":{
"path":"type1",
"query":{
"bool":{
"must":[
{
"match":{
"type1.obj1":"火腿nested"
}
}
]
}
}
}
}
}
has_child query/ has_parent query(父子查询)
:父子关系可以存在单个的索引的两个类型的文档之间。 has child 查询将返回其子文档能满足特定查询的父文档,而 has_parent 则返回其父文档能满足特定查询的子文档。
新增父子字段,这里branch是parent type,employee是child type。
PUT /company
{
"mappings": {
"branch":{},
"employee" : { "_parent": { "type": "branch" }}
}
}
父子查询
GET company/branch/_search
{
"query": {
"has_child": {
"type": "employee",
"query": {
"range": {
"dob": {
"gte": "1980-01-01"
}
}
}
}
}
}
位置查询
geo_distance query
可以查找在一个中心的点指定范围内的地理点文档。例如,查找距离天津200km以内的城市,搜索结果中会返回北京。
GET geo/_search
geo_bounding_box query
用于查找落 指定的矩形内的地理坐标。查询中由两个点确定一个矩形,在这两个点上分别做垂线(经度)和平行线(纬度),相交线会组成 个矩形区域。
geo_polygon query
用于查找在指定多边形内的地理点。
geo_shape query
可用于查询 geo_shape类型的地理数据,地理形状之间的关系有相交、包含、不相交三种。
特殊查询
more_like_this query
可以查询和提供文本类似的文档,通常用于近似文本的推荐等场景。
script query
脚本查询。
percolate query
一般情况下,我们是先把文档写入到 Elasticsearch 中,通过查询语句对文档进行搜索。percolate query则是反其道而行之的做法,它会先注册查询条件,根据文档来查询query。
搜索高亮
使用高亮功能标记查询关键字, Elasticsearch 默认会用<em>
搜索排序
Elasticsearch按照查询和文档的相关度进行排序的,默认按评分降序排序。Elasticsearch也支持按字段和多宇段排序。
聚合查询
Max Aggregation 用于最大值统计。
Min Aggregation 用于最小值统计。
Avg Aggregation 用于计算平均值。
Sum Aggregation用于计总和。
等等···
Elasticsearch中还包括很多的设置参数以及api,可以通过Kibana操作DSL语句,也可以通过引入ES依赖来操作Java API。在搭建ES以及执行DSL、JAVA API的过程中也会遇到各种各样的问题,以上内容还需实际操作才能加深理解。
参考书籍:《从Lucene到Elasticsearch》
集群节点角色
集群启动流程
对节点ID排序,取ID值最大的节点作为Master
,每个节点都运行这个流程,投票选出唯一、公认的主节点。然后再把最新的机器元数据复制到选举出的主节点上。基于节点ID排序需要参选人数过半
以及得票过半,否则可能因网络问题产生脑裂
。根据版本号确定最新的元信息
,然后把这个信息广播下去。为了集群的一致性,参与选举的元信息数量需要过半。选举算法
Bully算法:每个节点都有一个唯一的ID,使用该ID对节点进行排序,ID最高的为Leader。该算法简单易实现,但是会出现脑裂,主节点挂掉又恢复等问题。ES通过推迟选举,直到当前的Master失效来解决上述问题,只要当前主节点不挂掉,就不重新选主;再通过得票超过半数的原则解决脑裂问题。
Paxos算法:Paxos算法详解
Memory Buffer(同时写入translog)
,然后定时(默认是每隔1秒)写入到Filesystem Cache,这个从Momery Buffer到Filesystem Cache的过程就叫做refresh
;flush
;Get流程
Get流程是指通过 doc id 来查询,会根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个 shard 去查询。
round-robin
随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡。Search流程
搜索被执行成一个两阶段过程,Query和Fetch,我们称之为 Query Then Fetch;
Search type
query then fetch(默认方式):
如果你搜索时,没有指定搜索方式,就是使用的这种搜索方式。这种搜索方式,大概分两个步骤,第一步,先向所有的shard发出请求,各分片只返回排序和排名相关的信息(注意,不包括文档document),然后按照各分片返回的分数进行重新排序和排名,取前size个文档。然后进行第二步,去相关的shard取document。这种方式返回的document与用户要求的size是相等的。
query and fetch
:向索引的所有分片(shard)都发出查询请求,各分片返回的时候把元素文档(document)和计算后的排名信息一起返回。这种搜索方式是最快的。因为相比下面的几种搜索方式,这种查询方法只需要去shard查询一次。但是各个shard返回的结果的数量之和可能是用户要求的size的n倍。
DFS query then fetch
:这种方式比第一种方式多了一个初始化散发(initial scatter)步骤,有这一步,据说可以更精确控制搜索打分和排名,但是性能会变差。
DFS query and fetch
:这种方式比第一种方式多了一个初始化散发(initial scatter)步骤。
Elasticsearch搜索类型讲解
在ES的默认设置下,是综合考虑数据可靠性、搜索实时性、写入速度等罂粟的。当离开默认设置、追求极致的写入速度时,很多是以牺牲可靠性和搜索实时性为代价的。对写入速度要求高可以从以下几方面入手:
translog flush间隔调整
ES默认设置意为每个请求都将translog flush到磁盘上,保证写入数据的可靠性。这是影响ES写入速度的最大因素
index.translog.durability: request
调整为以下配置表示按指定时间周期性刷盘,这样可以提高写入速度,但会因宕机导致数据丢失。
index.translog.durability: async
index.translog.sync_interval: 120s
索引刷新间隔refresh_interval
index.refresh_interval: 1s
默认情况下所有的refesh_interval为1秒,意味着数据写入1秒后可以被搜到。每次索引的refresh会产生一个新的Lucene段,这会导致频繁的segment merge行为。如果不需要这么高的实时性可以降低refresh的的间隔。
index.refresh_interval: 120s
段合并优化
index.merge.policy.segment_per_tier
index.merge.policy.max_merged_segment
该属性指定了每层分段的数量,取值越小则最终segment越少,因此需要merge的操作更多,增加segment_per_tier值可以减少merge操作。
max_merged_segment指定了单个segment的最大容量,默认为5GB,可以考虑适当降低此值。
indexing buffer
indices.memory.min_index_buffer_size
indexing buffer在为doc简历索引时使用,当缓冲满时刷入磁盘,生产一个新的segment,可以考虑适当增大该值。
使用bulk请求
批量写比一个索引请求只写单个文档的效率高很多,但是要注意bulk请求的整体字节数不要太大。
磁盘间的任务均衡
ES在分配shard时,落到磁盘上的shard可能并不均匀,会导致磁盘的占用率不均匀。可以修改ES的负载策略,默认为简单轮询,改为基于可用空间的动态加权轮询。
节点间的任务均衡
为了节点间任务均衡,写入客户端应该把bulk请求轮询发送到各个节点。
索引过程调整和优化
自动生成doc ID、调整字段mappings、调整_source字段、禁用_all字段、对Analyzed的字段禁用Norms…
为文件系统cache预留足够的内存
一般情况下,应用程序的读写都会被操作系统cache,命中cache可以降低对磁盘的访问频率。
使用更快的硬件
写入性能对CPU性能更敏感,搜索性能更多是在IO能力,使用SSD比旋转类存储介质好得多。
文档模型
避免join操作,嵌套查询会使查询慢几倍,父子查询会使查询慢几百倍。
预索引数据
针对某些查询的模式来优化数据的索引方式。
字段映射
有的字段类型使用keyword更好。
避免使用脚本
一般避免使用脚本,如果一定要用,优先考虑painless和expressions。
优化日期搜索
为只读索引执行force-merge
为不再更新的只读索引执行force merge,将Lucene索引合并为单个分段,可以提升查询速度。当一个Lucene索引存在多个分段时,每个分段会单独执行搜索再将结果合并,将只读索引强制合并为一个Lucene分段不仅可以优化搜索过程,对索引恢复速度也有好处,
预热全局序号
全局序号是一种数据结构,用于在keyword字段上运行terms聚合。可以通过配置映射在刷新(refresh)时告诉ES预先加载全局序数。
调节搜索请求中的batched_reduce_size
默认情况下,聚合操作时在协调节点需要等待所有的分片都取回结果后才执行,使用batched_reduce_size参数可以不等待全部分片返回结果,而是在指定数量的分片返回结果之后就可以先处理一部分。
深度优先还是广度优先
深度优先是默认设置,先构建完整的树,然后修建无用节点。广度优先是先执行第一层聚合,再继续下一层聚合之前会先做修剪。
先知搜索请求的分片数
默认ES会拒绝超过1000个分片的搜索请求,可以通过action.search.shard_count
配置修改。
利用自适应副本选择(ARS)提升ES响应速度
ES的搜索请求默认通过轮询策略转发到分片的副本,但是这种没有考虑后端实际的系统压力,通过ARS策略可以策略可以根据实际的负载情况选择节点。
参考书籍:《Elasticsearch源码解析与优化实战》
ES6.X和7.X对比
版本信息 | 6.X | 7.x |
---|---|---|
集群连接对比 | 可使用TransportClient,建议使用restclient | TransportClient被废弃,只能使用restclient。于java编程,建议采用 High-level-rest-client 的方式操作ES集群 |
ES数据存储结构变化 | index/type 一对一,建议使用_doc做type | 去除了type,es7中使用默认的_doc作为type |
默认配置变化 | 默认节点名称为主机名,默认分片数改为5 | 默认节点名称为主机名,默认分片数改为1 |
ES程序包默认 | Jdk需自己安装配置,需要配置官方支持的jdk版本建议在Java 8发行版系列中安装Java版本1.8.0_131或更高版本。打包大小100M+ | 捆绑版本的OpenJDK。 捆绑的JVM,位于Elasticsearch主目录的jdk目录中。可使用自己的Java版本,在JAVA_HOME环境配置。打包大小300M+(包含jdk) |
询相关性速度优化 | 倒排索引的方式进行查询 | 倒排索引的方式进行查询+Weak-AND算法 |
间隔查询(Intervals queries) | 无 | 查找单词或短语彼此相距一定距离的记录 |
安全性功能 | 6.8有免费 其他无 | 7.0无免费 其他有免费 |