EleasticSearch
- ES概述
- 1.1 基本概念
- ES集群
- 2.1 集群相关概念
- 2.2 单机部署
- 2.3 集群部署
- 2.3.1 如何分配主分片?
- 2.3.2 备份片设置多少合适?
- 2.3.3 集群选举
- ES集群核心原理
- 3.1 分片存储
- 3.1.1 主分片(primary shard)
- 3.1.2 备份片(replica shard)
- 3.2 负载均衡
- 3.3 故障探测
- 3.4 索引存储
- 3.4.1 不可变性
- 3.4.2 存储结构
- 3.4.3 存储流程
- 3.5 DOC操作
- 3.5.1 doc新增
- 3.5.2 refresh
- 3.5.3 doc删除(修改)
- 3.5.3 段合并
- 3.6 集群路由
- 3.6.1 document如何路由
- 3.7 倒序索引
- 3.7.1 Posting List
- 3.7.2 Term Dictionary
- 3.7.3 Term Index
- 3.7.4 FST(Finite State Transducers)
- 3.7.5 Frame Of Reference
- 3.7.5 联合索引
- ES基础语法
- 4.1 添加数据
- 4.2 查询数据
- 4.3 更新数据
- 4.4 删除数据
- 4.5 排序分页
- 4.6基于Java代码查询
- 4.6.1 查询所有
- 4.6.2 解析查询字符串
- 4.6.3 通配符查询(wildcardQuery)
- 4.6.4 词条查询(termQuery)
- 4.6.5 字段匹配查询
- 4.6.6 只查询ID(标识符查询)
- 4.6.7 相似度查询
- 4.6.8 范围查询
- 4.6.9 组合查询(复杂查询)
- 4.6.10 排序查询
- 4.6.11 其他
- 分词器
- 5.1 简介
- 5.2 分类
- 5.3 指定分词器
- 5.4 IK分词器
- 5.4.1 分词划分
- 5.4.2 扩展词和关键词
- ES 深度翻页问题解决方案
- 6.1 scroll
- 6.2 sliced scroll
- 6.3 search after
ES概述
Elasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene。
EleasticSearch的特点如下:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索。
- 一个分布式实时分析搜索引擎。
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
ES是一个搜索引擎, 同时也是一个分布式文档存储数据库。
1.1 基本概念
数据库 | 表 | 行 | 列 | |
---|---|---|---|---|
MySQL | DB | Table | Row | Colume |
ES | Index | Mapping | Document | Field |
索引Index
ES中的索引概念不是关系型数据库中的索引, 而是指存数据的地方, 类似于关系型数据库中数据库的概念。类型Type
有的文章说ES中的类型Type对应的是关系型数据库中的表, 在使用ES中我们会遇到另外一个概念映射(Mapping), 大部分文档都认为映射(Mapping)才是真正的对应关系型数据库中的表。实际上ES中Type的概念已经名存实亡了,后期的版本中越来越被弱化, 在未被ES正式移除之前, ES后期版本已经不允许一个索引Index创建多个Type了, 在ES7中已经移除了Type, 而ES6只允许一个Index创建一个Mapping。
如果现阶段一定要理解ES中的Type, 那么一定要和Mapping结合起来。可以理解为类型Type就是定义一个表而已, 而映射Mapping定义了表结构, 包括哪些列, 哪些行.
文档Document
在非关系型数据库中, 有部分被称之为"文档数据库", 对应于关系型数据库中的一行记录。字段Field
对应关系型数据库中的列。
ES集群
2.1 集群相关概念
节点
一个ES实例称之为一个节点, 单机部署的ES有且只有一个节点, 集群部署的ES有多个节点且只有一个主节点。分片
ES作为分布式集群部署, 同样也可以作为单机节点部署.。ES中数据被分散存储在分片中, ES屏蔽了底层的分片实现, 我们直接与索引交互而不与分片交互。
分片数量的多少与是否是集群部署和单机部署无关, 即使是单机部署在创建索引的时候也可以制定多个分片(默认是5个主分片, 1个备份(包含5个备分片))。
分片有主分片和备份片之分, 顾名思义, 备份片是主分片的备份, 当主分片出现故障, 备份片充当主分片。
2.2 单机部署
- 单机部署的ES, 即ES只有一个节点;
- 在创建索引时, 如果不指定主分片和备份片的数量, 默认创建5个主分片, 5个备份片;
实际上对于单机而言多个主分片并无多大意义, 因为主备都在一个节点机器上, 如果主分片故障, 备分片也同样会故障.。
2.3 集群部署
- 集群部署的ES有多个节点,且只有一个主节点。
- 主节点是可以通过选举产生的,主从节点是对于集群内部来说的。
- ES是去中心化的,与任何一个节点的通信和与整个ES集群通信是等价的。
- 提供了联合索引以及可跨所有节点的搜索能力。
- 拥有冗余能力,它可以在一个或几个节点出现故障时保证服务的整体可用性。
- 需要确定主分片的分配,备份片机制。
2.3.1 如何分配主分片?
主分片的划分更多是取决于用户的数量和节点的数量。通常来讲, 分片数量越多越好, 因为这样能将数据分散到不同分片, 以便于以后在扩容新增节点时, ES能自动将分片重新均匀分布。
但这也不是绝对的, 例如有3个节点, 100个分片, 每个节点就33个分片, 当搜索请求调度到同一节点的不同分片时, 此时就会引发硬件资源争夺, 造成性能问题。
反过来, 如果3个节点只分配3个分片, 随着业务增长, 数据量增大, 单个分片已不能承载它最大的数量, 此时就算新增节点, 但是分片数量就3个, 主分片的数量在创建索引时便确定且不可修改, 此时只能重新创建索引。
既要对合理的数据增长有一个判断, 又要对期望有一度的把握。官方给出的建议, 每个分片的数量最好在20G~40G。
这就意味着如果你有4个节点, 数量预估在200G左右甚至更大, 此时分片数量设置为5~10个比较合适, 7或8个差不多, 每个节点2个分片。
2.3.2 备份片设置多少合适?
- 上面谈到主分片, 副分片划分同等重要。
- 如果不对主分片备份, 主分片故障则会导致数据丢失, 部分数据不可查询。
- 副本分片设置过多会造成额外的存储空间。
- 默认情况下, 创建索引时会创建一个分片副本。
2.3.3 集群选举
- 如果同时启动, 按照nodeId进行排序, 取出最小的做为master节点。
- 如果不是同时启动, 则先启动的候选master节点, 会竞选为master节点。
- 节点完成选举后, 新节点加入, 会发送join request 到master节点。默认会重试20次。
- 如果宏机, 集群node会再次进行ping过程, 并选择一个新的master。
- 一旦一个节点被明确设为一个客户端节点(node.client设置为true), 则不能在成为主节点(node.master会自动设为false)。
相关配置:
# 如果`node.master`设置为了false,则该节点没资格参与`master`选举。
node.master = true
# 默认3秒,最好增加这个参数值,避免网络慢或者拥塞,确保集群启动稳定性
discovery.zen.ping_timeout: 3s
# 用于控制选举行为发生的集群最小master节点数量,防止脑裂现象(= 节点数/2+1)
discovery.zen.minimum_master_nodes : 2
# 新节点加入集群的等待时间
discovery.zen.join_timeout : 10s
# 当集群中没有活动的Master节点后,该设置指定了哪些操作(read、write)需要被拒绝(即阻塞执行)。有两个设置值:all和write,默认为wirte。
discovery.zen.no_master_block : write
节点类型:
#配置文件中给出了三种配置高性能集群拓扑结构的模式,如下:
#1. 如果你想让节点从不选举为主节点,只用来存储数据,可作为负载器
node.master: false
node.data: true
#2. 如果想让节点成为主节点,且不存储任何数据,并保有空闲资源,可作为协调器
node.master: true
node.data: false
#3. 如果想让节点既不称为主节点,又不成为数据节点,那么可将他作为搜索器,从节点中获取数据,生成搜索结果等
node.master: false
node.data: false
ES集群核心原理
3.1 分片存储
- ES的“分片(shard)”机制可将一个索引内部的数据分布地存储于多个节点上。
- 它通过将一个索引切分为多个底层物理的Lucene索引完成索引数据的分割存储功能。
- 这每一个物理的Lucene索引称为一个分片(shard)。
- 分片分布到不同的节点上。构成分布式搜索。
- 每个分片其内部都是一个全功能且独立的索引,因此可由集群中的任何主机存储。
- 创建索引时,用户可指定其分片的数量,默认数量为5个。
- 分片的数量只能在索引创建前指定,并且索引创建后不能更改。
- Shard有两种类型:primary和replica,即主shard及副本shard。
3.1.1 主分片(primary shard)
- 用于文档存储。
- 每个新的索引会自动创建5个Primary shard,当然此数量可在索引创建之前通过配置自行定义。
- 一旦创建完成,其Primary shard的数量将不可更改。
3.1.2 备份片(replica shard)
- 是Primary Shard的副本,用于冗余数据及提高搜索性能。
- 每个Primary shard默认配置了一个Replica shard,但也可以配置多个,且其数量可动态更改。
- ES会根据需要自动增加或减少这些Replica shard的数量。
副本的作用:
- 提高系统的容错性,当个某个节点某个分片损坏或丢失时可以从副本中恢复。
- 提高es的查询效率,es会自动对搜索请求进行负载均衡。
3.2 负载均衡
- 每个索引被分成了5个分片;
- 每个分片有一个副本;
- 5个分片基本均匀分布在datanode节点上;
如果其中一个datanode节点故障, 剩余的分片将会重新进行均衡分配。
3.3 故障探测
ES有两种集群故障探查机制:
- 通过master进行的,master会ping集群中所有的其他node,确保它们是否是存活着的。
- 每个node都会去ping master来确保master是存活的,否则会发起一个选举过程。
有下面三个参数用来配置集群故障的探查过程:
ping_interval : 每隔多长时间会ping一次node,默认是1s
ping_timeout : 每次ping的timeout等待时长是多长时间,默认是30s
ping_retries : 如果一个node被ping多少次都失败了,就会认为node故障,默认是3次
3.4 索引存储
3.4.1 不可变性
- 倒排索引被写入磁盘后是不可改变的,它永远不会修改。
为什么索引被写入磁盘后不可改变?有什么优点和缺点?
优点:
- 提升查询性能。 一旦索引被读入内核的文件系统缓存,便会留在那里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。
缺点:
- 想修改一个Doc,那就必须重建整个待排索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。
解决方案:
- ES解决不变形和更新索引的方式是使用多个索引,利用新增的索引来反映修改,在查询时从旧的到新的依次查询,最后来一个结果合并。这就是段产生的由来。
3.4.2 存储结构
- ES底层是基于Lucene,最核心的概念就是Segment(段),每个段本身就是一个倒排索引。
- ES中的 Index 由多个段的集合和 commit point (提交点)文件组成。
- 提交点文件中有一个列表存放着所有已知的段。
3.4.3 存储流程
- 当一个写请求发送到 es 后,es 将数据写入 memory buffer 中,并添加事务日志( translog ),此时写入的数据还不能被查询到。
- 默认设置下,es 每1秒钟将 memory buffer 中的数据 refresh 到 Linux 的 File system cache ,并清 空 memory buffer,此时写入的数据就可以被查询到了。
- 默认设置下,es 每30分钟调用 fsync 将 File system cache 中的数据 flush 到硬盘。
- translog 默认设置下,每一个 index 、delete、 update 或 bulk 请求都会直接 fsync 写入硬盘。
问:为什么需要File System cache ?
答:如果每次一条数据写入内存后立即写到硬盘文件上,由于写入的数据肯定是离散的,因此写入硬盘的操作也就是随机写入了。硬盘随机写入的效率相当低,会严重降低es的性能。
问:问什么需要Translog?
答:File system cache 依然是内存数据,一旦断电,则 File system cache 中的数据全部丢失。因此需要通过translog 来保证即使因为断电 File system cache 数据丢失,es 重启后也能通过日志回放找回丢失的数据。
为了保证 translog 不丢失数据,在每一次请求之后执行 fsync 确实会带来一些性能问题。对于一些允许丢失几秒钟数据的场景下,可以通过设置 index.translog.durability 和index.translog.sync_interval 参数让 translog 每隔一段时间才调用 fsync 将事务日志数据写入硬盘。
3.5 DOC操作
3.5.1 doc新增
Doc会先被搜集到内存中的Buffer内,这个时候还无法被搜索到,每隔一段时间,会将buffer提交,在flush磁盘后打开新段使得搜索可见,详细过程如下:
1)创建一个新段(segment),作为一个追加的倒排索引,写入到磁盘(文件系统缓存)
2)将新的包含新段的Commit Point(提交点)写入磁盘(文件系统缓存)
3)磁盘进行fsync,主要是将文件系统缓存中等待的写入操作全部物理写入到磁盘,保证数据不会在发生错误时丢失
4)这个新的段被开启, 使得段内文档对搜索可见
5)将内存中buffer清除,又可以把新的Doc写入buffer了
通过这种方式,可以使得新文档从被索引到可被搜索间的时间间隔在数分钟,但是还不够快。因为磁盘需要 fsync ,这个就成为性能瓶颈。我们前面提到过Doc会先被从buffer刷入段写入文件系统缓存(很快),那么就自然想到在这个阶段就让文档对搜索可见,随后再被刷入磁盘(较慢)。
3.5.2 refresh
Lucene支持对新段写入和打开-可以使文档在没有完全刷入硬盘的状态下就能对搜索可见,而且是一个开销较小的操作,可以频繁进行。 这种对新段的巧妙操作过程被称为refresh,默认执行的时间间隔是1秒,这就是ES被称为近实时搜索的原因。
可以使用refreshAPI进行手动操作,但一般不建议这么做。还可以通过合理设置refresh_interval在近实时搜索和索引速度间做权衡。
3.5.3 doc删除(修改)
- 删除一个ES文档不会立即从磁盘上移除,它只是被标记成已删除。
- 因为段是不可变的,所以文档既不能从旧的段中移除,旧的段也不能更新以反映文档最新的版本。
- ES的做法是,每一个提交点包括一个.del文件(还包括新段),包含了段上已经被标记为删除状态的文档。
- 当一个文档被做删除操作,实际上只在.del文件中将该文档标记为删除,依然会在查询时被匹配到,只不过在最终返回结果之前会被从结果中删除。
- ES将会在用户之后添加更多索引的时候,在后台进行要删除内容的清理。
问题:而且每秒自动刷新创建新的段,用不了多久段的数量就爆炸了,每个段消费大量文件句柄,内存,cpu资源。更重要的是,每次搜索请求都需要依次检查每个段。段越多,查询越慢。
解决方案:ES通过后台合并段解决这个问题。
3.5.3 段合并
ES利用段合并的时机来真正从文件系统删除那些version较老或者是被标记为删除的文档。被删除的文档(或者是version较老的)不会再被合并到新的更大的段中。
ES对一个不断有数据写入的索引处理流程如下: 索引过程中,refresh会不断创建新的段,并打开它们。 合并过程会在后台选择一些小的段合并成大的段,这个过程不会中断索引和搜索。
段合并之前,旧有的Commit和没Commit的小段皆可被搜索。
段合并后的操作: 新的段flush到硬盘 编写一个包含新段的新提交点,并排除旧的较小段。 新的段打开供搜索 旧的段被删除 合并完成后新的段可被搜索,旧的段被删除。
3.6 集群路由
- 客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点);
- coordinating node,对document进行路由,将请求转发给对应的node(有primary shard);
- 实际的node上的primary shard处理请求,然后将数据同步到replica node;
- coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端。
3.6.1 document如何路由
路由算法公式:shard = hash(routing)%number_of_primary_shards。
例子: 一个索引index ,有3个primary shard : p0,p1,p2 增删改查 一个document文档时候,都会传递一个参数 routing number, 默认就是document文档 _id,(也可以手动指定) Routing = _id,假设: _id = 1 算法: Hash(1) = 21 % 3 = 0 表示 请求被 路由到 p0分片上面。
自定义路由请求:
PUT /index/item/id?routing = _id (默认)
PUT /index/item/id?routing = user_id(自定义路由)---- 指定把某些值固定路由到某个分片上面。
primary shard不可变原因 即使加服务器也不能改变主分片的数量
3.7 倒序索引
例子:表的数据如下
ID | Name | Age | Sex |
---|---|---|---|
1 | Kate | 24 | Female |
2 | John | 24 | Male |
3 | Bill | 29 | Male |
ES 存储如下:
Name:
Term | Posting List |
---|---|
Kate | 1 |
John | 2 |
Bill | 3 |
Age:
Term | Posting List |
---|---|
24 | [1,2] |
29 | 3 |
Sex:
Term | Posting List |
---|---|
Female | 1 |
Male | [2,3] |
3.7.1 Posting List
- Elasticsearch分别为每个field都建立了一个倒排索引;
- Kate, John, 24, Female这些叫term;
- 而[1,2]就是Posting List;
- Posting list就是一个int的数组,存储了所有符合某个term的文档id。
3.7.2 Term Dictionary
问题:通过posting list这种索引方式似乎可以很快进行查找,但是,如果这里有上千万的记录呢?如果是想通过name来查找呢?
答案:Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样,这就是Term Dictionary。
3.7.3 Term Index
问题:Term Dictionary看起来似乎和传统数据库通过B-Tree的方式类似啊,为什么说比B-Tree的查询快呢?
答案:B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树:
这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
所以term index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。
3.7.4 FST(Finite State Transducers)
FSTs are finite-state machines that map a term (byte sequence) to an arbitrary output。
- ⭕️表示一种状态
- –>表示状态的变化过程
- 字母/数字表示状态变化和权重
- 将单词分成单个字母通过⭕️和–>表示出来,0权重不显示。
- 如果⭕️后面出现分支,就标记权重;
- 最后整条路径上的权重加起来就是这个单词对应的序号。
FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源。
3.7.5 Frame Of Reference
增量编码压缩,将大数变小数,按字节存储。
首先,Elasticsearch要求posting list是有序的,这样做的一个好处是方便压缩。
原理就是通过增量,将原来的大数变成小数仅存储增量值,再精打细算按bit排好队,最后通过字节存储,而不是大大咧咧的尽管是2也是用int(4个字节)来存储。
还有一种压缩技术:Roaring bitmaps
3.7.5 联合索引
多个field索引的联合查询,倒排索引如何满足快速查询的要求呢?
- 利用跳表(Skip list)的数据结构快速做“与”运算。
- 利用bitset按位“与”。
跳表的数据结构
将一个有序链表level0,挑出其中几个元素到level1及level2,每个level越往上,选出来的指针元素越少,查找时依次从高level往低查找,比如55,先找到level2的31,再找到level1的47,最后找到55,一共3次查找,查找效率和2叉树的效率相当,但也是用了一定的空间冗余来换取的。
如果使用bitset,就很直观了,直接按位与,得到的结果就是最后的交集。
ES基础语法
4.1 添加数据
PUT /humen/user/1
{"first_name" : "John","last_name" : "Smith","age" : 25,"about" : "I love to go rock climbing","interests": [ "sports", "music" ]
}
返回以下信息,表示创建成功。
{"_index": "humen","_type": "user","_id": "1","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 0,"_primary_term": 1
}
_index 为索引名称。
_type 为类型名称。
_version 为该文档的版本号,初始为 1,每次修改该文档时 _version 增加 1。
_seq_no 为索引的版本号,每次修改了索引下的文档,被修改的文档的 _seq_no 和索引当前的 _seq_no 都被更新为修改前索引 _seq_no + 1。
注意: 当请求方法为 POST,且修改前后数据完全一样时,文档的 _version 和 _seq_no 都不改变。
这种请求方式的写法为:POST /index/{index}/index/{type}/${_id}/_update,请求体格式为:
{"doc":{"first_name": "Bob"}
}
_id 为添加数据时自己设置的,如上 PUT /humen/user/1 中的 1 就是自己设置的字段 _id,也可以在添加字段的时候不设置 _id 值,这时系统会默认返回一个字符串类型的 _id 值。
注意: 此时请求方式不能为 PUT,应当为 POST,否则会返回 405 报错。
4.2 查询数据
查询索引为 humen下的全部文档。
GET /humen/_search
查询索引为 humen 下的类型名为 user 的全部文档。
GET /humen/user/_search
根据 _id 来准确查询文档。
GET /humen/user/_search/1
为查询方法加上参数,参数的 key 和 value 之间用 : 分隔。
GET /humen/user/_search?q=last_name:Smith
将查询参数放在请求体中。
GET /humen/user/_search
{"query" : {"match" : {"last_name" : "Smith"}}
}
4.3 更新数据
更新数据和插入数据又可以用 PUT 和 POST 两种请求方式,且两种请求方式都可以用一下格式来更新数据:
- PUT/POST /humen/user/_id
请求方法体同插入数据, 逻辑上等同于将用数据将原数据覆盖掉。如:
{"first_name" : "John","last_name" : "Smith","age" : 25,"about" : "I love to go rock climbing","interests": [ "sports", "music" ]
}
- POST 还可以通过 POST /megacorp/employee/_id/_update
格式来修改数据,此时请求体格式应当为:
{"doc":{"first_name": "Taoqiang"}
}
将要修改的字段和新的值放在 doc 中,多个字段用逗号分隔。
4.4 删除数据
- 删除整个索引的数据:DELETE /humen
- 删除指定 _id 的数据:DELETE /humen/user/_id
正确删除后返回以下类型数据:
{"_index": "megacorp","_type": "employee","_id": "5","_version": 2,"result": "deleted","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 11,"_primary_term": 1
}
4.5 排序分页
GET /test2/product/_search
{"query": {"match": {"name": "test"}},"sort": [{"age": {"order": "desc"}}],#从第几条开始"from": 0,#获取几条数据"size": 2#from + size其实就是limit from,size}
4.6基于Java代码查询
方法名 | 作用 |
---|---|
matchAllQuery | 匹配所有文档 |
queryStringQuery | 基于Lucene的字段检索 |
wildcardQuery | 通配符查询匹配多个字符,?匹配1个字符* |
termQuery | 词条查询 |
matchQuery | 字段查询 |
idsQuery | 标识符查询 |
fuzzyQuery | 文档相似度查询 |
includeLower includeUpper | 范围查询 |
boolQuery | 组合查询(复杂查询) |
SortOrder | 排序查询 |
4.6.1 查询所有
/***matchAllQuery()匹配所有文件match_all查询是Elasticsearch中最简单的查询之一。它使我们能够匹配索引中的所有文件* */
@Test
public void searchAll(){SearchResponse searchResponse = client.prepareSearch("humen").setTypes("user").setQuery(QueryBuilders.matchAllQuery()).get(); // 获取命中次数,查询结果有多少对象SearchHits hits = searchResponse.getHits(); System.out.println("查询结果" + hits.getTotalHits() + "条");Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.2 解析查询字符串
/*** 相比其他可用的查询,query_string查询支持全部的Apache Lucene查询语法针对多字段的query_string查询* */
@Test
public void query_String(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.queryStringQuery("孙尚香")).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.3 通配符查询(wildcardQuery)
/*** *匹配多个字符,?匹配1个字符 注意:避免* 开始, 会检索大量内容造成效率缓慢* */
@Test
public void wildcardQuery(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.wildcardQuery("address", "广东*")).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.4 词条查询(termQuery)
/*** 词条查询是Elasticsearch中的一个简单查询。它仅匹配在给定字段中含有该词条的文档,而且是确切的、未经分析的词条* */
@Test
public void termQuery(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.termsQuery("name", "张飞","刘备","关羽")).get(); // 获取命中次数,查询结果有多少对象SearchHits hits = searchResponse.getHits();Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.5 字段匹配查询
/*** match query搜索的时候,首先会解析查询字符串,进行分词,然后查询 。而term query,输入的查询内容是什么,就会按照什么去查询,并不会解析查询内容,对它分词。* multiMatchQuery("text", "field1", "field2"..); 匹配多个字段* */
@Test
public void MatchQuery(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.matchQuery("address", " 上海")).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象System.out.println("查询结果有:" + hits.getTotalHits() + "条");Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.6 只查询ID(标识符查询)
/*** 按照id进行查询,通过id返回我们想要的结果* */
@Test
public void idsQuery() {SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.idsQuery().addIds("AWNkQSCJzU0_wTuf7egi")).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.7 相似度查询
/*** 相似度查询:fuzzy查询是模糊查询中的第三种类型,它基于编辑距离算法来匹配文档* */
@Test
public void fuzzyQuery(){SearchResponse searchResponse = client.prepareSearch("tt1").setTypes("doc1").setQuery(QueryBuilders.fuzzyQuery("address", "北京市")).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象System.out.println("查询结果有:" + hits.getTotalHits() + "条");Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.8 范围查询
includeLower(true):包含上界IncludeUpper(true):包含下界/**范围查询使我们能够找到在某一字段值在某个范围里的文档,字段可以是数值型,也可以是基于字符串的includeLower(true):包含上界IncludeUpper(true):包含下界* */
@Test
public void rangeQuery(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.rangeQuery("age").from(18).to(22) .includeLower(true).includeUpper(false)).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.9 组合查询(复杂查询)
/** 组合查询:* must(QueryBuilders) : ANDmustNot(QueryBuilders): NOTshould(QueryBuilders):OR
* */@Testpublic void boolQuery(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("address", "程序员")).must(QueryBuilders.termQuery("male", "女"))).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}}
4.6.10 排序查询
/*** ASC : 正序(从小到大)* DESC: 倒序(从大到小)* */
@Test
public void SortOrderQuery(){SearchResponse searchResponse = client.prepareSearch("sanguo").setTypes("dahan").setQuery(QueryBuilders.matchAllQuery()).addSort("age", SortOrder.ASC).get();SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象Iterator<SearchHit> iterator = hits.iterator();while (iterator.hasNext()) {SearchHit next = iterator.next();System.out.println("===============================================");}
}
4.6.11 其他
// 高亮设置
private static final String HIGH_LIGHT_PRE_TAG = "<span style=\"color: #F56B6B\">";
private static final String HIGH_LIGHT_POST_TAG = "</span>";
HighlightBuilder highlightBuilder = new HighlightBuilder().field("field1").field("field2").field("field13").preTags(HIGH_LIGHT_PRE_TAG).postTags(HIGH_LIGHT_POST_TAG);queryBuilder.withHighlightBuilder(highlightBuilder);
分词器
5.1 简介
ES的分词器(Analyzer)一般由三种组件构成:
- character filter (字符过滤器):在一段文本分词之前,先进行预处理,比如说最常见的就是 【过滤html标签】,hello --> hello,I & you --> I and you
- tokenizers (分词器):默认情况下,英文分词根据空格将单词分开;中文分词按单字隔开,也可以采用机器学习算法来分词。
- Token filters (Token过滤器):将切分的单词进行加工,大小写转换,去掉停用词(例如“a”、“and”、“the”等等 ),加入同义词(例如同义词像“jump”和“leap”)
三者的顺序:Character Filters—>Tokenizer—>Token Filter
三者个数:Character Filters(0个或多个) + Tokenizer + Token Filters(0个或多个)
5.2 分类
- Standard Analyzer - 默认分词器,英文按单词切分,小写处理,过滤标点符号。
- Simple Analyzer - 按照单词切分(符号被过滤), 小写处理,中文按照标点符号(逗号、句号、包括空格等等)进行分词
- Stop Analyzer - 小写处理,停用词过滤(the,a,is)
- Whitespace Analyzer - 按照空格切分,不转小写 , 不去掉标点符号
- Keyword Analyzer - 不分词,直接将输入当作输出
- 中文分词器: smartCN、IK 等,推荐的就是 IK分词器。需要安装IK。
5.3 指定分词器
创建索引的时候就要指定分词器
PUT /索引名
{"settings": {},"mappings": {"properties": {"title":{"type": "text","analyzer": "standard" //显示指定分词器}}}
}
5.4 IK分词器
5.4.1 分词划分
IK分词器有两种粒度划分:
- ik_smart: 会做最粗粒度的拆分
- ik_max_word: 会将文本做最细粒度的拆分
POST /_analyze
{"text":"中华民族共和国国歌","analyzer":"ik_smart"
}{"tokens" : [{"token" : "中华民族","start_offset" : 0,"end_offset" : 4,"type" : "CN_WORD","position" : 0},{"token" : "共和国","start_offset" : 4,"end_offset" : 7,"type" : "CN_WORD","position" : 1},{"token" : "国歌","start_offset" : 7,"end_offset" : 9,"type" : "CN_WORD","position" : 2}]
}
POST /_analyze
{"text":"中华民族共和国国歌","analyzer":"ik_max_word"
}{"tokens" : [{"token" : "中华民族","start_offset" : 0,"end_offset" : 4,"type" : "CN_WORD","position" : 0},{"token" : "中华","start_offset" : 0,"end_offset" : 2,"type" : "CN_WORD","position" : 1},{"token" : "民族","start_offset" : 2,"end_offset" : 4,"type" : "CN_WORD","position" : 2},{"token" : "共和国","start_offset" : 4,"end_offset" : 7,"type" : "CN_WORD","position" : 3},{"token" : "共和","start_offset" : 4,"end_offset" : 6,"type" : "CN_WORD","position" : 4},{"token" : "国","start_offset" : 6,"end_offset" : 7,"type" : "CN_CHAR","position" : 5},{"token" : "国歌","start_offset" : 7,"end_offset" : 9,"type" : "CN_WORD","position" : 6}]
}
5.4.2 扩展词和关键词
- 扩展词:就是有些词并不是关键词,但是也希望被ES用来作为检索的关键词,可以将这些词加入扩展词典。
- 停用词:就是有些关键词,我们并不想让他被检索到,可以放入停用词典中。
设置扩展词典和停用词典在es容器中的config/analysis-ik目录下的IKAnalyzer.cfg.xml中
- 修改vim IKAnalyzer.cfg.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.dic</entry><!--用户可以在这里配置自己的扩展停止词字典--><entry key="ext_stopwords">ext_stopword.dic</entry></properties>
在es容器中
config/analysis-ik
目录下中创建ext_dict.dic文件 编码一定要为UTF-8才能生效
vim ext_dict.dic 加入扩展词即可在es容器中
config/analysis-ik
目录中创建ext_stopword.dic文件
vim ext_stopword.dic 加入停用词即可重启es生效。
ES 深度翻页问题解决方案
ES提供了3中解决深度翻页的操作,分别是scroll、sliced scroll 和 search after。
6.1 scroll
scroll api提供了一个全局深度翻页的操作, 首次请求会返回一个scroll_id,使用该scroll_id可以顺序获取下一批次的数据;scroll 请求不能用来做用户端的实时请求,只能用来做线下大量数据的翻页处理,例如数据的导出、迁移和_reindex操作,还有同一个scroll_id无法并行处理数据,所以处理完全部的数据执行时间会稍长一些。
例如:
POST /twitter/_search?scroll=1m
{"size": 100,"query": {"match" : {"title" : "elasticsearch"}}
}
其中scroll=1m是指scroll_id保留上下文的时间。
首次请求会返回一个scroll_id,我们根据这个值去不断拉取下一页直至没有结果返回:
POST /_search/scroll
{"scroll" : "1m", "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
针对scroll api下,同一个scroll_id无法并行处理数据的问题,es又推出了sliced scroll,与scroll api的区别是sliced scroll可以通过切片的方式指定多scroll并行处理。
6.2 sliced scroll
sliced scroll api 除指定上下文保留时间外,还需要指定最大切片和当前切片,最大切片数据一般和shard数一致或者小于shard数,每个切片的scroll操作和scroll api的操作是一致的:
GET /twitter/_search?scroll=1m
{"slice": {"id": 0, "max": 2 },"query": {"match" : {"title" : "elasticsearch"}}
}
GET /twitter/_search?scroll=1m
{"slice": {"id": 1,"max": 2},"query": {"match" : {"title" : "elasticsearch"}}
}
因为支持并行处理,执行时间要比scroll快很多。
6.3 search after
上面两种翻页的方式都无法支撑用户在线高并发操作,search_after提供了一种动态指针的方案,即基于上一页排序值检索下一页实现动态分页:
GET twitter/_search
{"size": 10,"query": {"match" : {"title" : "elasticsearch"}},"sort": [{"date": "asc"},{"tie_breaker_id": "asc"} ]
}
因为是动态指针,所以不需要像scroll api那样指定上下文保留时间了。
通过上一页返回的date + tie_breaker_id最后一个值做为这一页的search_after:
GET twitter/_search
{"size": 10,"query": {"match" : {"title" : "elasticsearch"}},"search_after": [1463538857, "654323"],"sort": [{"_score": "desc"},{"tie_breaker_id": "asc"}]
}
EleasticSearch相关推荐
- 数据治理展示血缘关系的工具_Nebula Graph 在微众银行数据治理业务的实践
本文为微众银行大数据平台:周可在 nMeetup 深圳场的演讲这里文字稿,演讲视频参见:B站 自我介绍下,我是微众银行大数据平台的工程师:周可,今天给大家分享一下 Nebula Graph 在微众银行 ...
- Ubuntu 16.04下部署Graylog日志服务器
Graylog 是一个开源的日志管理系统,集中式收集.索引.分析其它服务器发来的日志.它是由 Java 语言编写的,能够接收 TCP.UDP.AMQP 协议发送的日志信息,并且使用 Mongodb 做 ...
- elasticsearch v6.5.4配置
elasticsearch是一款知名的开源全文搜索引擎,应用广泛,因项目需要,需要使用elasticsearch满足应用内搜索,地图搜索.目前还在线上试运营,根据自己的使用部署过程,分享一下经验,梳理 ...
- Elasticearch 安装 基础介绍 (一)
文章目录 1 介绍 2 ELK安装 2.1 Elasticearch安装 2.1.1 windows 安装 2.1.2 linxu安装 2.2 可视化界面 elasticsearch-head 2.3 ...
- python接口 同花顺_利用python探索股票市场数据指南
虽然同花顺之类的金融理财应用的数据足够好了,但还是有自己定制的冲动, 数据自然不会不会比前者好很多,但是按照自己的想法来定制还是不错的. 目标 通过免费的数据接口获取数据,每日增量更新标的历史交易数据 ...
- Elastic:配置 Elasticsearch 服务器 logs
当我们运行 Elasticsearch 集群时,我们可以看到一些 Elasticsearch 的日志,比如我们可以在如下的目录中找到相应的一些 server 日志: <cluster name& ...
- ELK之logstash
下载安装(Redhat/Centos7) rpm --import http://packages.elasticsearch.org/GPG-KEY-elasticsearch cat > / ...
- 【ElasticSearch系列】ES插件安装
上篇文章介绍了一下EleasticSearch以及安装,这篇文章继续,将介绍ES的插件安装. 其实最开始我也不知道要安装什么插件,其实也疑惑,为什么ES不将需要的插件集成到自身,这样就能避免很多问题. ...
- Docker学习文档(个人向)
Docker日常使用文档 1.为什么是docker 在开发的时候,在本机测试环境可以跑,生产环境跑不起来 这里我们拿java Web应用程序举例,我们一个java Web应用程序涉及很多东西,比如jd ...
最新文章
- 【工具类】分布式文件存储-FastDFS
- Web Service概念梳理
- [BZOJ1030] [JSOI2007]文本生成器
- mybatis-plus的概念
- file 创建 txt文件
- 移动边缘计算与计算卸载概述
- MSM8953 Android9.0 配置USB2.0 Camera
- 免费url长网址缩短压缩工具评测,短链接在线生成器推荐。
- Matlab中的ismember和contains傻傻分不清
- 【普通人VS程序员】电脑还可以这样关机,神操作,学到了学到了~(爆赞)
- 用Qt在Iinux上开发一个带UI的工业控制系统,应该用C++还是QML
- Go 语言 入门 基于 GoLand 2023.1 创建第一个Go程序
- 冬季12种食物减肥巧妙止饿
- 深入理解同步工具类,看这篇就够了
- 信息学奥林匹克,精品大学AP课程
- xd生成html,7款Adobe XD必备插件
- windows下nginx配置OpenSSL自签名证书
- 小程序复制文字、保存图片
- qs.js库 使用方法
- 1710AL电台参数设置流程
热门文章
- 洛伦茨曲线半高全宽_关于MatLab如何取某变函数半高全宽(FWHM)并Plot的问题
- java命令截图,GitHub - xuege-cn/fmj: FMJ (FFMpeg for Java)。通过Java调用FFMpeg命令的方式来对音视频进行处理(获取信息、截图等等)。...
- GDOI2018 游记
- 原创 | 国内首批工业互联网十大双跨平台建设现状如何?
- 面向对象的五大基本原则
- 【沃顿商学院学习笔记】商业基础——Financing:04通货膨胀 Inflation
- 竟还有如此沙雕的代码注释!我笑喷了
- 计算机组成原理:区分机器字长、存储字长、指令字长 | 位、字节、字与字长
- 数据结构之逻辑结构和物理结构
- 南理工cs夏令营面试