昨天有一个需求,就是想要根据某个网关url做过滤,获取其下面所有的上下文nginx日志;如果直接"query":"https://XXX/YYY/ZZZ"发现有问题,啥也查不出来,后来仁杰指出来需要使用“”括起来,果然这样就变成了前后匹配的模糊查询了。但是继续,我发现如果我指定了字段"query":"request:\"http://XXX/YYY/ZZZ\""就啥也返回不了了;但是如果是换位message字段则可以;后来发现原来是因为request的类型是keywords,message的类型是text,所以request必须是全匹配,message是模糊匹配。
第一部 分词
但是为什么需要添加“https://XXX/YY/ZZZ/”双引号呢?如果不添加会怎样,导致分词吗,如果分词正常应该可以查出来啊。后来发现之所以查不出来数据,是因为当时查询的querystirng最后一位带了"/"导致的,如果把最后一位的"/"给删掉就可以查出数据了;但是这个查询是经过ES的分词的,将会被解析为XXX,/YY/以及ZZZ,所以此时查询是分词查询;如果是想要不分词,进行完整匹配(ES不进行分词),需要添加双引号。如果获知分词的过程?就是通过index/_validate/query?explain,所谓的validate-query模式来进行查询解释。
通过validate-query模式的查询,你会发现如果直接查(不指定字段),将会是所有的字段对所有的分词进行笛卡尔积的匹配;所以构建的查询语句也是十分复杂,这里你就会发现指定字段重要性了。
第二部 索引
什么是索引?引用一下我之前的一篇博文来说明:
索引扮演的角色其实并不是存储,而是“索引”,看起来有点傻,但是其实我之前一直理解索引是存储,其实从命名上可以看出来,索引其实是分片的索引,分片的字典,记录了每个分片的位置,索引范围;当需要查询的时候,可以定位到对应的分片来进行数据操作;最后进行汇总。所以index本质作用就是记录分片;所谓查询,有向无环图(DAG)都是基于index来进行分析绘制的,然后基于该图下放数据操作。
索引模板,就是指定了索引的属性,包括分片数,副本数等物理属性以及字段名称以及字段类型等信息。索引模板指定了索引模式,一般都是前缀模式,例如:app_log-*,这样每当创建了一个app_log-开头的索引(比如app_log-2019-3-3),就会按照该模板的属性进行创建。
下面说一下索引的生命周期,这里包括热盘到冷盘,热盘到冷盘可以通过box_type来实现,创建的时候,指定分布部署的节点的类型(需要事先为每个集群节点设置节点类型),定时通过修改配置来对分片进行迁移到另外节点类型;
生命周期的最后操作是:删除,关闭以及归档三种,删除就是DELETE指令,关闭就是_close,这个除了会占用硬盘空间之外不会有别的影响,好处就是可以随时打开参与查询;最后一个是归档,可以将索引归档到S3,hdfs等存储引擎。这些归档文件将会被ES系统记录;此时可以将索引删除(导致分片一起删除);日后如果有需要可以通过_restore的api进行重新导入。
第三部 查询和解析
字段配置中有一个属性:index,默认是“analyzed”,代表存入文档后,将会被分词保存,同时统一转化为小写;另外一个值是not_analyzed,这样的文档里面保存的就是原始的文档内容。是的,所有的这一切都是5.0之前的配置,5.0之后,string+analyzed被text替换,string+not_analyzed被keyword替换,完美。
term查询
代表完全匹配,什么是完全匹配,是指索引保存的分词中的一个能够完全一致,和分词中的一个完全一样(不是模糊匹配);term查询不会对查询字符串进行分词;这里牵涉到term查询中指定的字段类型是keyword还是text,如果是keyword,那么分词只有一个,就是文档全文本身,此时可以理解为有的字段,在查询字符串必须要和文档全部一致才可以;如果是text,那么将会根据内置规则进行分词,此时term查询的匹配就是要能够匹配分词后的一个即可;
match查询
match查询则是会对查询字符串进行分词,然后和索引的字段分词进行逐个匹配(又是一个笛卡尔积)。所以,你会发现其实ES查询本质面对的分词,文档其实只是一个PUT的概念,一旦存入了ES之后,就会被进行分解为分词(除非keyword,唯一的分词是文档本身)。下一个是match_phase,这个查询和match一样都是解析分词查询,但是增加了所有分词必须都匹配,而且分词的顺序要一致(可以指定距离slop)。
query_string查询
那么match和query_string的差别在哪里?在于后者有谓词表达式。其实如果是没有谓词表达式,那么query_string就是和match一样,如果没有谓词,查询字段用双引号包裹,其实就和match_phrase是一样的,query_string的强大就在于一个查询可以完成match和match_phrase的统一,同时还额外提供了谓词表达式做多条件匹配。
第四部 文档写入ES流程
逐个流程可以被进一步细化:PUT了一个文档之后,首先将会进行字段级别处理,一般是在logstash中进行,对于文档中的字段通过正则等规则进行提取,提取的目的是填充到各个字段;然后对于各个字段里面保存的内容,其实是所提取出来的子文档的分词,是的,字段保存的是分词,不再内容或者子内容,而是分词。文档的概念也不是原始的字符串,而是所有的字段以及字段分词的抽象;那么查询的时候其实都是基于对于查询字符串的分词(可能是不解析,比如使用“”,那就是一个分词)和字段分词(可能是keyword,那么就只有一个分词)进行匹配;查询字符串的解析有良心的使用者指定一下字段,没良心的不指定,那么就是所有的查询分词和所有的字段分词进行笛卡尔积形式的匹配,如果匹配的上,那么该分词所在的文档就被返回。
第五部 总结和其他
索引里面存的分片信息;分片里面的存的是文档信息,文档里面存的字段信息,字段里面存的是分词信息。基本就是这么个层级关系;为什么要指定这么多层,就是为了能够细粒度定位数据,减少查询的面积。
_all字段是一个虚拟字段,它将会打破字段这个层级,将所有的字段的分词都集成到一起,形成一个新的虚拟字段:_all。设置_all是一个字段级别的设置,在每个字段下面设置_all是否可用,因为会损耗性能,所以默认是关闭。但是_all在6.0之后将会被废弃,类似功能由copy_to参数来实现。