ELK从入门到放弃

中文文档:序言 | Elasticsearch: 权威指南 | Elastic

上面的中文文档版本比较老,建议阅读英文文档,可选择各个历史版本:https://www.elastic.co/guide/en/elasticsearch/reference/7.2/index.html

1、使用docker搭建ELK

https://github.com/deviantony/docker-elk#how-can-i-persist-elasticsearch-data

从github上clone该仓库,在仓库目录下使用docker-compose命令即可一键搭建ELK三个软件

docker-compose up
# docker-compose up -d
# docker-compose down

2、logstash使用syslog协议作为input

通过上面的docker-compose文件可以看出,将logstash的yml配置文件挂载到了宿主机,所以可以通过该文件来修改针对logstash的配置:

input {
        tcp {
                port => 5000
        }
}

input{
        syslog{
       port => 8888
    }

}

## Add your filters / logstash plugins configuration here

output {
        elasticsearch {
                hosts => "elasticsearch:9200"
        }
}

第一个INPUT为监听tcp 5000端口作为日志输入,第二个INPUT为使用syslog协议监听8888端口作为日志输入,OUTPUT为将日志输出到elasticsearch。

3、将elasticsearch数据持久化存储

根据github文档描述及我自己测试结果发现,elasticsearch的docker-compose配置默认没有将数据持久化存储,如果容器elasticsearch的容器关闭未删除,那么数据仍然存在,但如果删除了容器,数据就没了,所以需要将数据持久化存储:

- /home/elk/elk-storage:/usr/share/elasticsearch/data  #新增的一行,用于挂载数据文件

也就是这一行,将容器内的/usr/share/elasticsearch/data挂载到宿主机的/home/elk/elk-storage

存在的问题:由于elasticsearch容器内使用的用户uid为1000,而宿主机大概率都是用的root用户把!?而你刚才手动创建的用于挂载数据的文件夹也会属于root用户,这将导致elasticsearch没有权限对文件进行写入。

解决方法:

groupadd -g 1001 elk  # 创建一个用户组,名字为elk,用户组ID为1001,这个ID随意
useradd -g 1001 -u 1000 elaticsearch # 创建一个用户,属于elk组,用户ID为1000,这个ID必须为1000,与elasticsearch容器内的uid保持一致
chgrp elk elk-storage/ # 将目录所属权划为elk组
chown elasticsearch elk-storage # 将目录所属权划给elaticsearch用户

创建完毕后,使用docker-compose down命令销毁容器,再使用docker-compose up命令重新创建即可

4. 官方ELK搭建

1.安装elasticsearch

useradd elk -d /home/elk # 不允许使用root用户运行elasticsearch
cd /home/elk
su elk
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.2.1-linux-x86_64.tar.gz
tar -xvf elasticsearch-7.2.1-linux-x86_64.tar.gz
cd elasticsearch-7.2.1/bin/
./elasticsearch
curl -X GET "localhost:9200/_cat/health?v" # 获取集群健康状态

curl -X GET "localhost:9200/_cat/nodes?v" # 获取集群节点信息

curl -X PUT "localhost:9200/customer?pretty" # 创建一个索引
curl -X GET "localhost:9200/_cat/indices?v" # 查看所有索引

Elasticsearch:

然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:

  • 一个分布式的实时文档存储,每个字段 可以被索引与搜索
  • 一个分布式实时分析搜索引擎
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据

Elasticsearch多个节点之间通过9300端口交互,Kibana通过Elasticsearch的9200端口交互

关于索引:

文档:索引员工文档 | Elasticsearch: 权威指南 | Elastic

将 HTTP 命令由 PUT 改为 GET 可以用来检索文档,同样的,可以使用 DELETE 命令来删除文档,以及使用 HEAD 指令来检查文档是否存在。如果想更新已存在的文档,只需再次 PUT 。

一些查询例子:

1.轻量查询
curl -X GET "localhost:9200/megacorp/employee/_search?q=last_name:Smith&pretty"

2.复杂表达式查询
curl -X GET "localhost:9200/megacorp/employee/_search?pretty" -H 'Content-Type: application/json' -d'
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}
'

3.更复杂的查询,多条件
curl -X GET "localhost:9200/megacorp/employee/_search?pretty" -H 'Content-Type: application/json' -d'
{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}
'

4.全文搜索,感觉叫模糊搜索比较容易理解
curl -X GET "localhost:9200/megacorp/employee/_search?pretty" -H 'Content-Type: application/json' -d'
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}
'
查询about字段涵盖相关字段的所有结果,并根据匹配程度来计算得分,得分越高说明匹配程度越高

5.短语搜索,精确匹配,match_phrase
curl -X GET "localhost:9200/megacorp/employee/_search?pretty" -H 'Content-Type: application/json' -d'
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}
'

6.聚合查询,等同于MySQL的group by
GET your_index/_search
{
  "size": 0,
  "aggs": {
    "per_count": {
      "terms": {
        "size":100,
        "field": "field_name",
        "min_doc_count":1
      }
    }
  }
}

5. 安装Kibana

https://www.elastic.co/cn/downloads/kibana

通过rpm包安装:rpm -ivh kibana-7.2.0-x86_64.rpm

安装完成后可以直接通过systemctl进行管理,默认是和elasticsearch的9200端口通信

修改kibana配置文件,绑定在0.0.0.0上。默认是绑定的localhost

vim /etc/kibana/kibana.yml

server.host: "0.0.0.0"

Kibana DevTools

1)查询某个索引的mapping信息

GET test/_mapping

2) 查询一条DEMO数据

GET test/_search
{
  "size": 1
}

3) 查询所有索引信息

GET _cat/indices

4)查询某个字段为某个值的数据,需要注意的是如果是嵌套字段需要访问嵌套字段内部元素

GET test/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "server_info.server_ip": "192.168.10.2"
        }}
      ]
    }
  }
}

5)聚合查询需要这fielddata=True

PUT /twitter/_mapping/
{
  "properties": {
    "city": { 
      "type": "text",
      "fielddata": true
    }
  }
}

6)统计某个字段不同的值共有多少个

GET /cars/transactions/_search
{
    "size" : 0,
    "aggs" : {
        "distinct_colors" : {
            "cardinality" : {
              "field" : "color"
            }
        }
    }
}

7)查看ES基本信息

GET /

6.ES相关

1.index type document
索引相当于关系型数据库中的数据库,type相当于表,document相当于某一行数据。
7.X版本以前的ES支持在index中有多个type,后来就不支持了,一个index中只能有一个type,这样更利于管理

2.关于_all
q=date:2020-03-11
q=2020-03-11

若查询指定了搜索的字段则会在该字段的value进行查询,若没有指定字段,仅仅指定了value的值,则默认使用_all字段进行查询。_all字段是存储了一条数据所有字段的值的大字符串。

3.查询和过滤
查询关键字:query
过滤关键字:filter

使用过滤语句得到的结果集 -- 一个简单的文档列表,快速匹配运算并存入内存是十分方便的, 每个文档仅需要1个字节。这些缓存的过滤结果集与后续请求的结合使用是非常高效的。
查询语句不仅要查找相匹配的文档,还需要计算每个文档的相关性,所以一般来说查询语句要比 过滤语句更耗时,并且查询结果也不可缓存。
幸亏有了倒排索引,一个只匹配少量文档的简单查询语句在百万级文档中的查询效率会与一条经过缓存 的过滤语句旗鼓相当,甚至略占上风。 但是一般情况下,一条经过缓存的过滤查询要远胜一条查询语句的执行效率。
过滤语句的目的就是缩小匹配的文档结果集,所以需要仔细检查过滤条件。

7.倒排索引

ES会对文档数据进行分词,再记录每个词出现在哪些文档的ID,这样查询某个词存在于哪些文档中时能够很快查询到。

Example:

Document:

id   doc
1    my first doc
2    my second doc
3    my third doc
4    my last doc

那么倒排索引会这样记录:

id   word   doc_id_group
1    my     1,2,3,4
2    first  1
3    doc    1,2,3,4
4    second 2
5    third  3   
6    last   4

除此之外,倒排索引还会记录每个词在文档中出现的频率,还会记录单词在文档中出现的位置

8.mapping

(1)静态mapping和动态mapping

当插入一个文档到ES中时,ES会自动判断文档中每个字段的值的类型,并建立mapping,也就是value和type的映射关系,例如2020-3-12会被识别为date类型,"ewqe"会被识别为string类型,123123会被识别为long类型。ES会对text类型进行分词,不会对keyword类型分词

可以关闭某个索引的动态mapping功能,只使用设置的静态mapping,所以在mapping设置外的字段都不会入库:

PUT tac_result/_mapping
{
  "dynamic":false
}

关于新增字段和动态索引

  • dynamic设置为true,一旦有新增字段的文档写入,mapping也同时被更新。
  • dynamic设置为false,mapping不会被更新,新增的字段数据无法被索引,但是信息会出现在source中.
  • dynamic设置为strict,文档写入失败
  • 已有的字段,一旦有数据写入,不支持修改(倒排索引不支持修改)
  • 希望更改字段类型,用Reindex API,重建索引
    设计原因
  • 如果修改字段数据类型,会导致已经被索引的文档不能被搜索。
  • 新增字段不存在影响。

(2)嵌套数据结构

1)嵌套对象

也就是列表嵌套对象,例如:

PUT test/_doc/1
{
  "test3": [
    {"a": "1"},
    {"a": "2"}
  ]
}

mapping中使用properties字段表示对象,properties中会记录对象包含的key和value的情况:

2)嵌套列表

嵌套列表就是外层为列表,内层也为列表:

PUT test/_doc/1
{
  "test": [
    ["192.168.1.1", "1"], 
    ["192.168.2.2", "2"]
  ],
  "ttt": "asd",
  "test2": [
    [["192.168.1.1", "1"]], 
    [["192.168.2.2", "2"], ["192.168.2.2", "2"]]
  ],
}

经过测试,不管是二层嵌套列表还是三层嵌套列表,在mapping中都是单一类型进行存储的,所以列表的每个数据的类型必须保持一致,否则入ES会报错。

推测:不管多少层嵌套的列表,ES都会将其进行展开,然后将每个数据平等存储,所以这就要求列表中的每个数据的类型都是一致的

9.丰富的ES查询过滤语句

term 过滤

term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed的字符串(未经分析的文本数据类型):

    { "term": { "age":    26           }}
    { "term": { "date":   "2014-09-01" }}
    { "term": { "public": true         }}
    { "term": { "tag":    "full_text"  }}

terms 过滤

terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配:

{
    "terms": {
        "tag": [ "search", "full_text", "nosql" ]
        }
}

range 过滤

range过滤允许我们按照指定范围查找一批数据:

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}

范围操作符包含:

gt :: 大于

gte:: 大于等于

lt :: 小于

lte:: 小于等于

exists 和 missing 过滤

exists 和 missing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件

{
    "exists":   {
        "field":    "title"
    }
}

这两个过滤只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用。

bool 过滤

bool 过滤可以用来合并多个过滤条件查询结果的布尔逻辑,它包含一下操作符:

must :: 多个查询条件的完全匹配,相当于 and

must_not :: 多个查询条件的相反匹配,相当于 not

should :: 至少有一个查询条件匹配, 相当于 or

这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:

{
    "bool": {
        "must":     { "term": { "folder": "inbox" }},
        "must_not": { "term": { "tag":    "spam"  }},
        "should": [
                    { "term": { "starred": true   }},
                    { "term": { "unread":  true   }}
        ]
    }
}

match_all 查询

使用match_all 可以查询到所有文档,是没有查询条件下的默认语句。

{
    "match_all": {}
}

此查询常用于合并过滤条件。 比如说你需要检索所有的邮箱,所有的文档相关性都是相同的,所以得到的_score为1

match 查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:

{
    "match": {
        "tweet": "About Search"
    }
}

如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,它将为你搜索你给定的值:

{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}

提示: 做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。

不像我们在《简单搜索》中介绍的字符查询,match查询不可以用类似"+usid:2 +tweet:search"这样的语句。 它只能就指定某个确切字段某个确切的值进行搜索,而你要做的就是为它指定正确的字段名以避免语法错误。

multi_match 查询

multi_match查询允许你做match查询的基础上同时搜索多个字段:

{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}

bool 查询

bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功, 而bool 查询要计算每一个查询子句的 _score (相关性分值)。

must:: 查询指定文档一定要被包含。

must_not:: 查询指定文档一定不要被包含。

should:: 查询指定文档,有则可以为文档相关性加分。

以下查询将会找到 title 字段中包含 "how to make millions",并且 "tag" 字段没有被标为 spam。 如果有标识为 "starred" 或者发布日期为2014年之前,那么这些匹配的文档将比同类网站等级高:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }},
            { "range": { "date": { "gte": "2014-01-01" }}}
        ]
    }
}

提示: 如果bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有must子句,那么没有should子句也可以进行查询。

10.ES内联脚本和动态script

https://www.elastic.co/guide/en/elasticsearch/reference/7.2/modules-scripting-painless.html

ES支持使用内联脚本,进行简单的数值操作,例如long类型数值+1,数组元素增删改查等,但是对于复杂的操作需要人工编写动态脚本,可以将脚本编译到ES内部,下次可以通过脚本ID进行调用。也可以将脚本写在DSL中,ES第一次碰到新脚本时会对其进行编译,然后就可以一直使用了。

在脚本中可以通过多种方式访问文档的值:

(1)在update, update-by-queryreindex API中使用的脚本将有权访问以下ctx变量:

ctx._source

访问文档_source字段

ctx.op

应该应用于文档的操作:indexdelete

ctx._index 等等

访问文档元字段,其中某些字段可能是只读的。

(2)使用doc访问:

doc['dynamic_data'],使用该方法访问速度是最快的,但是缺点是只能访问到简单的value,例如整数,字符串,数组等,并不能返回对象。

(3)通过_source进行访问:可以访问到层级对象,对象数组等复杂的数据结构

_source.field_name.field_name

(4)通过_fields['field_name']访问

查看doc的mapping信息,如果strore被设置成了true那么可以通过这种方式访问字段的值

11.Python ES API

1.连接ES

2.只查询某几个字段
es.search(params={"_source": "dynamic_data,md5"})

3.es.search如果不指定size字段默认返回10条数据,如果需要返回更多需要指定size

4.创建或更新索引中的文档:
result = es.index(index='my-index-000001', id="12313", body=data)
# result['result']记录了是更新操作还是创建操作, 'updated' or 'created'

5.根据id更新一条数据,如果id不存在会报错(数据的外部需要嵌套一个doc的key):
data = {
    "doc":{
      "group": "fans",
      "user": [
        {
          "first" : "John",
          "last" :  "4"
        },
        {
          "first" : "Alice",
          "last" :  "5"
        }
      ]
    }
}
es.update(index='my-index-000001', id="12313", body=data, _source=True)

6.使用bulk可以在一个请求中进行多个操作(index,update,delete,create等),可以提升性能
bulk中的多个操作相互独立,每个操作的成功或失败不会影响到其他的操作,并且在bulk的响应中,会返回每个操作的具体响应状态

重试装饰器:

def es_timeout_decorator(func):
    """
    ES超时重试装饰器,默认重试三次
    捕获ConnectionTimeout异常并重试,如果出现其他异常直接返回并记录日志
    :param func:
    :return:
    """
    def wrapper(*args, **kwargs):
        retry_num = 0
        while retry_num < 3:
            try:
                resp = func(*args, **kwargs)
                return 1, resp
            except ConnectionTimeout:
                retry_num += 1
                time.sleep(2)
                continue
            except TransportError:
                retry_num += 1
                time.sleep(2)
                continue
            except Exception as e:
                logger.error(e)
                return 0, str(e)
        return 0, "ConnectionTimeout caused by - ReadTimeoutError"
    return wrapper


@es_timeout_decorator
def create_or_update_doc(es, md5_sha256, data):
    """
    创建或更新一个文档,目前感觉性能足够,如性能不足可考虑使用bulk
    :param es:
    :param md5_sha256:
    :param data:
    :return:
    """
    es_resp = es.index(index=INDEX, id=md5_sha256, body=data)
    return es_resp

判断数据是否存在ES中:

from elasticsearch import Elasticsearch
import certifi


ES = {
    'url': '',
    'user': '',
    'password': ''
}

def conn_es():
    es = Elasticsearch(ES['url'], http_auth=(ES['user'], ES['password']), ca_certs=certifi.where(), verify_certs=False, timeout=30)
    return es


es = conn_es()
INDEX = ''
body = {
  "query": {
    "match": {
      "sha256": "XXXXXX"
    }
  }
}
data = es.count(index=INDEX, body=body)
print data
print data['count']

bulk insert:Helpers — Elasticsearch 8.0.0 documentation

注:EX7.x之后不再支持指定type,所以不需要type_name参数,见文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.12/removal-of-types.html#removal-of-types

注:bulk一次请求body最大限制为100MB,这是由http协议和es的配置决定的

def generate_actions(data_list, json_file, index_name, type_name):
    actions = []

    for d in data_list:
        _id = d["md5"] + '_' + d["sha256"]
        try:
            actions.append({
                    '_op_type': 'index',
                    '_index': index_name,
                    '_type': type_name,
                    '_id': _id,
                    '_source': d
                })
        except:
            logger.exception()

    return actions


def bulk_insert(data_list, filepath, index_name, type_name):
    json_file = os.path.basename(filepath)
    actions = generate_actions(data_list, json_file, index_name, type_name)

    count = 0
    while count < 10:
        count += 1
        try:
            helpers.bulk(ES, actions, request_timeout=30)
            logger.warning("json_file: %s, bulk insert %s success, delete it..." % (json_file, len(actions)))
            os.system("rm -rf %s" % filepath)
            return True
        except ConnectionTimeout as msg:
            logger.error(msg)
            time.sleep(2)
        except ConnectionError as msg:
            logger.error(msg)
            time.sleep(2)
        except SerializationError as msg:
            time.sleep(2)
            logger.warning("json_file: %s, SerializationError: %s" % (json_file, msg))
        except BulkIndexError as msg:
            logger.error("json_file: %s, BulkIndexError: %s" % (json_file, msg))
            return False
        except Exception as msg:
            # logger.error(data_list)
            logger.exception("bulk insert exception, json_file: %s" % json_file)
            return False

    logger.error("bulk insert ConnectionTimeout/ConnectionError, json_file: %s" % json_file)
    return False


bulk_status = bulk_insert(data_list, filepath, index_name, type_name)

12.ES返回的字段解析

13.ES分页,深度分页和快照分页

1)深度分页,使用from size参数

from定义的是数据的偏移值,即从第多少条数据开始截取

size定义的是返回事件的数目

例如 from=1000,size=10,那么返回的就是1000-1010这十条数据

深度分页存在的问题:

查询流程如下:

  1. 客户端发送请求到某个node节点。

  2. 此node将请求广播到各分片,各分片各自查询前5100条数据。

  3. 查询结果返回给node节点,node对结果进行合并整合,取出前5100条数据。

  4. 截取前5000条数据,留下100条返回给用户

  5. 返回给客户端。

默认是不允许查询10000以上的数据的,也就是默认是会拒绝from >= 10000的查询的,但是可以通过配置修改。

当分页查询的数据总数很大时,绝对要避免使用深度分页的方式,会极大占用CPU 内存等资源,很容易让ES宕掉,推荐使用scroll的分页方式。

2)快照分页,使用scroll参数

scroll的存在就是为了一次性查询大量的数据,初次调用时会尽可能返回所有数据和一个scroll_id,相当于所有的数据已经在ES中建立了一个快照,当一次性不能返回所有数据时,通过scroll_id可以读取后续的数据,直到返回的hits为空列表为止。


scroll相当于关系型数据库中的游标,会记录读取快照的下标信息,当通过scroll_id读取时,会从上次的下标继续向后读取,所以该方法不能读取前一页的记录或者某一页的记录。

在使用完scroll_id后记得删除该资源

ES Python API调用scroll的一个demo:

data = es.search(index='xxx', params={"_source": "hash"}, body=q, scroll='1m')

# scroll参数指定快照保存的时间,1m就表示1分钟
# 如果数据量不大,可以一次性读取完,那么data中就返回了所有的数据

scroll_id = data['_scroll_id']
next_data = es.scroll(scroll_id=data['_scroll_id'], scroll='1m')
# 指定scroll='1m'参数,表示此次读取之后延长1min的游标存在时长
if len(next_data['hits']['hits']) == 0:
    print '数据已经读取完毕'
else:
    print '还存在未读取的数据,请通过scroll_id继续读取'


# 碰到的异常情况:
# 使用scroll去读取时,没有指定size参数,那么scroll会尽可能一次性读取出所有数据,因为数据量比较大,所以ES经常会TimedOut,建议指定size参数,每次返回适中的数据。

14.一些查询DEMO

1)按某个字段分类查询

def query_type():
    es = conn_es()
    body = {
    "size": 0,
    "aggs" : {
        "group_by_type" : {
            "terms" : { "field" : "type", "size": "2000" },
        }
    },
   #"sort": ['_doc']
}
    data = es.search(index='', body=body, params={"_source": "type"})

2)动态脚本

def query_type():
    es = conn_es()
    q = {
        "_source": ["md5", "file_type"],
        "query": {
            "bool": {
                "must": {
                    "match": {"file_type": "word"},
                },
                "filter": {
                    "script": {
                        "script": """
                            def match = false;
                            for (obj in _source.dynamic_data) {
                                if ((obj).size() >= 3) {
                                    match = true;
                                    break;
                                };
                            };
                            return match;
                        """
                    }
                }
            }
        },
        "size": 100,
        "sort": [
            "_doc"
        ]
    }
    data = es.search(index='', body=q)

3)查看mapping信息

es.indices.get_mapping(index='sandbox', doc_type=None, params=None)

4)设置断路器参数

curl --user elastic:changeme -XPUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d '{"persistent" :{"indices.breaker.fielddata.limit" : "40%"}}'

5)索引一条数据

es.index(index='sandbox', doc_type='doc', body=data)

6)删除一个索引

es.indices.delete('sandbox')

7)有则跳过,无则插入

def conn_es():
    es = Elasticsearch([{'host': ES_HOST, 'port': ES_PORT}], timeout=300, http_auth=('elastic', '12345678'))
    return es


def data2es(es, data):
    es.index(index='result', body=data)


es = conn_es()
for j in os.listdir('/home/json/'):
    json_file = os.path.join('/home/json/', j)
    with open(json_file, 'r') as f:
        line = f.readline()
        while line:
            line = json.loads(line)
            body = {
               "query": {
                  "bool":{
                  "must":{
                    "match": {
                      "id": line['id'],
                   }
                 }
                }
              }
            }
            count = es.search(index='result', body=body)
            if not count['hits']['hits']:
                print 'not exists,insert'
                data2es(es, line)
            line = f.readline()

8)分桶查询,查询字段不同value的数量

GET your_index/_search
{
  "size": 0, 
  "aggs": {
    "different_count": {
      "terms": {
        "field": "field_name"
      }
    }
  }
}

9)查询某个字段的value共有多少种

GET your_index/_search
{
  "size": 0,
  "aggs": {
    "category_name": {
      "cardinality": {
        "field": "field_name"
      }
    }
  }
}

15.复杂数据结构的存储方式

1)在ES中没有内部对象的概念,也就是说不会多层嵌套存储对象,而是会对嵌套对象进行展开处理:

例如嵌套字典:

{ 
  "region": "ZH-CN",
  "manager": { 
    "age":     30,
    "name": { 
      "first": "John",
      "last":  "Smith"
    }
  }
}

es中的存储方式为:

{
  "region":             "US",
  "manager.age":        30,
  "manager.name.first": "John",
  "manager.name.last":  "Smith"
}

mapping:

{
  "mappings": {
    "my_type": { 
      "properties": {
        "region": {
          "type": "keyword"
        },
        "manager": { 
          "properties": {
            "age":  { "type": "integer" },
            "name": { 
              "properties": {
                "first": { "type": "text" },
                "last":  { "type": "text" }
              }
            }
          }
        }
      }
    }
  }
}

如果是列表嵌套字典以object方式存储:

{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

存储方式:

{
  "group" :        "fans",
  "user.first" : [ "alice", "john" ],
  "user.last" :  [ "smith", "white" ]
}

对于对象数组,默认会设置type为object,然后就会按照上面的方式进行存储,这样就会丢失John和Smith之间的联系,例如以下查询语句:

GET my-index-000001/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "user.first": "Alice" }},
        { "match": { "user.last":  "Smith" }}
      ]
    }
  }
}

由于user.first和user.last没有任何关联关系,所以只要user.first包含Alice并且user.last包含smith,那么就可以被我们的查询语句匹配到:

{
  "took" : 20,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "group" : "fans",
          "user" : [
            {
              "first" : "John",
              "last" : "Smith"
            },
            {
              "first" : "Alice",
              "last" : "White"
            }
          ]
        }
      }
    ]
  }
}

2)nested数据类型

为了解决关联丢失的问题,可以将field的type设置为nested。还是以对象数组为例,当设置type为nested后,ES会保存John和Smith之间的联系,在内部,每个对象会被索引为一个单独的文档,那么便可以是用nested查询来进行有关联关系的查询。例如以下查询语句,查询到的结果为null,因为会严格匹配user.first=Alice并且其对应的user.last=Smith的数据,显然是不匹配的。

{
  "query": {
    "nested": {
      "path": "user",
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "Alice" }},
            { "match": { "user.last":  "Smith" }} 
          ]
        }
      }
    }
  }
}

更多的说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html#nested-fields-array-objects

3)nested查询

类型为nested的字段,如果要查询该字段的内容,则需要使用nested查询,例如:

{
  "query": {
    "nested": {
      "path": "network.http",
      "query": {
        "bool": {
          "must": [
            { "match": { "network.http.status": 200 } },
            { "match": { "network.http.sha1": "qwerrtqweqwe" } }
          ]
        }
      },
      "score_mode": "avg"
    }
  }
}

16.索引模式和索引模板

(1)索引模式

index pattern用于匹配一系列索引,例如test_index_*,相当于把这些数据进行汇总,提供统一的查询,经常用于日志的查询,例如Logstash通常以logstash-YYYY.MMM.DD格式创建一系列索引。 要浏览2018年5月的所有日志数据,您可以指定索引模式logstash-2018.05 *。

(2)索引模板(https://www.elastic.co/guide/en/elasticsearch/reference/7.12/index-templates.html

用于设置索引的固定配置,例如分片数,备份数,mapping等,可以根据该模板创建与设置相同的索引,常用于需要按时间切割索引的场景,例如需要按天存储日志,那么就可以设置一个索引模板,每天通过该模板创建一个新索引存储当天的数据。可以通过kibana创建索引模板,也可以通过POST一个template的json配置创建,例如:

PUT _index_template/template_1
{
  "index_patterns": ["te*", "bar*"],
  "template": {
    "settings": {
      "number_of_shards": 1
    },
    "mappings": {
      "_source": {
        "enabled": true
      },
      "properties": {
        "host_name": {
          "type": "keyword"
        },
        "created_at": {
          "type": "date",
          "format": "EEE MMM dd HH:mm:ss Z yyyy"
        }
      }
    },
    "aliases": {
      "mydata": { }
    }
  },
  "priority": 500,
  "composed_of": ["component_template1", "runtime_component_template"], 
  "version": 3,
  "_meta": {
    "description": "my custom"
  }
}

字段解析:

  1. aliases:指定索引别名,通过该模板创建的索引自动会添加该别名,查询该别名时会去搜索该别名对应的所有索引的分片。默认所有索引都只能查询,如果使用别名进行写入的话,会出现报错:no write index is defined for alias [alias_mac]. The write index may be explicitly disabled using is_write_index=false or the alias points to multiple indices without one being designated as a write index
  2. index_patterns:匹配哪些索引
  3. priority:优先级,同名模板,高优先级的将覆盖低优先级,并吸收低优先级的设置
  4. _meta:个人添加的额外信息
  5. settings:索引配置信息
  6. mappings:索引的mapping

创建索引模板后可以适配配置的通配符的所有索引,如果向符合的索引插入数据会自动创建索引并引擎索引模板的配置。

初探 Elasticsearch Index Template(索引模板) - 简书

DataStream:

碰到的一些问题:

(1)Data too large

TransportError(429, u'circuit_breaking_exception', u'[parent] Data too large, data for [<http_request>] would be [256779492/244.8mb], which is larger than the limit of [255013683/243.1mb], real usage: [253204504/241.4mb], new bytes reserved: [3574988/3.4mb], usages [request=0/0b, fielddata=1507/1.4kb, in_flight_requests=3574988/3.4mb, model_inference=0/0b, accounting=1711948/1.6mb]')

分配给ES的堆内存不够用了,内存可用阈值为243MB,但是仍在往内存(JVM)中写入数据,所以就导致报错。

那么为什么内存会不够用呢?简单来说就是一直在往内存写数据,ES还没有将内存的数据刷到硬盘上,所以也没有清空内存,导致内存占用一直增大,直到达到设置的阈值,设置的阈值为Xms和Xmx参数。

转自网络:

分析: 向ES写入数据时,大致分为以下4个步骤:

        1. 数据写入到buffer中。

        2. 每隔一段时间 (可以在settings中通过refresh_interval手动设置值)刷新buffer,将数据组装并保存到index segment file(可以看作是一份File,只不过目前存储在内存中)

        3. index segment file在被创建后,会被立刻读取并写入到OS Cache中(此时数据就可以对客户端提供搜索服务了)。

        4. 默认每隔30分钟或translog过大时,ES会将当前内存中所有的index segment标记并形成一个commit point(类似git 的commit id),进行原子性的持久化操作,操作完毕后,删除本次已经已经了持久化的index segment,腾出内存空间。

所以我认为出现上述的情况是因为: buffer的刷新时间间隔过长或一次新增的数据量过大,导致数据堆集在buffer中,此外由于OSCache 默认30分钟会进行一次自动的flush,清空内存中的部分数据,因此ES会出现"时好时坏"的现象。

解决方案

方案一.增加分配给ES应用的内存 (验证通过)

   编辑 /opt/elasticsearch/config jvm.option,将-Xms和-Xmx 调大,初始值都是1G。修改配置后,需要重启ES才能生效。

方案二.清空缓存 (尚未验证)

   curl -XPOST -u admin:Admin@123 'http://xxx:9200/elasticsearch/_cache/clear?fielddata=true

方案三: GET /_flush  执行所有索引的flush操作

(2)嵌套列表数据不统一问题

JSON数据DEMO:

{
    "test_field": [
        ["192.168.56.104", 49176],
    ]
}

该数据格式存入ES会出现报错:

RequestError(400, u'illegal_argument_exception', u'mapper [test_field] cannot be changed from type [text] to [long]')

问题就在于这个嵌套列表中即存储了text类型,又存储了long类型,导致出现了冲突

(3)时间字段存储问题

最常用的时间格式为yyyy-MM-dd HH:mm:ss,但是这种时间格式直接入库的话会被ES设置为text格式,而不是date。而"yyyy-MM-dd"、"yyyyMMdd"、"yyyyMMddHHmmss"、"yyyy-MM-ddTHH:mm:ss"、"yyyy-MM-ddTHH:mm:ss.SSS"、"yyyy-MM-ddTHH:mm:ss.SSSZ"等格式可以。所以可以在设计数据时可以注意设定的格式,或者对源数据进行进行处理再写入

(4)查询数组不为空的数据

{
  "query":{
    "bool":{
      "must":[
        {
          "exists":{
            "field":"YOUR_FIELD"
          }
        }
      ]
    }
  }
}

(5)nested类型的开销

在不频繁更新的场景下,nested类型表现不错,可以增加查询的范围,但是如果要对文档进行频繁更新,nested类型会带来巨大的开销。例如在nested类型的最深层次添加一个文档,将会导致重新索引该nested类型包含的所有文档,如果文档数量巨大的话,会带来巨大的开销。或者在根级别修改了某些内容,这也会导致文档重新索引,同样会带来巨大的开销。

此外,使用nested类型要警惕映射爆炸的问题,在索引中定义太多字段会导致映射爆炸。例如该索引开启了动态映射,然后每次都有新的字段插入,那么就会导致字段数无限膨胀,然后会引起内存不足或者其他难以恢复的异常情况。为了避免该问题,一般会设置映射字段数量(mapping limit setting),默认限制为1000

(6)空对象

默认配置下,当一个对象为空{},或者为null,或者没有该对象的key时,插入到ES都不会报错,而且可以通过查询对象为空的语句将这三个文档都查询出来,那么问题来了,如果一个对象没有值,那么应该设置为{},还是null,还是干脆不设置该对象的key?(null_value | Elasticsearch Guide [7.11] | Elastic

从文档描述来看,[]等价于null等价于[null],三者是相等的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值