在Python中组合不同类的几个大型数据结构;如何在减少内存使用的同时组合和存储所需的数据



发生了什么

在Python 2.7.8中,我每隔几分钟就通过包netsnmp从数千个网络设备收集数据。我还使用了fastsnmpy,这样我就可以访问(更高效的(Net SNMP命令snmpbulkwalk

我正在努力减少我的脚本使用的内存。我正在运行同一脚本的三个实例,该脚本会休眠两分钟,然后再向所有设备重新查询我们想要的数据。当我在bash中创建原始脚本时,它们在同时处于活动状态时将使用不到500MB。然而,当我将其转换为Python时,每个实例占用4GB每个,这表明(对我来说(需要更有效地管理我的数据结构。即使在空闲时,它们也会总共消耗4GB。


代码活动

我的脚本首先创建一个列表,在其中打开一个文件,并将目标设备的主机名作为单独的值附加。这些通常包含80到1200个名称。

expand = []
f = open(self.deviceList, 'r')
for line in f:
    line = line.strip()
    expand.append(line)

从那里我设置SNMP会话并执行请求

expandsession = SnmpSession ( timeout = 1000000 ,
    retries = 1,            # I slightly modified the original fastsnmpy
    verbose = debug,        # to reduce verbose messages and limit
    oidlist = var,          # the number of attempts to reach devices
    targets = expand,
    community = 'expand'
)
expandresults = expandsession.multiwalk(mode = 'bulkwalk')

由于两个SNMP包的行为方式,设备响应被解析为列表,并存储在一个巨大的数据结构中。例如,

for output in expandresults:
    print ouput.hostname, output.iid, output.val
#
host1 1 1
host1 2 2
host1 3 3
host2 1 4
host2 2 5
host2 3 6
# Object 'output' itself cannot be printed directly; the value returned from this is obscure
...

我必须迭代每个响应,组合相关数据,然后输出每个设备的完整响应。这有点困难。例如,

host1,1,2,3
host2,4,5,6
host3,7,8,9,10,11,12
host4,13,14
host5,15,16,17,18
...

每个设备都有不同数量的响应。我不能指望每个设备都有一个统一的任意数量的值,然后组合成一个字符串,写入CSV。


我如何处理数据

我相信这是我消耗大量内存的地方,但我无法解决如何在删除访问数据的同时简化流程。

expandarrays = dict()
for output in expandresults:
    if output.val is not None:
        if output.hostname in expandarrays:
            expandarrays[output.hostname] += ',' + output.val
        else:
            expandarrays[output.hostname] = ',' + output.val
for key in expandarrays:
    self.WriteOut(key,expandarrays[key])

目前,我正在创建一个新的字典,检查设备响应是否为null,然后将响应值附加到一个字符串中,该字符串将用于写入CSV文件。

问题是,我本质上是在克隆现有的字典,这意味着我使用的系统内存是原来的两倍。当我将值移到expandarrays时,我想删除我在expandresults中访问过的值,这样我就不会使用太多RAM。有有效的方法吗?还有没有更好的方法可以降低代码的复杂性,使其更容易遵循?


罪犯

感谢回答的人。对于那些将来因为遇到类似问题而偶然发现这个线程的人来说:fastsnmpy包是大量使用系统内存的罪魁祸首。multiwalk()函数为每个主机创建一个线程,但同时执行,而不是设置某种上限。由于我的脚本的每个实例将处理多达1200个设备,这意味着1200个线程在几秒钟内被实例化并排队。使用bulkwalk()函数的速度较慢,但仍然足够快,可以满足我的需求。两者之间的差异是4GB和250MB(系统内存使用量(。

如果设备响应按顺序排列并按主机分组,那么您不需要字典,只需要三个列表:

last_host = None
hosts = []                # the list of hosts
host_responses = []       # the list of responses for each host
responses = []
for output in expandresults:
    if output.val is not None:
        if output.hostname != last_host:    # new host
            if last_host:    # only append host_responses after a new host
                host_responses.append(responses)
            hosts.append(output.hostname)
            responses = [output.val]        # start the new list of responses
            last_host = output.hostname
        else:                               # same host, append the response
            responses.append(output.val)
host_responses.append(responses)
for host, responses in zip(hosts, host_responses):
    self.WriteOut(host, ','.join(responses))

内存消耗是由于以未绑定的方式实例化了几个工作者。

我已经更新了fastsnmpy(最新版本是1.2.1(并将其上传到PyPi。你可以从PyPi中搜索"fastsnmpy",或者获取它直接从我在FastSNMPy 的PyPi页面

刚刚更新完文档,并将其发布到fastSNMPy docs 的项目页面

我在这里所做的基本上是用来自多处理的进程池来替换早期的未绑定工作者模型。这可以作为参数传入,也可以默认为1。

为了简单起见,现在只有2个方法。snmpwalk(进程=n(和snmpbulkwalk(进程=n(

你不应该再看到内存问题了。如果你这样做了,请在github上给我打电话。

通过使用探查器,您可能更容易弄清楚内存的去向

https://pypi.python.org/pypi/memory_profiler

此外,如果您已经在调整fastsnmpy类,您可以更改实现,为您进行基于字典的结果合并,而不是让它先构建一个巨大的列表。

你要坚持多久?如果重复使用,结果列表将无限期增长。

最新更新