什么是ELK
ELK是三个开源软件的缩写,分别表示:
- Elasticsearch
- Logstash
- Kibana
Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。
Logstash主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作再一并发往elasticsearch上去。
Kibana也是一个开源和免费的工具,Kibana可以为Logstash和ElasticSearch提供的日志分析友好的Web界面,可以帮助汇总、分析和搜索重要数据日志。
新增了一个Beats系列组件,它是一个轻量级的日志收集处理工具(Agent),Beats占用资源少,适合于在各个服务器上搜集日志或信息后传输给Logstash。
为什么需要ELK
先来看一个应用场景,常见的WEB应用日志分析,一般我们会怎么做?登录到每台服务器上,直接在日志文件中grep、awk就可以获得自己想要的信息。但在规模较大的场景中,此方法效率低下,面临问题包括日志量太大如何归档、文本搜索太慢怎么办、如何多维度查询。
这个时候我们希望集中化的日志管理,所有服务器上的日志收集汇总。常见解决思路是建立集中式日志收集系统,将所有节点上的日志统一收集,管理,访问。这样对于大型系统来说,都是一个分布式部署的架构,不同的服务模块部署
在不同的服务器上,问题出现时,大部分情况需要根据问题暴露的关键信息,定位到具体的服务器和服务模块,构建一套集中式日志系统,可以提高定位问题的效率。
一个完整的集中式日志系统,需要包含以下几个主要特点:
- 收集-能够采集多种来源的日志数据
- 传输-能够稳定的把日志数据传输到中央系统
- 存储-如何存储日志数据分析-可以支持
- UI 分析警告-能够提供错误报告,监控机制
ELK就是这样一整套解决方案,并且都是开源软件,之间互相配合使用,完美衔接,高效的满足了很多场合的应用,而不仅仅是日志分析。
HelloWorld
打开:http://localhost:5601/,点击面板中的“Dev Tools”按钮,进入Dev工具,开始我们的Elasticsearch初次访问之旅。
创建索引
输入:
put test
输出:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "test"
}
查看索引
输入:
get test
输出:
{
"test" : {
"aliases" : { },
"mappings" : { },
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "1",
"provided_name" : "test",
"creation_date" : "1634041189082",
"number_of_replicas" : "1",
"uuid" : "Q6ja6kKDQ6-XUBQWcIzXxg",
"version" : {
"created" : "7140199"
}
}
}
}
}
添加文档
输入:
put /test/_doc/1
{
"msg": "hello elasticsearch"
}
输出:
{
"_index" : "test",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
查看文档
输入:
get /test/_doc/1
输出:
{
"_index" : "test",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"msg" : "hello elasticsearch"
}
}
基本原理与概念
倒排索引
从上面的例子中,我们看到了很多熟悉又陌生的概念,比如索引、文档等等。这些概念和我们平时看到的数据库里的索引有什么区别呢?
举个例子,现在我们要保存唐宋诗词,数据库中我们们会怎么设计?诗词表我们可能的设计如下:
朝代 | 作者 | 标题 | 诗词全文 |
唐 | 李白 | 静夜思 | 床前明月光,疑是地上霜。举头望明月,低头思故乡。 |
宋 | 李清照 | 如梦令 | 常记溪亭日暮,沉醉不知归路,兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。 |
唐 | 李白 | 蜀道难 | 蜀道之难难于上青天,侧身西望长咨嗟。 |
唐 | 李隆基 | 春台望 | 暇景属三春,高台聊四望。 |
… | … | … | … |
要根据朝代或者作者寻找诗,都很简单,比如“select 诗词全文 from 诗词表 where 作者=‘李白’”,如果数据很多,查询速度很慢,怎么办?我们可以在对应的查询字段上建立索引加速查询。
但是如果我们现在有个需求:要求找到包含“望”字的诗词怎么办?用“select 诗词全文 from 诗词表 where 诗词全文 like ‘%望%’”,这个意味着要扫描库中的诗词全文字段,逐条比对,找出所有包含关键词“望”字的记录。基本上,数据库中一般的 SQL优化手段都是用不上的。数量少,大概性能还能接受,如果数据量稍微大点,就完全无法接受了,更何况在互联网这种海量数据的情况下呢?
怎么解决这个问题呢,用倒排索引。
可以将上述诗词的中每个字都可以作为关键字,然后建立关键字和文档之间的对应关系,也就是标识关键字被哪些文档包含,于是我们可以这么保存:
序号 | 关键词 | 静夜思 | 如梦令 | 蜀道难 | 春台望 |
1 | 望 | 有 | 无 | 有 | 有 |
2 | 上 | 有 | 无 | 有 | 无 |
所以,倒排索引就是将文档中包含的关键字全部提取处理,然后再将关键字和文档之间的对应关系保存起来,最后再对关键字本身做索引排序。用户在检索某一个关键字是,先对关键字的索引进行查找,再通过关键字与文档的对应关系找到所在文档。
在存储在关系型数据库中的数据,需要我们事先分析将数据拆分为不同的字段,而在es这类的存储中,需要应用程序根据规则自动提取关键字,并形成对应关系。这些预先提取的关键字,在全文检索领域一般被称为term(词项),文档的词项提取在es中被称为文档分析,这是全文检索很核心的过程,必须要区分哪些是词项,哪些不是,比如很多场景下,apple和apples是同一个东西,望和看其实是同一个动作。
Elasticsearch基本概念
Elasticsearch中比较关键的基本概念有索引、文档、映射、映射类型、文档字段概念,为了方便理解,可以和关系数据库中的相关概念进行个比对:
关系数据库 | ES |
库 | 索引(Index) |
表 | 映射类型(Mapping Type) |
数据行 | 文档(Document) |
字段 | 文档字段(Field) |
表结构 | 映射(Mapping) |
Elasticsearch索引
索引是映射类型的容器,一个Elasticsearch索引非常像关系型世界的数据库,是独立的大量文档集合。
当然在底层,肯定用到了倒排索引,最基本的结构就是“keyword”和“Posting List”,Posting list就是一个int的数组,存储了所有符合某个term的文档 id。
另外,这个倒排索引相比特定词项出现过的文档列表,会包含更多其它信息。它会保存每一个词项出现过的文档总数, 在对应的文档中一个具体词项出现的总次数,词项在文档中的位置(便于高亮显示),每个文档的长度,所有文档的平均长度等等相关信息。
文档 (Document)
文档是ES中所有可搜索数据的最小单位,比如日志文件中的日志项、一部电影的具体信息等等。
文档会被序列化JSON格式保存到ElasticSearch中,JSON对象由字段组成,每个字段都有对象的字段类型(字符串,数值,布尔,日期,二进制,范围类型)。同时每个文档都有一个Unique ID,可以自己指定ID,或者通过ElasticSearch自
动生成。
所以严格来说,es中存储的文档是一种半结构化的数据。
映射
映射(mapping)定义了每个字段的类型、字段所使用的分词器等。
可以显式映射,由我们在索引映射中进行预先定义;也可以动态映射,在添加文档的时候,由 es 自动添加到索引,这个过程不需要事先在索引进行字段数据类型匹配等等,es会自己推断数据类型。
文档字段
文档中的一个字段field就相当于关系型数据库中的一列column,那么它肯定有数据类型,es提供的数据类型有:
字符串类型:string,字符串类还可被分为text和keyword类型,如果我们让es自动映射数据,那么es会把字符串定义为 text,并且还加了一个keyword类型字段。
- text:文本数据类型,用于索引全文值的字段。使用文本数据类型的字段,它们会被分词,在索引之前将字符串转换为单个术语的列表(倒排索引),分词过程允许ES搜索每个全文字段中的单个单词。什么情况适合使用text,只要不具备唯一性的字符串一般都可以使用text。
- keyword:关键字数据类型,用于索引结构化内容的字段。使用keyword类型的字段,其不会被分析,给什么值就原封不动地按照这个值索引,所以关键字字段只能按其确切值进行搜索。什么情况下使用keyword,具有唯一性的字符串,例如:电子邮件地址、MAC 地址、身份证号、状态代码…等等。
数字型数据类型:long、integer、short、byte、double、float
日期类型:date
布尔类型:boolean
复杂数据类型
- 数组:无需专门的数据类型
- 对象数据类型:单独的 JSON 对象
- 嵌套数据类型:nested,关于JSON对象的数组
地理数据类型:
- 地理点数据类型
- 地理形状数据类型
专门数据类型:
- IPv4数据类型
- 单词计数数据类型token_count
各种数据类型的使用
创建一个新的索引:
输入:
put base-test
输出:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "base-test"
}
显式映射,指定文档中各个字段的数据类型:
输入:
put /base-test/_mapping
{
"properties": {
"crop": {
"type": "text"
},
"name": {
"type": "text"
},
"lang": {
"type": "text"
}
}
}
输出:
{
"acknowledged" : true
}
假如我们在插入一个文档时,多了个star字段,会怎么样?
输入:
put /base-test/_doc/1
{
"crop": "Apache",
"name": "ES",
"lang": "JAVA",
"star": 200
}
输出:
{
"_index" : "base-test",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
查询文档,可以发现star字段已经保存成功:
输入:
get /base-test/_doc/1
输出:
{
"_index" : "base-test",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"crop" : "Apache",
"name" : "ES",
"lang" : "JAVA",
"star" : 200
}
}
查询索引的映射,可以看到es自动帮我们新增了star这个字段:
输入:
get /base-test/_mapping
输出:
{
"base-test" : {
"mappings" : {
"properties" : {
"crop" : {
"type" : "text"
},
"lang" : {
"type" : "text"
},
"name" : {
"type" : "text"
},
"star" : {
"type" : "long"
}
}
}
}
}
修改映射,增加一个新的字段year:
输入:
put /base-test/_mapping
{
"properties": {
"year": {
"type": "integer"
}
}
}
输出:
{
"acknowledged" : true
}
输入:
get /base-test/_mapping
输出:
{
"base-test" : {
"mappings" : {
"properties" : {
"crop" : {
"type" : "text"
},
"lang" : {
"type" : "text"
},
"name" : {
"type" : "text"
},
"star" : {
"type" : "long"
},
"year" : {
"type" : "integer"
}
}
}
}
}
数组
不需要特殊配置,一个字段如果被配置为基本数据类型,就是天生支持数组类型的。任何字段都可以有0个或多个值,但是在一个数组中数据类型必须一样。
比如:
输入:
put /base-test/_doc/2
{
"name": [
"Apache Activemq",
"Activemq Artemis"
],
"lang": "Java",
"corp": "Apache",
"stars": [
500,
200
]
}
输出:
{
"_index" : "base-test",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
是没问题的,但是如果:
输入:
put /base-test/_doc/3
{
"name": [
"Apache Kafka"
],
"lang": "Java",
"corp": "Apache",
"stars": [
500,
"java"
]
}
输出:
{
"error" : {
"root_cause" : [
{
"type" : "mapper_parsing_exception",
"reason" : "failed to parse field [stars] of type [long] in document with id '3'. Preview of field's value: 'java'"
}
],
"type" : "mapper_parsing_exception",
"reason" : "failed to parse field [stars] of type [long] in document with id '3'. Preview of field's value: 'java'",
"caused_by" : {
"type" : "illegal_argument_exception",
"reason" : "For input string: \"java\""
}
},
"status" : 400
}
对象
JSON文档是有层次结构的,一个文档可能包含其他文档,如果一个文档包含其他文档,那么该文档值是对象类型,其数据类型是对象。当然ElasticSearch中是没有所谓对象类型的,比如:
输入:put /base-test/_doc/4{ "name": [ "Apache ShardingSphere" ], "lang": "Java", "corp": "JingDong", "stars": 400, "address": { "city": "BeiJing", "country": "亦庄" }}输出:{ "_index" : "base-test", "_type" : "_doc", "_id" : "4", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1}
对象类型可以在定义索引的映射关系时进行指定。
多数据类型
如果说数组允许你使用同一个设置索引多项数据,那么多数据类型允许使用不同的设置,对同一项数据索引多次。带来的好处就是可以同一文本有多种不同的索引方式,比如一个字符串类型的字段,可以使用text类型做全文检索,使用keyword 类型做聚合和排序。我们可以看到es的动态映射生成的字段类型里,往往字符串类型都使用了多数据类型。当然,我们一样也可以自己定义:
输入:
put /base-test/_mapping
{
"properties": {
"name": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
},
"length": {
"type": "token_count",
"analyzer": "standard"
}
}
}
}
}
输出:
{
"acknowledged" : true
}
在上面的代码里,我们使用"fields"就把name字段扩充为多字段类型,为name新增了两个子字段raw和length,raw设置类型为keyword,length设置类型为token_count,告诉es这个字段在保存还需要做词频统计。
通过fields字段设置的子字段raw和length,在我们添加文档时,并不需要单独设置值,他们name共享相同的值,只是es 会以不同的方式处理字段值。同样在检索文档的时候,它们也不会显示在结果中,所以它们一般都是在检索中以查询条件的形式出现,以减少检索时的性能开销。