日志平台总查不到日志,怎么办?运维大侠来支招~

2021-06-24 00:00:00 索引 查询 文档 过滤器 分词


笔者在一家银行科技部门工作。从2018年开始接触 Elasticsearch(以下简称ES),我所在的日志平台团队从ES5.x到ES7.x,对ES的两个大版本都经历了比较深入的实践。从初的日志检索,到后来的数据分析、智能监控,ES 在行内的应用越来越广泛。

目前,我们日志平台存储了全行近 1000 多个系统的日志,日均日志量达1600亿条,业务高峰期峰值数据写入速率可达 400w 条/秒,集群共15000余个索引,数据总量达万亿级,磁盘总消耗 1.5 PB。运维管理如此大规模的 ES 集群,无论在写入还是在查询方面势必会遇到各种各样的问题,需要灵活对配置做优化调整,以保证集群良好的性能。写入方面已经做过的优化涉及集群规划、角色选择、冷热数据隔离、线程池刷新时间等配置和参数调优。然而笔者本次遇到的问题,与 ES 查询有关。
近常常有项目组反馈在服务器上能够查询到系统错误日志,然而在ES中却无法根据关键字搜索到相应的错误日志信息。可以确定的是系统日志已经进入了ES,但是全文搜索却无法有效命中,这是什么原因呢?带着疑惑,我们对ES全文搜索原理进行了深入了解。

1.ES全文搜索原理

实现全文搜索目前主要有两种方法:顺序扫描法和倒排索引法。顺序扫描法就是按照顺序去扫描每个文档的内容,看看是否有要搜索的关键字,从而实现查找文档的功能。倒排索引法则是提前将要搜索的关键词都建成索引,然后根据索引词去查找文档。顺序扫描和倒排索引大的区别就是一个根据文档找词,一个根据索引词找文档。

1.1 倒排索引

一般而言,在数据库中直接全表查询的时间复杂度是 O(n),如果对索引列进行查询,其时间复杂度为O(logn),而数据以 key-value 的形式存储,查询时间复杂度将降为 O(1)。倒排索引则是在全文搜索中直接建立从查询词到文档的映射,从而将查询复杂度降到O(1),大大提高了查询性能。

一般创建倒排索引的流程如下,假如有3篇文档:
在经过分词算法以后得到了每篇文档所包含的词,即:

然后再构建从词汇到文档id的映射,终形成一个简单的倒排索引库:

1.2 数据索引写入过程

  • 创建文档对象
ES是面向文档型的数据库,一条数据在这里就是一个文档,用json作为文档序列化的格式。为了便于后续对文档数据进行分词分析处理,需要创建统一数据格式的文档。一个文档中包含多个字段,字段中存储内容。这里我们可以把数据库中的一条记录当成一个文档,一列当成一个字段。
  • 分析文档
分析文档主要是应用一些分词算法对字段内容进行分析,形成一个个的关键词,目的是方便索引。
  • 索引创建

将分析文档得到的一个个关键词传给索引组件。索引组件会根据这些词创建关键词与文档的映射关系,从而形成一个大的词典。词典的索引结构则是一种倒排索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合规模较大。

1.3 数据查询过程

  • 查询语句
直接输入关键词,多个查询关键词之间可以使用AND、OR、NOT来表示逻辑关系。如:java AND outofmemory NOT log。
  • 执行搜索

首先再次对关键词进行分词算法分析处理,将搜索语句拆分成一个个关键词并去掉关键词之间的停用词。结合关键字AND、NOT进行语法分析与语言处理,终得到一棵可直接执行搜索的语法树。根据语法树到不同的节点分片上进行搜索,并将所有返回的文档进行合并。
  • 根据得到的文档和查询语句的相关性,对结果进行排序。

1.4 分词

搜索引擎的核心是倒排索引,而倒排索引的基础就是分词。所谓分词可以简单理解为将一个完整的句子切割为一个个单词的过程。分词器的存在,使得ES的全文搜索,不仅可以返回的结果文档,同时可以返回所有与关键词相关的文档。一个分词器一般由三部分组成:字符过滤器,标记器和标记过滤器。
  • 字符过滤器
在一段文本进行分词之前,先进行预处理,比如说常见的就是,过滤html标签,将类似于<b>的字符从原始文本中剥离出去。
  • 标记器

接受来自字符过滤器的字符,将其分割成一个个独立的词,并将其输出。例如,进入标记器的字符为”QUICK brown fox!”,经过处理后输出的结果为三个词[quick,brown,box]。
  • 标记过滤器
将切分后的单词进行加工。例如,大写转换成小写,去掉停用词(如,”and”,”the”,”a”等)。
三者可以以不同个数来进行组合,从而形成符合业务场景的自定义分词器。例如:0个或多个字符过滤器+1个标记器+0个或多个标记过滤器。

2.解决问题

通过对ES全文搜索原理的了解,我们发现要想从根本上去解决问题,需要对分词进行优化。优化思路为在构建索引和查询时使用不同的分词算法。方案是,在索引写入的时候使用细粒度的分词以保证召回,在查询的时候使用粗粒度的分词以保证精度。

2.1 写入分词优化

大家都知道,ES默认分词器对中文是按单个字的粒度去进行分词的,因此在索引写入时只需要在默认分词器的基础上对英文分词进行优化,使其分词更加细粒度即可。

2.1.1 探索ES默认英文分词器

1、创建一个索引,并写入一个文档。
POST token_test_index-2021.05.26/_doc{  "message":"java.outofmemory"}

2、用 ES 默认分词器查看对 message 的分词情况。

POST token_test_index-2021.05.26/_analyze{   "analyzer":"standard",  "text": "java.outofmemory"}

返回值为:

{  "tokens" : [    {      "token" : "java.outofmemory",      "start_offset" : ,      "end_offset" : 18,      "type" : "<ALPHANUM>",      "position" :     }  ]}

从中可以看出 java.outofmemory 被作为一个词存入了ES里。

  1. 用关键词 outofmemory 从 ES 中进行搜索。

{  "took" : 3,  "timed_out" : false,  "_shards" : {    "total" : 3,    "succESsful" : 3,    "skipped" : ,    "failed" :   },  "hits" : {    "total" : {      "value" : ,      "relation" : "eq"    },    "max_score" : null,    "hits" : [ ]  }}
可以看出并没有搜索到想要的结果。那么如何才能通过搜索 outofmemory 也能搜索到想要的结果呢?我们可以通过构造自定义分词器来实现。

2.1.2 自定义英文分词器

默认分词器,不对以特殊字符“.”连接的词汇进行分词,然而很多系统打印的错误日志内容格式我们是无法提前知道的,只能通过关键词去搜索,分词的限制使得我们无法搜索到想要的结果。

通过对ES分词原理的了解,我们知道ES分词有字符过滤器,标记器和标记过滤器三部分。字符过滤器的作用是在一段文本进行分词之前,先进行预处理,将多余的字符去掉。在这里,我们尝试自定义一个字符过滤器,将不分词的特殊字符全部过滤掉。

1.自定义分词器 my_analyzer1,并将其应用于 token_test_index-* 索引模板
PUT _template/token_tESt_index{       "order" : 10,    "index_patterns" : [      "token_test_index-*"    ],    "settings":{        "refresh_interval" : "1s",        "analysis": {          "analyzer": {            "my_analyzer1":{              "type": "custom",                          "char_filter": [                "special_char_filter"                ],                "tokenizer":"standard"            }          },          "char_filter":{            "special_char_filter":{              "type": "mapping",              "mappings":[                 "· =>  "                ]            }          }        }    },    "mappings" : {      "properties" : {        "message" : {          "type" : "text",          "analyzer":"my_analyzer1",          "fields" : {            "keyword" : {              "type" : "keyword",              "ignore_above" : 256            }          }        }      }    }}
2.按照探索ES默认英文分词器章节的方式,创建索引token_test_index-2021.05.26_1,并写入相同的文档。

3.重新以outofmemory关键字搜索新建索引。

GET token_test_index-2021.05.26_1/_search{  "query": {    "query_string": {      "default_field": "message",      "query": "outofmemory"    }  }}

返回结果如下:

{  "took" : 2,  "timed_out" : false,  "_shards" : {    "total" : 3,    "successful" : 3,    "skipped" : ,    "failed" :   },  "hits" : {    "total" : {      "value" : 2,      "relation" : "eq"    },    "max_score" : .8025915,    "hits" : [      {        "_index" : "token_test_index-2021.05.26",        "_type" : "_doc",        "_id" : "4CSDp3kBitgxHTbP_Efs",        "_score" : .8025915,        "_source" : {          "message" : "java.outofmemory"        }      }    ]  }}
可以看到,在新的分词器下,可以搜索到想要的结果了。通过实践,我们发现ES默认英文分词器不会对以下八种特殊字符连接的词进行分词:

“:”,”.”,”‘“,”_”,”·”,”:”,”‘“,”’”。将这些特殊字符在字符过滤器阶段以空格替代的方式,可以很好解决该问题,并极大地提高关键词命中率。

2.2 查询分词优化

在查询上使用更加粗粒度的分词,有助于我们过滤掉大量不需要的结果,从而提高结果的命中率。英文分词可以直接通过空格、标点符号等停用词对其进行分词,这一点官网提供的分词插件已经做得很好了,然而不同于英文的是,中文句子中没有词的界限,无法简单地通过空格、标点符号进行分词。因此在进行中文自然语言处理时的分词效果将直接影响词性、句法树等模块的效果,一个好的中文分词方法能够使得查询事半功倍。项目中通过开发自定义中文分词器,从而对查询分词进行了一定的优化。

中文分词根据实现原理和特点,主要分为基于词典分词算法和基于统计的机器学习算法。基于词典分词算法是按照一定的策略将待匹配的字符串和一个已建立好的词典中的词进行匹配,若找到某个词条,则说明匹配成功,识别了该词。常见的基于词典的分词算法分为以下几种:正向匹配法、逆向大匹配法和双向匹配分词法等。基于统计的机器学习算法目前常用的算法是HMM、CRF、SVM、深度学习等算法。基本思路是对汉字进行标注训练,不仅考虑了词语出现的频率,还兼顾了上下文,具备较好的学习能力,因此其对歧义词和未登录词的识别都具有良好的效果。

本文基于CRF算法对查询中文分词进行了优化,并且取得了不错的效果。插入一条索引,并使用自定义中文分词器分词的结果如下:
插入一条索引POST test_index/_doc{  "mESsage":"数据解析失败"}对字段应用crf_analyzer分词POST test_index/_analyze{   "analyzer":"crf_analyzer",  "text": "数据解析失败"}分词效果如下{  "tokens" : [    {      "token" : "数据",      "start_offset" : ,      "end_offset" : 2,      "type" : "<IDEOGRAPHIC>",      "position" :     },    {      "token" : "解析",      "start_offset" : 3,      "end_offset" : 5,      "type" : "<IDEOGRAPHIC>",      "position" : 1    }    {      "token" : "失败",      "start_offset" : 6,      "end_offset" : 8,      "type" : "<IDEOGRAPHIC>",      "position" : 2    }  ]}

自定义中文分词插件应用于查询分词,给涉及中文的日志查询提供了便利,通过过滤大量的信息,提高了查询日志的效率。

3. 总结

本文通过对ES搜索原理的深入理解,进行 ES 搜索引擎写入分词器的实践优化,从而解决了日志查询无法有效命中的问题,同时针对涉及中文的日志查询,开发自定义中文分词插件,提高日志查询的效率。后,希望本文能够给读者提供一点可借鉴的方法与思路。

原文链接:https://mp.weixin.qq.com/s/724guj6FcDJAkDzu7KNV1A


相关文章