如何使用python访问Elasticsearch并实现大批量查询

问题背景:

近期的工作中,获取应用日志数据时,发现采用的是elk架构的日志服务器,需要访问ElasticSearch服务器来查询所需要的日志数据

自己也是第一次接触elk,因此走了不少的弯路,主要遇到的问题有:
1、Python如何连接es服务器——elasticsearch库
2、如何发送查询语句,获取查询结果——Query DSL查询语法
3、查询结果较多时,如何进行查询和保存——scroll方法
4、网络异常/查询失败时,如何自动重连——try+catch+while语法

在爆肝了几个晚上之后,总算把以上的全部问题都解决。目前脚本已经能够自动完成大批量的查询(百万量级以上),更改几个简单的参数就可以查询不同类型的日志数据。

总的来说,以下内容是基于实际工作应用的,可能无法包含所有的情况。大家遇到无法解决的问题都欢迎评论回复沟通

正文

问题1:Python如何连接es服务器

其实非常的简单,Python已经有了完善的库函数elasticsearch,只需要导入第三方库,然后实例化es对象就可以建立连接了。

from elasticsearch import Elasticsearch
es_url = "http://127.0.0.1:9200/"
es = Elasticsearch(es_url)

“http://127.0.0.1:9200/” 换成要访问的es服务器地址就可以,一般端口都是9200
连接过程是非常快的,基本在0.1秒左右就能完成连接的建立。但是这时候不会有数据回传,我们需要通过查询语句,才可以进行访问服务器数据

问题2:如何发送查询语句及获取查询结果

发送查询语句

es储存数据的结构和Mysql之类的数据库,其实是差不多的。都是选定表名,然后执行查询语句,通过es.search方法,可以完成查询的操作

res = es.search(index=index_name,
                body=query_body,
                scroll='20m',      
                request_timeout=20,   
                size=10000
                )

es.seach方法的各个参数说明如下:

-index:表名。es的结构只有表没有库,index就类似于mysql的表名,指定需要查询的数据表
-body:查询体。就是es的查询语句,使用DSL语句,相比SQL语句要复杂得多,但是基本逻辑其实是类似的
-scroll:游标有效期 。指定了这个参数之后,会启用scroll方法,用于批量获取数据。"20m"代表scroll游标的有效期为20分钟
-request_timeout:超时等待时间 。20表示20秒没有响应,会终止查询并报错
-size:返回大小 。返回结果的长度,10000表示查询最大返回10000条记录,超出的结果不会返回。单次查询最大长度为10000,设置太大了会报错。当查询的结果比较多的时候,还是得用scroll方法

其中最难的应该是body,因为es使用的是专用的Query DSL 语法,不太容易掌握。由于我需要查询的语句较为单一,所以目前用的是自己不断试错得到的一个语句。
针对其他较为复杂的场景,建议找一个SQL to ES之类的工具包来使用(目前还没有找到基于python写的,后面找到了再更新)

如何读取查询结果

返回的查询结果以字典的形式储存在res中,除了我们需要的数据,还包含了很多状态信息,大部分的状态信息都是不需要的。通常我们通过下面的方式来获取所需的数据

total_num = res["hits"]["total"]  #int
data=res["hits"]["hits"]    #list

total_num查看查询到的结果有多少条
data是包含所有结果数据的list对象,其中每个元素都是一个字典

如果不需要获取所有的数据,或者总数据量小于10000,那么以上的内容已经能够满足大家通过python访问es的需求了
但是如果需要大量的查询数据,那么就要用到scroll方法来辅助

问题3:查询结果较多时,如何进行查询和保存

出于可靠性、数据传输大小、服务器稳定性等等多方面的原因,es不接受一次性的对数据库的所有内容进行遍历,因此要达到遍历所有查询结果的需求,只能通过scroll方法

es的scroll游标

scroll可以理解为一个游标,或者说是书签。当发起一个查询请求的时候,服务器内会整理完全部的结果,但是这些结果不能一次性全部导出,只能分页导出,scroll就是这些页码的书签。举例来说:
某次查询,共得到3.5w个匹配的结果
第一次查询时:

这次总共查询到【3.5w个结果】,这是第1~10000个结果,我做了一个书签放在了第10001个结果这里
你要其他结果的话,下次告诉我书签编号就可以

第二次查询,调用scroll方法:

这个书签我知道,就是上次查的3.5w个结果,我现在给你第10001~20000个结果,并且把书签放在了第20001个结果这里

第三次查询,同理省略

第四次查询,调用scroll方法:

这个书签我知道,就是上次查的3.5w个结果,我现在给你剩下的第30001~35000个结果,现在结果都输出完了。书签可以扔掉了

调用scroll方法的代码也非常简单,只需要传入scroll_id

scrl_id = res["_scroll_id"]   #从第一次的查询结果中,取scroll_id
res_02 = es.scroll(scroll_id=scrl_id, scroll='5m')     #scroll参数必须指定否则会报错

这里res是第一批查询结果,res_02是第二批查询结果。这里只要加一个循环结构,就可以一直遍历完所有的查询结果,并进行输出

for i in range(round(total_num/10000)):   #每一批结果是10000w个,计算总共循环的次数
    t4_0=tm.time()
    res = es.scroll(scroll_id=scrl_id, scroll='5m')     #scroll参数必须指定否则会报错
    logs_data += res["hits"]["hits"]  #把每一批的结果拼接起来
    t4_1=tm.time()
    print('Part %d obtained, %5.2f seconds used' % (i+2,t4_1-t4_0))

要注意
1)这里scroll_id是不会变化的,每次查询都会生成唯一的scroll_id,每取一次结果,游标的位置会自动改变,但是id不会变。scroll只能用来遍历,不能针对性的输出某一页的结果
2)我这里采用了拼接的方式,把每一页的结果都整合进logs_data这个变量里面,便于后续的分析
3)计时的这个最好是加上,因为查询返回这个过程是最耗费时间的部分,加上timer有助于我们查看进度和优化代码效率

问题4:网络异常/查询失败时,如何自动重连

这个其实是我自己加进去的一个小功能,因为我的网络问题,而且常常需要查询大量的数据,经常出现跑一次等60s然后弹出个错误的情况,所以稍微研究了一下怎么建立自动重连机制

其实就是做一个很简单的异常处理的while循环,让代码出错时最多重试5次

while trytimes<=5:
    try:
        ESquery()  #上述的查询的流程,我打包成了一个函数
        print('query succeed')
        trytimes=1  #计数器重置
        break     #跳出循环
    except:
        print('连接错误,第{}次重试...'.format(trytimes))  
        trytimes+=1

结语

通过以上的这些核心代码,已经能够正常的完成一次量级较大的查询操作了。
上述内容都非常的简单,只要照着代码做就可以了,也不需要考虑太多的意外情况。主要的难点可能是Query DSL方面,多次尝试就可以

以上。

你可能感兴趣的:(elasticsearch,python,数据分析)