大数据量文本文件高效解析方案代码实现

测试环境

Python 3.6.2

Win 10 内存 8G,CPU I5 1.6 GHz

背景描述

这个作品来源于一个日志解析工具的开发,这个开发过程中遇到的一个痛点,就是日志文件多,日志数据量大,解析耗时长。在这种情况下,寻思一种高效解析数据解析方案。

解决方案描述

1、采用多线程读取文件

2、采用按块读取文件替代按行读取文件

由于日志文件都是文本文件,需要读取其中每一行进行解析,所以一开始会很自然想到采用按行读取,后面发现合理配置下,按块读取,会比按行读取更高效。

按块读取来的问题就是,可能导致完整的数据行分散在不同数据块中,那怎么解决这个问题呢?解答如下:

将数据块按换行符\n切分得到日志行列表,列表第一个元素可能是一个完整的日志行,也可能是上一个数据块末尾日志行的组成部分,列表最后一个元素可能是不完整的日志行(即下一个数据块开头日志行的组成部分),也可能是空字符串(日志块中的日志行数据全部是完整的),根据这个规律,得出以下公式,通过该公式,可以得到一个新的数据块,对该数据块二次切分,可以得到数据完整的日志行

上一个日志块首部日志行 +\n + 尾部日志行 + 下一个数据块首部日志行 + \n + 尾部日志行 + ...

3、将数据解析操作拆分为可并行解析部分和不可并行解析部分

数据解析往往涉及一些不可并行的操作,比如数据求和,最值统计等,如果不进行拆分,并行解析时势必需要添加互斥锁,避免数据覆盖,这样就会大大降低执行的效率,特别是不可并行操作占比较大的情况下。

对数据解析操作进行拆分后,可并行解析操作部分不用加锁。考虑到Python GIL的问题,不可并行解析部分替换为单进程解析。

4、采用多进程解析替代多线程解析

采用多进程解析替代多线程解析,可以避开Python GIL全局解释锁带来的执行效率问题,从而提高解析效率。

5、采用队列实现“协同”效果

引入队列机制,实现一边读取日志,一边进行数据解析:

  1. 日志读取线程将日志块存储到队列,解析进程从队列获取已读取日志块,执行可并行解析操作
  2. 并行解析操作进程将解析后的结果存储到另一个队列,另一个解析进程从队列获取数据,执行不可并行解析操作。

代码实现

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import re
import time
from datetime import datetime
from joblib import Parallel, delayed, parallel_backend
from collections import deque
from multiprocessing import cpu_count
import threading class LogParser(object):
def __init__(self, chunk_size=1024*1024*10, process_num_for_log_parsing=cpu_count()):
self.log_unparsed_queue = deque() # 用于存储未解析日志
self.log_line_parsed_queue = deque() # 用于存储已解析日志行
self.is_all_files_read = False # 标识是否已读取所有日志文件
self.process_num_for_log_parsing = process_num_for_log_parsing # 并发解析日志文件进程数
self.chunk_size = chunk_size # 每次读取日志的日志块大小
self.files_read_list = [] # 存放已读取日志文件
self.log_parsing_finished = False # 标识是否完成日志解析 def read_in_chunks(self, filePath, chunk_size=1024*1024):
"""
惰性函数(生成器),用于逐块读取文件。
默认区块大小:1M
""" with open(filePath, 'r', encoding='utf-8') as f:
while True:
chunk_data = f.read(chunk_size)
if not chunk_data:
break
yield chunk_data def read_log_file(self, logfile_path):
'''
读取日志文件
这里假设日志文件都是文本文件,按块读取后,可按换行符进行二次切分,以便获取行日志
''' temp_list = [] # 二次切分后,头,尾行日志可能是不完整的,所以需要将日志块头尾行日志相连接,进行拼接
for chunk in self.read_in_chunks(logfile_path, self.chunk_size):
log_chunk = chunk.split('\n')
temp_list.extend([log_chunk[0], '\n'])
temp_list.append(log_chunk[-1])
self.log_unparsed_queue.append(log_chunk[1:-1])
self.log_unparsed_queue.append(''.join(temp_list).split('\n'))
self.files_read_list.remove(logfile_path) def start_processes_for_log_parsing(self):
'''启动日志解析进程''' with parallel_backend("multiprocessing", n_jobs=self.process_num_for_log_parsing):
Parallel(require='sharedmem')(delayed(self.parse_logs)() for i in range(self.process_num_for_log_parsing)) self.log_parsing_finished = True def parse_logs(self):
'''解析日志''' method_url_re_pattern = re.compile('(HEAD|POST|GET)\s+([^\s]+?)\s+',re.DOTALL)
url_time_taken_extractor = re.compile('HTTP/1\.1.+\|(.+)\|\d+\|', re.DOTALL) while self.log_unparsed_queue or self.files_read_list:
if not self.log_unparsed_queue:
continue
log_line_list = self.log_unparsed_queue.popleft()
for log_line in log_line_list:
#### do something with log_line
if not log_line.strip():
continue res = method_url_re_pattern.findall(log_line)
if not res:
print('日志未匹配到请求URL,已忽略:\n%s' % log_line)
continue
method = res[0][0]
url = res[0][1].split('?')[0] # 去掉了 ?及后面的url参数 # 提取耗时
res = url_time_taken_extractor.findall(log_line)
if res:
time_taken = float(res[0])
else:
print('未从日志提取到请求耗时,已忽略日志:\n%s' % log_line)
continue # 存储解析后的日志信息
self.log_line_parsed_queue.append({'method': method,
'url': url,
'time_taken': time_taken,
}) def collect_statistics(self):
'''收集统计数据''' def _collect_statistics():
while self.log_line_parsed_queue or not self.log_parsing_finished:
if not self.log_line_parsed_queue:
continue
log_info = self.log_line_parsed_queue.popleft()
# do something with log_info with parallel_backend("multiprocessing", n_jobs=1):
Parallel()(delayed(_collect_statistics)() for i in range(1)) def run(self, file_path_list):
# 多线程读取日志文件
for file_path in file_path_list:
thread = threading.Thread(target=self.read_log_file,
name="read_log_file",
args=(file_path,))
thread.start()
self.files_read_list.append(file_path) # 启动日志解析进程
thread = threading.Thread(target=self.start_processes_for_log_parsing, name="start_processes_for_log_parsing")
thread.start() # 启动日志统计数据收集进程
thread = threading.Thread(target=self.collect_statistics, name="collect_statistics")
thread.start() start = datetime.now()
while threading.active_count() > 1:
print('程序正在努力解析日志...')
time.sleep(0.5) end = datetime.now()
print('解析完成', 'start', start, 'end', end, '耗时', end - start) if __name__ == "__main__":
log_parser = LogParser()
log_parser.run(['access.log', 'access2.log'])

注意:

需要合理的配置单次读取文件数据块的大小,不能过大,或者过小,否则都可能会导致数据读取速度变慢。笔者实践环境下,发现10M~15M每次是一个比较高效的配置。

Python 大数据量文本文件高效解析方案代码实现的更多相关文章

  1. 使用OPENROWSET、Microsoft.ACE.OLEDB实现大数据量的高效导入

    首先说明使用的环境是:java和Sqlserver. 最近公司需要进行大数据量的导入操作.原来使用的是Apache POI,虽然可以实现功能,但是因为逻辑处理中需要进行许多校验,处理速度太慢,使用多线 ...

  2. 对于大数据量的Json解析

    近几天做了一个项目,需要解析大量的json数据,有一万多条,以前我用的都是Gson包去自动解析,但是速度真是不敢恭维,于是我又去查了其它的方法,发现fastjson的解析,发现速度直的是很快,在此我不 ...

  3. 参考 ZTree 加载大数据量。加载慢问题解析

    参考 ZTree 加载大数据量. 1.一次性加载大数据量加载说明 1).zTree v3.x 针对大数据量一次性加载进行了更深入的优化,实现了延迟加载功能,即不展开的节点不创建子节点的 DOM. 2) ...

  4. 大数据量数据库设计与优化方案(SQL优化)

    转自:http://blog.sina.com.cn/s/blog_6c0541d50102wxen.html 一.数据库结构的设计 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的 ...

  5. 【Python开发】Python 适合大数据量的处理吗?

    Python 适合大数据量的处理吗? python 能处理数据库中百万行级的数据吗? 处理大规模数据时有那些常用的python库,他们有什么优缺点?适用范围如何? 需要澄清两点之后才可以比较全面的看这 ...

  6. C#实现大数据量TXT文本数据快速高效去重

    原文 C#实现大数据量TXT文本数据快速高效去重 对几千万的TXT文本数据进行去重处理,查找其中重复的数据,并移除.尝试了各种方法,下属方法是目前尝试到最快的方法.以下代码将重复和不重复数据进行分文件 ...

  7. 大数据量报表APPLET打印分页传输方案

     1 . 问题概述 当报表运算完成时,客户端经常需要调用润乾自带的runqianReport4Applet.jar来完成打印操作, 然而数据量比较大的时候,会导致无法加载完成,直至applet内存 ...

  8. PGIS大数据量点位显示方案

    PGIS大数据量点位显示方案 问题描述 PGIS在地图上显示点位信息时,随点位数量的增加浏览器响应速度会逐渐变慢,当同时显示上千个点时浏览器会变得非常缓慢,以下是进行的测试: 测试环境: 服务器: C ...

  9. 大数据量高并发的数据库优化详解(MSSQL)

    转载自:http://www.jb51.net/article/71041.htm 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能. ...

  10. 大数据量高并发的数据库优化,sql查询优化

    一.数据库结构的设计 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能.所以,在一个系统开始实施之前,完备的数据库模型的设计是必须的. ...

随机推荐

  1. .NET 反向代理 YARP 代理 GRPC

    前面的 YARP 文档中,介绍了怎么去代理 http,和如何根据域名转发,而在现在微服务的应用是越来越来多了,服务间的调用依靠 http 越来越不现实了,因为 http 多次握手的耗时越发的影响应用的 ...

  2. [题解] Codeforces Dytechlab Cup 2022 1737 A B C D E 题解

    傻*Dytechlab还我rating!(不过目前rating还没加上去,据说E是偷的说不定要unrated) 实在没预料到会打成这样... 求点赞 点我看题 A. Ela Sorting Books ...

  3. P4047 [JSOI2010]部落划分 方法记录

    原题链接 [JSOI2010]部落划分 题目描述 聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人们总是拉帮结派形成属于自己的部落,不同的部落之间则经常 ...

  4. GitLab + Jenkins + Harbor 工具链快速落地指南

    目录 一.今天想干啥? 二.今天干点啥? 三.今天怎么干? 3.1.常规打法 3.2.不走寻常路 四.开干吧! 4.1.工具链部署 4.2.网络配置 4.3.验证工具链部署结果 4.3.1.GitLa ...

  5. 关于JDK8中stream的用法小总结。

    import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; public class Ma ...

  6. linux安装达梦数据库8

    PS.本次测试只是为了项目需要,但是在部署和启动程序的时候发生了一系列的报错,由此记录下来为日后作参考 安装达梦数据库 1. 达梦数据库(DM8)简介 达梦数据库管理系统是武汉达梦公司推出的具有完全自 ...

  7. js排序的基础原理理解

    在了解数组排序前,我们先了解下如何交换两个数字的位置 假设我们有这么一个数组 var arr=[1,2,3] 我们想交换1和3两个的位置我们该如何做呢? 最简单的肯定是手动赋值,如下 var arr= ...

  8. day04-JavaScript01

    JavaScript01 官方文档 http://www.w3school.com.cn/js/index.asp 基本说明: JavaScript能改变html内容,能改变html属性,能改变htm ...

  9. JVM学习笔记——内存结构篇

    JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...

  10. Aspose.Cell和NPOI生成Excel文件2

    NPOI还是比较好用的,引用dll程序集即可 1创建workbook和工作流 HSSFWorkbook workbook = new HSSFWorkbook(); MemoryStream ms = ...