使用Python多处理/线程对数据不一致进行故障诊断

时间:2015-01-21 14:38:58

标签: python multithreading solr multiprocessing python-multiprocessing

TL; DR:使用线程和多处理以及单线程运行代码后获得不同的结果。需要有关故障排除的指

您好,我提前道歉,如果这可能有点过于通用,但我需要一些帮助解决问题,我不知道如何最好地继续。

这是故事;我有一堆索引到Solr Collection中的数据(~250m项目),该集合中的所有项目都有一个sessionid。某些项目可以共享相同的会话ID。我正在梳理整个集合以提取具有相同会话的所有项目,稍微按下数据并吐出另一个JSON文件以便稍后进行索引。

代码有两个主要功能: proc_day - 接受一天并处理当天的所有会话 和 proc_session - 为单个会话完成所有需要的事情。

多处理是在proc_day上实现的,所以每天都会由一个单独的进程处理,proc_session函数可以用线程运行。下面是我在下面用于线程/多处理的代码。它接受一个函数,一个参数列表和一些线程/多进程。然后它将根据输入参数创建一个队列,然后创建进程/线程并让它们通过它。我没有发布实际代码,因为它通常运行良好的单线程没有任何问题,但可以在需要时发布它。

autoprocs.py

import sys
import logging
from multiprocessing import Process, Queue,JoinableQueue
import time
import multiprocessing
import os

def proc_proc(func,data,threads,delay=10):
    if threads < 0:
        return
    q = JoinableQueue()
    procs = []

    for i in range(threads):
        thread = Process(target=proc_exec,args=(func,q))
        thread.daemon = True;
        thread.start()
        procs.append(thread)

    for item in data:
        q.put(item)

    logging.debug(str(os.getpid()) + ' *** Processes started and data loaded into queue waiting')

    s = q.qsize()
    while s > 0:
        logging.info(str(os.getpid()) + " - Proc Queue Size is:" + str(s))
        s = q.qsize()
        time.sleep(delay)

    for p in procs:
        logging.debug(str(os.getpid()) + " - Joining Process {}".format(p))
        p.join(1)

    logging.debug(str(os.getpid()) + ' - *** Main Proc waiting')
    q.join()
    logging.debug(str(os.getpid()) + ' - *** Done')

def proc_exec(func,q):
    p = multiprocessing.current_process()
    logging.debug(str(os.getpid()) + ' - Starting:{},{}'.format(p.name, p.pid))
    while True:
        d = q.get()
        try:
            logging.debug(str(os.getpid()) + " - Starting to Process {}".format(d))
            func(d)
            sys.stdout.flush()
            logging.debug(str(os.getpid()) + " - Marking Task as Done")
            q.task_done()
        except:
            logging.error(str(os.getpid()) + " - Exception in subprocess execution")
            logging.error(sys.exc_info()[0])
    logging.debug(str(os.getpid()) + 'Ending:{},{}'.format(p.name, p.pid))

autothreads.py:

import threading
import logging
import time
from queue import Queue

def thread_proc(func,data,threads):
    if threads < 0:
        return "Thead Count not specified"
    q = Queue()

    for i in range(threads):
        thread = threading.Thread(target=thread_exec,args=(func,q))
        thread.daemon = True
        thread.start()

    for item in data:
        q.put(item)

    logging.debug('*** Main thread waiting')
    s = q.qsize()
    while s > 0:
        logging.debug("Queue Size is:" + str(s))
        s = q.qsize()
        time.sleep(1)
    logging.debug('*** Main thread waiting')
    q.join()
    logging.debug('*** Done')

def thread_exec(func,q):
    while True:
        d = q.get()
        #logging.debug("Working...")
        try:
            func(d)
        except:
            pass
        q.task_done()

在python在不同的多处理/线程配置下运行后,我遇到了验证数据的问题。有很多数据,所以我真的需要让多处理工作。以下是我昨天的测试结果。

Only with multiprocessing - 10 procs: 
Days Processed  30
Sessions Found  3,507,475 
Sessions Processed 3,514,496 
Files 162,140 
Data Output: 1.9G

multiprocessing and multithreading - 10 procs 10 threads
Days Processed  30
Sessions Found   3,356,362 
Sessions Processed   3,272,402 
Files    424,005 
Data Output: 2.2GB

just threading - 10 threads
Days Processed  31
Sessions Found   3,595,263 
Sessions Processed   3,595,263 
Files    733,664 
Data Output: 3.3GB

Single process/ no threading
Days Processed  31
Sessions Found   3,595,263 
Sessions Processed   3,595,263 
Files    162,190 
Data Output: 1.9GB

这些计数是通过日志文件中的grepping和counties条目收集的(每个主进程1个)。跳出来的第一件事是处理的天数不匹配。但是,我手动检查了日志文件,它看起来像一个日志条目丢失,后面有日志条目,表明该日实际处理。我不知道为什么它被省略了。

我真的不想编写更多代码来验证这段代码,只是看起来像是浪费时间,有什么办法吗?

2 个答案:

答案 0 :(得分:1)

我在上面的评论中给出了一些一般性的提示。我认为在不同的抽象层次上,您的方法存在多个问题。您也没有显示所有相关代码。

问题可能就是

  1. 在您使用的方法中读取solr或准备读取数据,然后再将其提供给您的工作人员。
  2. 在您提出的用于在多个流程之间分配工作的架构中。
  3. 在您的日志记录基础结构中(正如您自己指出的那样)。
  4. 在您的分析方法中。
  5. 必须完成所有这些要点,而且由于问题的复杂性,肯定没有人能够为您确定具体问题。

    关于点(3)和(4):

    如果您不确定日志文件的完整性,则应根据处理引擎的有效负载输出执行分析。我想说的是:日志文件可能只是数据处理的副产品。主要产品是你应该分析的东西。当然,正确使用您的日志也很重要。但这两个问题应该独立对待。

    我对上面列表中第(2)点的贡献:

    对于基于multiprocessing的解决方案,特别可疑的是等待让工作人员完成的方式。您似乎不确定应该使用哪种方法等待您的工作人员,因此您应用了三种不同的方法:

    首先,您正在监视while循环中队列的大小并等待它变为0.这是一种非规范方法,可能实际上有效。

    其次,您以奇怪的方式join()您的流程:

    for p in procs:
        logging.debug(str(os.getpid()) + " - Joining Process {}".format(p))
        p.join(1)
    

    为什么在这里定义一秒的超时并且不响应该进程是否在该时间范围内实际终止?您应该真正加入一个进程,即等到已终止或指定超时,如果超时在进程完成之前到期,请特别处理该情况。您的代码无法区分这些情况,因此p.join(1)就像编写time.sleep(1)一样。

    第三次,您加入队列。

    因此,在确保q.qsize()返回0并等待另一秒后,您是否真的认为加入队列很重要?它有什么不同吗? 这些方法中的一个应该足够了,您需要考虑哪些标准对您的问题最重要。也就是说,其中一个条件确定性地暗示另外两个。

    所有这些看起来像一个快速&amp;多处理解决方案的肮脏黑客,而你自己并不确定该解决方案应该如何表现。我在处理并发体系结构时获得的最重要的见解之一:您,架构师,必须100%了解通信和控制流在您的系统中的工作方式。不正确地监视和控制工作进程的状态很可能是您正在观察的问题的根源。

答案 1 :(得分:1)

我弄清楚了,我遵循了Jan-Philip的建议并开始检查多进程/多线程进程的输出数据。事实证明,使用Solr中的数据执行所有这些操作的对象是在线程之间共享的。我没有任何锁定机制,所以在一个案例中它混合了来自多个会话的数据,导致输出不一致。我通过为每个线程实例化一个新对象并且计数匹配来验证这一点。它有点慢,但仍然可行。

由于