终于有时间记录一下最近学习的知识了,其实除了写下的这些还有很多很多,但懒得一一写下了;
ElasticSearch添加修改删除原理:
ElasticSearch的倒排索引和文档一旦生成就不允许修改(其实这是lucene的特性,包括下面的也是,毕竟ElasticSearch是基于lucene的),而提供的修改操作其实是新生成了一个文档,并将之前文档中的不进行修改的json保存到新的文档中,之后对老的文档添加一个删除标记(是添加标记,但并不删除,不过你也访问不到),等到某个时刻就会统一删除掉所有的有删除标记的文档,具体是什么时候看下文;
而在新添文档的时候,是先把文档存到一个在内存中的一个缓冲区in-memory buffer里,并且每一秒生成一个新的段(Segment)并将
这个缓冲区中的文档生成倒排索引并存入(此时是在内存中生成一个段(segment,一个分片中保存的倒排索引是分布式存于多个段中的),并存入,这个时间是可以修改的,也可以修改为-1,永远不会自动添加,直到我们手动调用_reflush接口),最后提交(也就是和commit point(一个分片就是一个lucene实例,而每个实例都对应一个commit point,这是lucene维护的一个保存着每个可用的Segment的信息)相关联),
这样就能搜索,之后会清空缓存,等待新的文档数据存入;
再回到更新的问题上,如何保证当我们搜寻的时候是我们更改后的数据呢,因为每个commit point维护着一个.del文件,里面记录了每个文档的删除和修改,这样在我们搜寻数据的时候,就会通过.del文件自动把老的数据过滤掉,只返回新的文档数据;
同时为了避免Segment产生太多影响搜索效率,ElasticSearch会定期的将多个小的Segment合并成一个大的Segment.并且在合并的过程中,会根据.del文件将老的文档丢弃掉;
在es中,可以使用forcemerge接口,来控制segment的合并。如: POST/logstash-2014-10/_forcemerge?max_num_segments=1(也可以人为控制合并,比如在晚上的时候,服务器性能空闲期间进行合并)
那么问题来了,如何保证在出现意外情况下,我们的文件缓冲区的数据的可靠性呢?这个就要靠我们Es维护的Translog日志文件了,在我们添加或更新或删除文档的时候,会fsync到我们的日志文件上(也可以设置成异步,不过不推荐,网上也有很多人做了实验,性能提高不明显),
之后在我们的Translog到512M的时候(这个是默认配置,可以更改,为时间单位),就会自动flush,然后将我们内存中segment文件的缓存数据进行刷盘(从内存刷新到硬盘上),当然我们也可以手动调用flush接口进行刷新,刷盘完成之后就会清空我们的Translog文件.
那么我们的ES是怎么根据Translog进行文件恢复的呢?它是根据CommitPoint文件中记录的segment(段)的记录,之后根据记录在我们的Translog文件里拿到对应的数据记录,再进行相对应的数据恢复;
ElasticSearch搜索查询:
如果查询的是日期或者整数,那么他们会将字符串作为日期或整数对待。
如果查询的是一个未分析的精确值字符串字段(比如,类型是keyword的话,是不分词的。),像我们存储邮编号,身份证号之类。它们会将整个查询字符串作为单个词项对待;
如果查询的是一个经过分词器解析过后的全文字段,它们首先会将查询字符串也经过分词器解析,然后生成一个词项列表再去对应着倒排索引找到对应的document;
queryString语法(下面的示例都是针对整个索引的,如果想缩小搜索查找范围,可以加上type字段,比如/school/highSchool):
1:全文检索:
GET /school/_search?q=zhangsan(如果查找的文档的相应字段进行过分词,那么也会将此搜索参数进行分词,zhangsan也就会变成zhang san)
2: 单字段全文检索:
GET /school/_search?q=name:zhangsan
3: 单字段精确检索:
GET /school/_search?q=mark:"good day"(查找mark字段的值里包含"good day"的文档并返回,这种的必须要包含good和day两个词并且位置也要相同)
4:多个检索条件的组合:
GET /school/_search?q=name:("zhangsan" OR "lisi") AND NOT course:spring (必须要name为"zhangsan"或"lisi"并且course不为spring)
5:判断字段是否存在:
GET /school/_search?q=_exists_:mark(返回存在mark字段的文档)
GET /school/_search?q=NOT _exists_:mark (返回不存在mark的字段)
6:通配符:
用?表示单字符,*表示任意个字符
GET /school/_search?q=name:zh???san (返回开头是zh,结尾是san,忽略中间三个字符)
GET /school/_search?q=name:zh*san (返回开头是zh,结尾是san,忽略中间任意个字符)
queryString语法也可以使用正则表达式进行搜索查询,但一般不推荐使用,麻烦且效率低
match查询步骤:
1:检查作为搜索条件的字段的字段类型
2:分析查询字符串(如果查找的字段不是分词的字段,那么搜索参数也不会进行分词)
3:查找匹配字段
4:为每个文档评分(评分是根据,你要搜索的参数条件在文档中出现的频率结合一些其它条件作为判断然后用一个评分算法来算出(这是lucene的功能),如果没有特定需求(也就是排序),可以关掉评分功能,以提高性能)
match_all搜索查询
1.空查询,查询该索引下的所有的文档
GET school/_search
{
"query":{
"match_all":{}
}
}
2.不匹配任何文档
GET school/_search
{
"query":{
"match_none":{}
}
}
3.根据单个or多个字段查询
GET school/_search
{
"query":{
"match":{
"mark":"Day"
"name":"zhangsan"
}
}
}(查找school索引下mark字段的包含Day的文档并返回)
match_phrase短语匹配:
GET school/_search
{
"query":{
"match_phrase":{
"mark":"good day",
"slop":1(slop的参数意思为只要如果good day中间有一个词的话那么就忽略,依然会返回这个文档)
}
}
}
执行步骤:
1.分析查询字符串,分解成词项;(因为是短语)
2.查找匹配文档(只要文档中的被搜索字段拥有分解后的词项列表的其中一个词,那么条件就会成立)
3.将匹配后的字段再进行两次过滤,一次是必须拥有词项列表中所有的搜索参数,一个是必须位置也相同;
可以用slop指定词项间间隔的范围
match_phrase_prefix短语前缀匹配:
GET school/_search
{
"query":{
"match_phrase_prefix":{
"mark":"good day",(此字段前缀必须为good day)
"slop" 1,(如果good day中间有一个词,那么忽略,依然返回此文档)
"max_expansions":10(此参数意为返回前10个)
}
}
}
执行步骤:
1.分析查询字符串,分解成词项;(因为是短语,当然也可以输入单个字母,比如a)
2.查找匹配文档(文档中的被搜索字段的前缀必须是短语中的词列表,且位置相同;有slop参数除外)
Multi_Match多个字段上进行Match匹配:
GET school/_search
{
"query":{
"multi_match":{
"query":"elasticsearch",
"fields":["mark","co*"]
}
}
}
(查询school索引下,所有type中,mark字段,和co开头的字段的值包含elastisearch的文档(是否将elasticsearch分词,先看搜索的字段,如果搜索的字段是经过分词的,那么再视此字段的分词器而定,因为有可能分词器会将elasticsearch视为一个完整的单词,而不进行分词))
term精确查询:
GET school/_search
{
"query":{
"term":{
"mark":"happy day"
}
}
}(term查询不会对查询参数进行分词,不管要查询的字段是否经过分词,如果你要查的字段的字段类型为text,并且你的搜索参数还是多个词组成的词,如示例中的happy day,那么你是查不出任何文档的,因为在text中是默认会分词的,那么happy day在倒排索引中就已经被分词了)
terms多参数精确查询:
GET school/_search{
"query":{
"terms":{
"name":["zhangsan","lisi"]
}
}
}(同样对于输入的查找参数不进行分析)
range范围查询:
GET school/_search{
"query":{
"range":{
"age":{
"gte":30,(大于等于30)
"lte":20(小于等于20)
}
}
}
}(大于的话是gt(grate than),小于是lt(less than))
还可以指定日期类型的字段作为搜索条件(因为日期在ElasticSearch内部存储的时候是存储为long型的)
GET school/_search{
"query":{
"range":{
"study_date":{
"gte":"2018-2-11",
"lte":"2019",
"format":"yyyy-MM-dd||yyyy"(自定义一下格式化方式,否则会按照默认的格式化方式来对日期进行格式化)
}
}
}
}
甚至还可以使用自带的/d/M/y一系列参数
GET school/_search{
"query":{
"range":{
"study_date":{
"gte":"now-10d/d",(大于等于现在的日期往前推十天,并且是当天的0点0分0秒)
"lte":"now+1M/M",(小于等于现在的日期往后推一个月,并且是那个月的最后一天的23时59分59秒)
"format":"yyyy-MM-dd"(自定义一下格式化方式,否则会按照默认的格式化方式来对日期进行格式化)
}
}
}
}(这种做法好处在于,不但固定查询条件,而且利用了ElasticSearch的缓存策略直接去拿缓存,减轻了服务器压力(ElasticSearch缓存策略和浏览器一样,只有你发的查询url完全一样,才会去拿缓存))
missing查询:
GET school/_search
{
"query":{
"bool":{
"must_not":{
"exists":{
"filed":"mark"
}
}
}
}
}
bool里所有的判断条件都是and与关系
fuzzy模糊查询:
GET school/student/_search
{
"query":{
"fuzzy":{
"name":{
"value":"zhangsan",
"fuzziness":2(意思是可以忽略掉两个字符的误差,比如搜索条件name:zhangsi也会出现name里为zhangsan的文档)
}
}
}
}
搜索性能优化
1.查询和过滤,query和filter
filter拥有和query同样的功能,并且不需要进行评分,以及拥有缓存,query是查询"此文档与查询条件的匹配程度",而filter则是"此文档是否匹配查询条件"。所以多用filter;
2.尽量使用Bool组合代替AND OR
bool条件组合可以一次性添加多个条件,并且会把通过条件的文档保存在bitset中,直接做交集运算。而AND OR这种则是一个个文档进行处理,如果非用不可,那么请和sql语句一样,根据语句执行顺序,将过滤文档多的条件放在前面;
3.增加刷新时间
每次刷新ElasticSearch都会增加一个新的段(segment),并且会使缓存失效,所以尽量将刷新时间变长一点
4.增加Translog的flush时间
因为每次flush都会把未存储到硬盘中的segment中的数据刷到硬盘上,而这种与硬盘交互的IO操作是极其浪费性能的,所以在对于数据安全性要求不是非常高的情况下可以把Translog的刷新时间和大小调长和大一些。
5.做好冷热分层,最好能指定一下空间位置
每台服务器的性能都不一样,而这是需要我们自己手动去调整的,可以定义好匹配模板,将新加入的索引强制分配到那些性能较好的服务器上(也就是hot),也可以在某些时候定时的将一些索引迁移到性能居中的服务器上(也就是warm)。而针对服务器安全性方面,我们为了保证索引中同样两个分片不在同一个机房,机架上,可以强行指定zone;
6.不要使用swap虚拟内存,因为会导致性能比较慢,可以直接在elasticSearch.yml里禁用
7.关于分页,不要使用size,from这种,因为它们会先到若干个数据节点中拿到所有的分页数据,之后在协调节点上再进行过滤,比如10000,10010,我们只要后面十条,但是却会到若干个节点上(比如5个)都拿10010条数据,最后返回到协调节点上就是50050条数据。所以我们在分页上使用scroll这种游标的形式来进行分页;