ElasticStack从入门到放弃

关于 ES 的基本使用之前已经写过了,然后 ELK 也许听的比较多,后来又有了一个 ELK Stack,指的就是 Elastic Stack。
这一套技术用在搜索需求、日志分析非常好用,也有很多用来处理 Excel 数据的,就现在都发展来看,ES 真是绕不开都技术,早晚都是要学,ELK 一套带走就好了。
毕竟这一全家桶内容很多,不打算过多深挖,很多东西都不细说了,提供关键词提点自己,用到来查到搜索方向后再 Google 具体使用就好了。
但是,它东西真的太多了,并且枯燥,对于不喜欢数据库的我来说,以至于最终还是不能坚持,草草了事吧。

  • Elasticsearch
    是一个搜索和分析引擎(数据的存储、查询、分析)
  • Logstash
    是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中
  • Kibana
    可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化
  • Beats
    一系列轻量型的单一功能数据采集器

Logstash 和 Beats 的作用就是数据的收集与整理,只不过 Beats 是为了解决用户的 “我只想对某个文件进行 tail 操作”的需求。
上面说过它们是采集数据的,范围很广,例如文件类的有日志、excel,数据库和 http 服务也可以,还支持自定义扩展。

ES 的服务端口默认 9300,Web 管理端口默认 9200.
官网文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

基本概念

复习一下 ES 的基本概念,交互是靠 RESTful API,ES 中主要的就三类:

  • 文档-Document
    最小的数据单元,内含多个字段 ,类似于数据库中的一行数据;
    由 Json Object (字段)构成,多种数据类型(可存二进制);
    当然,还会存在一些元数据
  • 类型-Type
    索引可以定义一个或多个类型,文档必须属于一个类型,相当于数据库中的 table;
    未来版本中因为索引调整为唯一,下面不再有多个类型,所以类型将会逐步消除。
  • 索引-Index
    相同属性的文档集合,相当于数据库的 Database
    再 6+ 的版本,官方已经禁止索引下创建多个类型,可以当作是 table 了。
  • 节点-Node
    一个 ES 的运行实例,构成集群的基本单元。
    每一个 ES 实例可以看作就是一个 JVM 线程。
  • 集群-Cluster
    多个节点构成的统一体

分布式中,必然还少不了分片和备份,类似 kafka 的概念了,一个索引可以分片成多个块,存放在不同的节点,这样既可以提高吞吐量,也可以方便的扩展,降低单节点的压力;备份就不多说了,分布在其他的节点上。

另外 ES 的特性还有:近实时,也就是秒级;分布式架构,方便扩展,简单说就是对 Lucene 的一个封装。

倒排索引与分词:

在说倒排索引之前,先要知道什么是正排索引,做比喻的话,就像一本书的目录,这就是正排索引;
而倒排索引就是书的名词索引页。
具体到 ES 的话,正排索引就是根据 ID 获取文档信息;倒排索引就是根据文档的内容(关键词)定位到是那个文档 ID;

而对于分词,这个也简单,毕竟对于搜索,分词后与之建立关系才能搜索。
正排与倒排的关系就是 ID 到单词(或者完整信息,不分词也可)与单词到 ID 的关系(一般需要分词)。

倒排索引主要包含两部分:

  1. 单词词典(Term Dictionary)
    记录所有文档单词和列表关联关系,一般都比较大。
    实现一般是类似 B+ 树的结构。
  2. 倒排列表(Posting List)
    主要有 ID (获取原始信息)、单词频率(相关性算分)、位置(搜索词的前后)、偏移构成(高亮显示)。

我们从单词词典中拿到一个词,然后通过记录的偏移量快速定位到对应的倒排列表,然后就拿到了原始 ID(还会涉及相关性算分等等步骤),最后就可以返回了。
PS:倒排索引是按字段来进行构建的。

分词

说道分词,就得说分词器,组成如下:

  • Character Filters
    针对原始文本进行处理,比如去除 html 特殊标记符;
    自带的有:HTML Strip、Mapping(字符串替换)、Pattern Replace(正则匹配替换)
  • Tokenizer
    将原始文本按照一定规则切分为单词;
    自带的有 Standard、letter(非字符分割)、Whitespace、path_hierarchy 等。
  • Token filters
    针对 tokenizer 处理的单词就行再加工,比如转小写、删除或新增等处理;
    自带的有 lowercase(小写转换)、stop、Synonym 等。

在调用过程中就是按照上面都顺序执行,可以使用 analyze_api 来进行测试分词是否符合预期。
ES 自带的分词器有:

  • Standard(默认)
    按词切分,支持多语言,小写处理
  • Simple
    按照非字母拆分,小写处理
  • Whitespace
    按照空格切分
  • Stop
    相比 Simple 多了 Stop Word 处理,也就是语气助词等修饰词
  • Keyword
    不分词,当作一个单词输出
  • Pattern
    通过正则表达式自定义分词,默认 \w+ 即非字符符号
  • language
    提供常见的 30+ 语言分词器

说到中文,中文的分词是比较难的,因为中文的词之间没有明显的分隔符,并且很依赖上下文。
常用的中文分词器有 IK、jieba (py 流行)等。
另外的一些就是基于自然语言的分词系统,也就是根据上下文来切,例如 Hanlp、THULAC;如果这些分词器满足不了你,那就只能进行自定义了,就是自己写分词器的那三部分。

PS:只有查询与索引使用相同的分词器才能保证一致性,一般查询不需要指定分词器,默认就是一致的。

Mapping

Mapping 相当于表结构定义,定义字段名、字段类型、倒排索引相关;获取可以使用 /index/_mapping API 查看。
更多操作看文档就行了,不多说。
Mapping 中的字段一旦确定,禁止直接修改,因为 Lucene 实现倒排索引后禁止修改,要改只能重新建立索引做 reindex 操作,所以要慎重。
虽然禁止修改,但是你新增是没有问题的,这个行为也是可以通过 dynamic 来进行控制。
Mapping 中还可以通过 copy_to 来将字段进行拼接处理;通过 index 来控制是否被索引,敏感信息可以加上这个,还省空间;对空值处理的 null_value,其他还有很多可配置的,参考官方文档。

ES 支持多字段特性 multi-fields,就是对一个字段采取不同的配置,例如拼音化。
我们还可以通过使用 dynamic-template 动态模板来设置 ES 自动匹配的内容为什么类型,例如默认我们不希望让它识别为 text 分词,这样会占用空间,以及用 float 保存浮点数节约存储等;它是由上到下顺序匹配。
类似的,也会有索引模板,便于索引的创建,使用 /_template API。


在数据建模过程中,要注意这些配置合理化,例如:

  • enabled
    是否仅存储,不做搜索和聚合分析,节省空间
  • index
    是否构建倒排索引
  • index_options
    存储倒排索引的那些信息
  • norms
    如果不需要算分排序,仅用于过滤和聚合分析,可关闭
  • doc_values
    同上,要排序要分析就开,搜索过滤就没必要
  • fielddata
    是否为 text 类型启用排序和聚合分析等
  • store
    是否存储字段值,当内容很多时为了避免影响性能会设置为 false,独立出去;配合 _source 元信息。
    即使你使用 API 只要求返回限定字段,由于 ES 的原理,还是会拿到所有字段,只是在返回的时候给你过滤掉,所以这种治标不治本。
  • coerce
    是否开启类型自动转换
  • dynamic
    Mapping 自动更新

其他的就是一些 date_detection 这种控制日期是否自动转换的配置,建模的过程也不简单,如果想设计的好。
枚举类型一般设置为 keyword 因为不需要分词,也不需要计算,例如 HTTP 的状态码之类


类似传统数据库,在 ES 中也可以处理表关系,Nested Object 与 Parent/child ,不过对于我来说太复杂了,不多看了,我真的对数据库提不起兴趣。
子元素更新频繁用 Parent,查询频繁用 Nested,因为 Parent 是分开存的,但是尽量用 Nested。

最后,保存好你的 Mapping 设计文档,做好版本管理。

查询

ES 毕竟是一个数据库,并且是为搜索而生,主力还是在搜索 API 上,这一块太多了,之前的笔记也有常用的部分,还是以官网 API 文档为准,搜索的时候只要善用关键词,例如 AND、NOT、分组、+/- (记得 URL 编码)、区间之类会大大简化查询语句。
尽量不要使用通配符、正则,尤其放在最前面,文档很大的时候会拖慢速度。
相比传统数据库,它还多了近似度匹配的模糊查询,并且它有一个相关性算分的功能(结果排序),搜索引擎必备了;相关性算分需要算法引擎支持,目前默认的是 BM25(相比 TF/IDF 优化了词频 tf 过大的无限增长问题),可以通过 explain 来查看算分过程,算分过程是 shard 独立的,集群的需要注意。
另外,在涉及日期搜索中,可以直接通过字符串计算,例如 now +1h ,总之就是很灵活的,不要受限于 SQL。
text 类型的字段在搜索排序的时候不能直接用(可通过 `name.keyword 转换为整体进行检索),ES 中提供了 3 种分页方式,最常用的还是 from/size(快照方式 Scroll 非实时性,利用排序方式优化的 Search After 不能实现自由翻页),因为分片的原因,分页的时候并不能确定需要的记录在那个分片,只能每个分片都查一下,最后汇总后确定真正的条数。
所以这种分页在遇到深度分页的时候会很耗资源,也就是越往后每个分片需要处理的数据就更大,就如同 Google 也不会把结果无限分页,如果前面都不是你想要的,后面更不会了。

由于倒排索引不可修改(确实有很多好处例如速度快可压缩等),新增数据的时候重新生成与修改,这样实时性肯定就无法保证,性能开销也很大,为了解决这个问题,可以单独为新文档构建倒排索引,查询的时候同时去两份倒排索引里翻, 当然需要有个文件来存储分块信息。
为了达到实时性搜索,ES 做了很多优化,例如文件系统的缓存(refresh),在还未完全写入的时候就可以提供查询,当然有相应的保护机制(translog),毕竟 ES 被称为近实时的搜素。

想要存储大量数据,分片是必须的,但是分片就会遇到相关性算分不准的问题,这时候就需要用 DFS Query-then-Fetch(通过 RESTful API 指定 search-type)。

集群

搭建过程就免了,在集群状态可视化上,可以常识下 cerebro 这个插件。
集群中,分片与副本是关键,分片还有主副之分。
新增节点后,原有对 index 不会增加数据容量,因为分片已经固定了;同样,只有新增节点才能增加吞吐量,增加副本是没用的。
ES 提供了 API 来查看集群的状态,用颜色表示的话,绿色健康,黄色表示主分片分配正常,副本未正确分配,至于红色就是主分片未正确分配,会影响搜索,但是不会影响服务。
既然是集群,肯定是要支持故障转移来实现高可用,这就牵扯到了 Master 选举。

文档的分片有几种方法,传统的随机在多节点下查找起来非常费劲,如果采用映射表的方式维护就是个难题(海量数据下),所以根据文档具有信息实时计算是不错的选择,就是利用哈希了,也正是因此,分片确定后就不可修改。
这里请求数据的时候,『寻址』或者重定向是服务端做的,客户端只与服务端建立一次连接即可,即使目标数据不在这个节点,具体的方案可参考之前写的分布式集群相关,例如 Redis、ZK 都在用,至于脑裂问题也就是选举的问题,在 ZK 哪里也着重说过。

ES 的性能基本是按照性能扩展的,所以,你只要计算出了单个 shard 的指标,就能推断出所需要多少 shard。压测工具可以试试 esrally,官方还提供了一个 x-pack 插件来监控,Kibana 也有对应的可视化插件。
就算日志场景一个 shard 也不要超过 50G,搜索场景不要超过 15G。

聚合分析

为了便于统计,ES 提供了很多聚合查询功能(aggs),相比 hadoop 这类 T+1 的,它是实时返回的。
聚合查询主要分为下面四类:

  • Bucket
    分桶类型,类似 SQL 中的 GROUP BY 语法
  • Metric
    指标分析类型,如计算最大值、最小值、平均值等等;分为单值与多值类型(stats)
  • Pipeline
    管道分析类型,基于上一级的聚合分析结果进行再分析
  • Matrⅸ
    矩阵分析类型,例如热力图

相比搜索,聚合还多了计算的能力,因为分片的存在,你不能要求它的计算结果是多么的精确,可以通过一些参数来调整精准度(show_term_doc_count_error 对应 doc_count_error_upper_bound),但是相应的也要付出性能。
海量数据、精准度、实时性,这三个不能兼得,只能选其二,显然 ES 聚合分析(Cardinality 和 Percentile)放弃了精准度,例如 Hadoop 就放弃了实时性。

文档地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

Logstash

按照之前的介绍,它就是个过滤器,或者说适配器。

1
2
3
4
5
6
7
8
9
10
11
12
13
// codec.conf
input {
// 标准输入
stdin {
codec => line
}
}

output {
stdout {
codec => json
}
}

配置文件就是这样了(支持表达式),内部原理也是使用队列,一定程度可以代替 Kafka,默认使用的是内存队列,建议使用持久队列就行,性能损耗不是太大。
主要涉及三类线程,读取、队列、消费,配置文件中可以对部分进行微调,这就是调优了,不多说。
常用的 input 插件有:stdin、file、kafka,不需要担心继续读取、更新检查、重复读取的问题,都考虑到了。
要想写的好,可能还需要学习 Grok 脚本。

Beats

简单说可以理解为 Logstash 的轻量级版,了解过 Logstash 的应该知道配置非常复杂,也正是因为 Beats 是轻量级的,主要在数据的收集上,如果需要对数据进行复杂的处理,还是要接入 Logstash。


用于日志分析的话,基本都是用 Filebeat 这个插件,配置也蛮复杂的,还好有 Modules 可以有,达到开箱即用。

如果用于指标分析,就是 Metricbeat 这个轻量级采集器配合使用了,细分可分为系统指标类(CPU 使用等)与日志类(Redis、MySQL 提供的性能指标),不管怎么说,它都是在收集处理指标数据,供分析系统的状态。
它相应的也有大量 Modules,系统的、Docker、Redis、MySQL 等等,用什么就装什么,相当于自动给你写好对应的配置文件了,你只需要改一下连接地址就能用。

网络数据包的分析使用 Packetbeat 插件,使用方式与上面一样,可以用来分析 DNS、HTTP、TLS、MySQL 连接等这些信息。
对于 Linux 系统可使用 af_packet 模式获取更好的性能。

心跳检测,确认对方是否存活,使用 Heartbeat 插件,社区也有很多质量很高的插件。

Kibana

为了让请求均匀分布,线上部署一般是采用一个 Coordinating Only ES Node 负责分发,这样 Kibana 只需要连接这个节点即可。
因为是 Web UI 终于不跟之前的那些似得那么枯燥了,也仅仅是视觉上,操作还是离不开 ES 的那些语句,要配置好界面也不是一个容易的活。

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~