ElasticSearch是开源的高扩展的分布式全文搜索引擎。
ES里的Index
可以看做一个库,而Types
想当于表,Documents
则相当于表的行,这里的Types
的概念已经被逐渐弱化,ElasticSearch6.X
中,一个index
下已经只能包含一个type,ElasticSearch7.X
中,Type
的概念已经被删除了。
PUT 请求 http://127.0.0.1:9200/shopping
GET 请求 http://127.0.0.1:9200/shopping 查看对应索引
GET 请求 http://127.0.0.1:9200/_cat/indices?v
DELETE 请求 http://127.0.0.1:9200/shopping
索引已经创建好了,接下来就是创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加数据格式为json
_id 是随机生成的
POST 请求 http://127.0.0.1:9200/shopping/_doc
{
"title":"小米手机",
"category":"小米",
"price":3999.00
}
_id指定ID
POST/PUT 请求 http://127.0.0.1:9200/shopping/_doc/1001
这样操作_id 就会变成 1001
GET 请求 http://127.0.0.1:9200/shopping/_doc/1001
查询所有文档
GET 请求 http://127.0.0.1:9200/shopping/_search
更新全量数据
http://127.0.0.1:9200/shopping/_doc/1001
{
"title":"小米手机",
"category":"小米",
"price":4999.00
}
局部更新数据 更新一个字段的数据
POST 请求 http://127.0.0.1:9200/shopping/_update/1001
{
"doc" : {
"title":"华为手机"
}
}
DELETE 请求 http://127.0.0.1:9200/shopping/_doc/1001
1、 GET 请求
http://127.0.0.1:9200/shopping/_search?q=category:小米
2、请求体查询
http://127.0.0.1:9200/shopping/_search
{
"query":{
"match" : {
"category":"小米"
}
}
}
3、分页查询
http://127.0.0.1:9200/shopping/_search
{
"query":{
"match_all" : {
}
},
# 分页查询
"from":0,
"size":2,
# 指定查找的结果
"_source":["title"],
# 排序
"sort":{
"price":{
"order":"desc"
}
}
}
4、多条件查询
and
{
"query" :{
"bool" :{
# 多个条件同时成立
"must":[
"match":{
"category":"小米"
},{
"match":{
"price":1999.00
}
]
}
}
}
or
{
"query" :{
"bool" :{
# 多个条件只要一个成立即可
"should":[
"match":{
"category":"小米"
},{
"match":{
"category":"华为"
}
]
}
}
}
范围
{
"query" :{
"bool" :{
# 多个条件只要一个成立即可
"should":[
"match":{
"category":"小米"
},{
"match":{
"category":"华为"
}
],
"filter" :{
"range":{
"price":{
"gt":5000
}
}
}
}
}
}
完全匹配到小米
{
"query":{
"match_phrase" : {
"category":"小米"
}
}
}
{
"query":{
"match_phrase" : {
"category":"小米"
}
},
"highlight":{
"fields" :{
"category":{}
}
}
}
{
"aggs":{ //聚合操作
"price_group":{ // 名称,可自定义
"terms":{ // 分组
"field":"price" //分组字段
}
}
},
//不需要原始数据
"size":0
}
{
"aggs":{ //聚合操作
"price_avg":{ // 名称,可自定义
"avg":{ // 平均值
"field":"price" //分组字段
}
}
},
//不需要原始数据
"size":0
}
PUT 请求 http://127.0.0.1:9200/user/_mapping
{
"properties":{
"name":{
//可以被分词查询
"type":"text",
"index":true
},
"sex":{
"type":"keyword",
"index":true
},
"tel":{
"type":"keyword",
"index":false
}
}
}
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供索引和搜索功能。一个ElasticSearch集群中有一个唯一的名字标识,这个名字默认就是"elasticsearch"。这个名字很重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
集群中包含很多服务器,一个节点就是其中的一个服务器。作为集群的一部分,它存储数据,参与集群的索引和搜索功能。一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。
elsticsearch.yml
cluster.name --- 集群名称 必须统一
node.name --- 节点名称 不能重复
node.master: true
node.data: true
network.host --- 主机名称 localhost
http.port --- 端口号 1001
transport.tcp.port --- 监听端口 9301
//跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
http.port: 1002
transport.tcp.port: 9302
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
hash(id) % 主分片数量 = [0,1,2]
用户可以访问任何一个节点获取数据,这个节点称之为协调节点
对于更新操作:可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,每个文档都有一个_version
版本号,这个版本号在文档被改变时加一。Elasticsearch使用这个_version
保证所有修改都被正确排序。当一个旧版本出现在新版本之后,它会被简单的忽略。利用_version
的这一优点确保数据不会因为修改冲突而丢失。比如指定文档的version
来做更改。如果那个版本号不是现在的,我们的请求就失败了。
对于写操作,一致性级别支持 quorum/one/all
,默认为 quorum
,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。
one
: 要求我们这个写操作,只要有一个primary shard
是active
活跃可用的,就可以执行all
: 要求我们这个写操作,必须所有的primary shard
和replica shard
都是活跃的,才可以执行这个写操作quorum
: 默认的值,要求所有的shard
中,必须是大部分的shard
都是活跃的,可用的,才可以执行这个写操作对于读操作,可以设置 replication
为 sync
(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication
为 async
时,也可以通过设置搜索请求参数_preference
为 primary
来查询主分片,确保文档是最新版本。
node
发送请求过去,这个 node
就是coordinating node
(协调节点)coordinating node
对 document
进行路由,将请求转发给对应的 node
(有 primary shard
)node
上的primary shard
处理请求,然后将数据同步到replica node
coordinating node
等到 primary node
和所有 replica node
都执行成功之后,就返回响应结果给客户端。memory buffer
,然后定时(默认每隔1s)将memory buffer
中的数据写入一个新的segment
文件中,并进入 Filesystem cache
(同时清空 memory buffer
),这个过程就叫做 refresh
;ES 的近实时性:数据存在 memory buffer
时是搜索不到的,只有数据被 refresh
到 Filesystem cache
之后才能被搜索到,而 refresh
是每秒一次, 所以称 es
是近实时的,可以通过手动调用 es
的 api
触发一次 refresh
操作,让数据马上可以被搜索到;memory Buffer
和 Filesystem Cache
都是基于内存,假设服务器宕机,那么数据就会丢失,所以 ES
通过 translog
日志文件来保证数据的可靠性,在数据写入 memory buffer
的同时,将数据写入 translog
日志文件中,在机器宕机重启时,es
会自动读取 translog
日志文件中的数据,恢复到 memory buffer
和 Filesystem cache
中去。ES 数据丢失的问题:translog
也是先写入 Filesystem cache
,然后默认每隔 5 秒刷一次到磁盘中,所以默认情况下,可能有 5 秒的数据会仅仅停留在 memory buffer
或者 translog
文件的 Filesystem cache
中,而不在磁盘上,如果此时机器宕机,会丢失 5 秒钟的数据。也可以将 translog
设置成每次写操作必须是直接 fsync
到磁盘,但是性能会差很多。flush
操作:不断重复上面的步骤,translog
会变得越来越大,当 translog
文件默认每30分钟或者 阈值超过 512M
时,就会触发 commit
操作,即 flush
操作。
buffer
中的数据 refresh
到 Filesystem Cache
中去,清空 buffer
;commit point
(提交点),同时强行将 Filesystem Cache
中目前所有的数据都fsync
到磁盘文件中;translog
日志文件并创建一个新的 translog
日志文件,此时 commit
操作完成删除和更新都是写操作,但是由于Elasticsearch
中的文档是不可变的,因此不能被删除或者改动以展示其变更;所以 ES
利用.del
文件 标记文档是否被删除,磁盘上的每个段都有一个相应的.del
文件
.del
文件中被标记为 deleted
状态。该文档依然能匹配查询,但是会在结果中被过滤掉。doc
标识为 deleted
状态,然后创建一个新的 doc
。memory buffer
每 refresh
一次,就会产生一个 segment
文件 ,所以默认情况下是 1s 生成一个segment
文件,这样下来 segment
文件会越来越多,此时会定期执行 merge
。
每次merge
的时候,会将多个 segment
文件合并成一个,同时这里会将标识为 deleted
的 doc
给物理删除掉,不写入到新的 segment
中,然后将新的segment
文件写入磁盘,这里会写一个 commit point
,标识所有新的 segment
文件,然后打开 segment
文件供搜索使用,同时删除旧的 segment
文件。
搜索被执行成一个两阶段过程,即 Query Then Fetch
:
客户端发送请求到 coordinate node
,协调节点将搜索请求广播到所有的 primary shard
或 replica shard
。每个分片在本地执行搜索并构建一个匹配文档的大小为from + size
的优先队列。每个分片返回各自优先队列中 所有文档的ID
和排序值 给协调节点,由协调节点及逆行数据的合并、排序、分页等操作,产出最终结果。
协调节点根据 doc id
去各个节点上查询实际的 document
数据,由协调节点返回结果给客户端。
coordinate node
对 doc id
进行哈希路由,将请求转发到对应的 node
,此时会使用 round-robin
随机轮询算法,在primary shard
以及其所有 replica
中随机选择一个,让读请求负载均衡。node
返回 document
给 coordinate node
。coordinate node
返回 document
给客户端。Query Then Fetch
的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确,DFS Query Then Fetch
增加了一个预查询的处理,询问 Term
和 Document frequency
,这个评分更准确,但是性能会变差。
ES在数据量很大的情况下(数十亿级别)如何提高查询效率
需要从ES搜索优化作答
es
的性能优化,主要是围绕着fileSystem cache
,也可以叫做OS cache
来进行;es
写入数据实际上数据最终都会写入到磁盘中去,当我们搜索读取的时候,系统会将数据放入到os cache
中,而es严重依赖于这个os cache
,如果我们给机器的内存足够多,在es
里存的数据小于内存容量,那么搜索效率是非常高的。
你往es
里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到filesystem cache
里面去,es
的搜索引擎严重依赖于底层的filesystem cache
,你如果给filesystem cache
更多的内存,尽量让内存可以容纳所有的idx segment file
索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高
es
减少数据量仅仅放要用于搜索的几个关键字段即可,尽量写入es
数据量跟es
机器的filesystem cache
是差不多的就可以了;其他不用来检索的数据放hbase
里或者mysql
每隔一段时间,访问一下数据,然后数据进入到os cache
中。这样用户来访问 时候就访问到os cache
中的数据,就比较快。
es
可以做类似于mysql
的水平切分,就是将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。这样可以确保热数据在被预热之后,尽量都让他们留在filesystem os cache
里,别被冷数据冲刷掉。
es
里面的复杂的关联查询尽量别用,比如join/ nested /parent-child
搜索都要尽量避免
可以使用scroll api
scroll
会一次性给你生成所有数据的一个快照,然后每次滑动向后翻页就是通过游标scroll_id
移动,获取下一页。除了使用scroll api
,也可以使用search_after
来做,search_after
的思想使用前一页的结果来帮助检索下一页的数据,这种方式也不允许你随意翻页,你只能一页页往后翻。
ElasticSearch
通过多个path.data
目录配置把数据条带化分配到他们上面NFS
或者SMB
、CIFS
delayed_timeout
,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改。带路由查询
Translog Flush
,目的是降低Iops
、Writelock
Index Refresh
间隔,目的是减少Segment Merge
的次数Bulk
线程池和队列Lucene
层的索引建立,目的是降低CPU及IO密集使用硬盘的应用
ES默认采用较保守的策略,让后台定期进行段合并
Lucene
在新增数据时,采用了延迟写入的策略,默认情况下索引的refresh_interval
为1秒Lucene
将待写入的数据先写到内存中,超过1秒(默认)时就会触发一次refresh
,然后refresh会把内存中的数据刷新到操作系统的文件缓存系统中。refresh
周期延长Flush
的主要目的是把文件缓存系统中的段持久化到硬盘,当Translog
的数据量达到512MB或者30分钟时,会触发一次Flush
index.translog.flush_threshold_size
参数默认值是512MB
如果我们需要大批量进行写入操作,可以先禁止Replica
复制,设置index.number_of_replicas:0
关闭副本。再写入完成后,Replica
修改回正常的状态。
jvm.options
不能超过物理内存的50%
堆内存的大小最好不要超过32GB
-Xms31g
-Xmx31g
Elasticsearch
会对存储的数据进行切分,将数据划分到不同的分片上,同时每一个分片会保存多个副本,主要是为了保证分布式环境的高可用。在 Elasticsearch
中,节点是对等的,节点间会选取集群的 Master
,由 Master
会负责集群状态信息的改变,并同步给其他节点。
Elasticsearch
的性能会不会很低:只有建立索引和类型需要经过 Master
,数据的写入有一个简单的Routing
规则,可以路由到集群中的任意节点,所以数据写入压力是分散在整个集群的。
Elasticsearch
的选主是ZenDiscovery
模块负责的,主要包含Ping
(节点之间通过这个RPC来发现彼此)和 Unicast
(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分;
elasticsearch.yml
设置的值 discovery.zen.minimum_master_nodes
;master
的节点(node.master: true
)根据 nodeId
字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master
节点。master
。否则重新选举一直到满足上述条件。补充:master
节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data
节点可以关闭http
功能。
(1)当集群中 master
候选节点数量不小于3个时(node.master: true
),可以通过设置最少投票通过数量(discovery.zen.minimum_master_nodes
),设置超过所有候选节点一半以上来解决脑裂问题,即设置为 (N/2)+1
;
(2)当集群master
候选节点 只有两个时,这种情况是不合理的,最好把另外一个node.master
改成false
。如果我们不改节点设置,还是套上面的(N/2)+1
公式,此时discovery.zen.minimum_master_nodes
应该设置为2。这就出现一个问题,两个master
备选节点,只要有一个挂,就选不出master
了