Python3使用队列进行并发日志处理

44 篇文章 0 订阅

简单来说logging是线程安全,而非进程安全,因此想要将多个进程的日志输出到同一文件,需要借助消息队列或者socket,再在单一进程中监听该队列或socket,输出至文件中

Although logging is thread-safe, and logging to a single file from multiple threads in a single process is supported, logging to a single file from multiple processes is not supported, because there is no standard way to serialize access to a single file across multiple processes in Python. If you need to log to a single file from multiple processes, one way of doing this is to have all the processes log to a SocketHandler, and have a separate process which implements a socket server which reads from the socket and logs to file. (If you prefer, you can dedicate one thread in one of the existing processes to perform this function.) This section documents this approach in more detail and includes a working socket receiver which can be used as a starting point for you to adapt in your own applications.

You could also write your own handler which uses the Lock class from the multiprocessing module to serialize access to the file from your processes. The existing FileHandler and subclasses do not make use of multiprocessing at present, though they may do so in the future. Note that at present, the multiprocessing module does not provide working lock functionality on all platforms (see https://bugs.python.org/issue3770).

Alternatively, you can use a Queue and a QueueHandler to send all logging events to one of the processes in your multi-process application. The following example script demonstrates how you can do this; in the example a separate listener process listens for events sent by other processes and logs them according to its own logging configuration. Although the example only demonstrates one way of doing it (for example, you may want to use a listener thread rather than a separate listener process – the implementation would be analogous) it does allow for completely different logging configurations for the listener and the other processes in your application, and can be used as the basis for code meeting your own specific requirements:

我在官方示例代码的基础上进行了一些修改:

import multiprocessing
import logging
import logging.handlers
import time

logger_pool = {}


class Loggers:
    global logger_pool

    def get_listener_logger(self, id):
        formatter = logging.Formatter("%(message)s")
        logger = logging.getLogger(id)
        logger.setLevel(logging.INFO)

        #handler = logging.handlers.RotatingFileHandler('log.log') 
        handler = logging.StreamHandler() 
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        return logger

    def get_worker_logger(self, id, queue):
        """
        如果池中存在则取出
        如果不存在则创建
        """
        if logger_pool.get(id):
            return logger_pool.get(id)
        else:
            """
            创建日志实例
            """
            formatter = logging.Formatter("[%(asctime)s] %(name)s:%(levelname)s: %(message)s")
            logger = logging.getLogger(id)
            logger.setLevel(logging.INFO)

            handler = logging.handlers.QueueHandler(queue)
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger_pool[id] = logger
            return logger

logger_class = Loggers()

def listener_process(queue, flag_queue):
    listener_logger = logger_class.get_listener_logger('listener')
    while True:
        if queue.empty() and flag_queue.empty():
            print('listener stop!')
            break
        else:
            try:
                record = queue.get(timeout=2)
            except:
                continue
            listener_logger.handle(record)

def worker_process(id, queue, flag_queue):
    try:
        logger = logger_class.get_worker_logger(id, queue)
        for _ in range(10):
            logger.info(time.time())
            time.sleep(1)
    finally:
        print('worker stop!')
        flag_queue.get()
    
if __name__ == "__main__":
    queue = multiprocessing.Queue(-1)
    flag_queue = multiprocessing.Queue(-1)
    
    id_list = ['00', '01', '02', '03']
    process_pool = []
    for id in id_list:
        flag_queue.put(id)

        p = multiprocessing.Process(target=worker_process, args=(id, queue, flag_queue,))
        p.start()
        process_pool.append(p)

    listener = multiprocessing.Process(
        target=listener_process, args=(queue, flag_queue,))
    listener.start()
    
    for p in process_pool:
        p.join()
    listener.join()
[2021-02-28 17:37:50,567] 00:INFO: 1614505070.567073
[2021-02-28 17:37:50,567] 01:INFO: 1614505070.567591
[2021-02-28 17:37:50,568] 02:INFO: 1614505070.568983
[2021-02-28 17:37:50,569] 03:INFO: 1614505070.569498
[2021-02-28 17:37:51,570] 02:INFO: 1614505071.570774
[2021-02-28 17:37:51,570] 01:INFO: 1614505071.570773
[2021-02-28 17:37:51,570] 00:INFO: 1614505071.570755
[2021-02-28 17:37:51,570] 03:INFO: 1614505071.570774
[2021-02-28 17:37:52,573] 03:INFO: 1614505072.572994
[2021-02-28 17:37:52,573] 00:INFO: 1614505072.572965
[2021-02-28 17:37:52,573] 01:INFO: 1614505072.572991
[2021-02-28 17:37:52,573] 02:INFO: 1614505072.572993

参考:
https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes
https://fanchenbao.medium.com/python3-logging-with-multiprocessing-f51f460b8778


2021-08-20更新

上面的listener可以使用logging.handlers.QueueListener进行优化
优化后的代码如下

import multiprocessing
import logging
import logging.handlers
import time

logger_pool = {}


class Loggers:
    global logger_pool

    def get_listener_logger(self, id):
        formatter = logging.Formatter("%(message)s")
        logger = logging.getLogger(id)
        logger.setLevel(logging.INFO)

        #handler = logging.handlers.RotatingFileHandler('log.log') 
        handler = logging.StreamHandler() 
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        return logger

    def get_worker_logger(self, id, queue):
        """
        如果池中存在则取出
        如果不存在则创建
        """
        if logger_pool.get(id):
            return logger_pool.get(id)
        else:
            """
            创建日志实例
            """
            formatter = logging.Formatter("[%(asctime)s] %(name)s:%(levelname)s: %(message)s")
            logger = logging.getLogger(id)
            logger.setLevel(logging.INFO)

            handler = logging.handlers.QueueHandler(queue)
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger_pool[id] = logger
            return logger

logger_class = Loggers()

def listener_process(queue):
    formatter = logging.Formatter("%(message)s")
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    listener = logging.handlers.QueueListener(queue, handler)
    return listener

def worker_process(id, queue):
    try:
        logger = logger_class.get_worker_logger(id, queue)
        for _ in range(10):
            logger.info(time.time())
            time.sleep(1)
    finally:
        print('worker stop!')
    
if __name__ == "__main__":
    queue = multiprocessing.Queue(-1)
    
    id_list = ['00', '01', '02', '03']
    process_pool = []
    for id in id_list:
        p = multiprocessing.Process(target=worker_process, args=(id, queue,))
        p.start()
        process_pool.append(p)

    listener = listener_process(queue)
    listener.start()
    
    for p in process_pool:
        p.join()
    listener.stop()

与原来的代码进行对比后发现原先使用单独进程进行队列监听的方式改为了使用主进程的子线程进行监听,大大减少了代码量
在这里插入图片描述
代码对比结果:https://www.diffchecker.com/aHZtLRo8
参考:https://blog.csdn.net/nilnaijgnid/article/details/107498878
https://docs.python.org/3/library/logging.handlers.html#queuelistener

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 要使用Python构建高并发高扩展的学习系统,您可以考虑使用以下技术和框架: 1. 异步编程:使用asyncio库进行异步编程,以便同时处理多个并发请求。 2. 分布式任务队列使用Celery或RabbitMQ等任务队列,将任务分发到多个工作节点,以便实现水平扩展。 3. 缓存系统:使用Redis或Memcached等缓存系统,以便缓存频繁请求的数据,减少对数据库的访问次数。 4. Web框架:使用Django或Flask等Web框架,以便快速搭建Web服务。 5. 数据库:使用MySQL或PostgreSQL等关系型数据库,以便存储和管理系统的数据。 6. 消息中间件:使用Kafka或ActiveMQ等消息中间件,以便异步处理系统中的事件和消息。 通过上述技术和框架的结合使用,您可以构建一个高并发高扩展的学习系统。 ### 回答2: 要实现高并发和高扩展的学习系统,可以按照以下方法利用Python: 1. 使用异步编程:Python提供了诸如asyncio和aiohttp等库,可用于实现异步编程。通过使用异步I/O和协程,可以提高系统的并发处理能力。 2. 使用分布式架构:将系统拆分成多个独立的服务模块,并将其部署在不同的服务器上。可以使用Python的一些开源分布式框架,如Celery或Pyro等来管理任务分发和节点通信,以实现系统的横向扩展能力。 3. 使用缓存和消息队列:将频繁读取的数据进行缓存,例如使用Redis作为缓存数据库。另外,可以使用消息队列(如RabbitMQ或Kafka)进行解耦和异步处理,以避免系统阻塞。 4. 使用负载均衡:通过将流量分发到不同的服务器上,可以实现请求负载的均衡,提高系统的并发能力。可以使用Python实现的一些负载均衡器,如HAProxy或Nginx。 5. 使用高性能的数据库:选择适合并发场景的高性能数据库,如MongoDB或Elasticsearch。这些数据库具有良好的横向扩展性,可以在处理大量数据时提供更好的性能。 6. 使用监控和日志工具:为了追踪系统运行情况并进行性能优化,可以使用Python提供的监控和日志工具,如Prometheus和ELK(Elasticsearch + Logstash + Kibana)。这些工具可以帮助您定位系统的瓶颈和问题,并优化系统性能。 总体而言,使用Python编程语言可以轻松地实现高并发和高扩展的学习系统。通过合理选择和应用相关的库和工具,可以提高系统的并发处理能力,支持大量用户的同时访问,并实现系统的横向扩展。 ### 回答3: 要实现一个高并发和高扩展的学习系统,可以使用Python编程语言,并采用以下几个方法: 1. 使用异步编程: Python提供了多个异步框架(如asyncio和Tornado),可以通过使用异步编程技术来处理并发请求。异步编程可以提高系统的吞吐量,同时避免阻塞线程的情况。 2. 使用分布式架构: 使用Python的分布式系统框架(例如Celery或Pyro),可以将任务分发到多个节点或服务器上进行处理。这样能够提高系统的扩展性,分担服务器的负载,以应对大量并发请求。 3. 数据库优化: 对于学习系统来说,数据库是一个重要的组成部分。使用高性能的数据库技术,如NoSQL(如MongoDB)或数据缓存(如Redis),可以加快数据库的读写速度,提高系统的并发处理能力。 4. 使用缓存: 在学习系统中,有些数据是可以被缓存的,如用户信息、用户学习的课程等。使用缓存技术,如Memcached或Redis,可以将经常访问的数据存储在内存中,减少对数据库的访问,提高系统的响应速度和并发能力。 5. 水平扩展: 当系统的负载逐渐增加时,可以考虑将系统进行水平扩展。使用负载均衡器,将请求分发给多个服务器处理,可以提高系统的并发处理能力,并保持系统的可用性。 6. 异常处理和错误日志: 在高并发系统中,出现异常和错误是不可避免的。使用适当的异常处理机制,并记录详细的错误日志,有助于排查问题和改进系统性能。 总之,使用Python编程语言可以通过异步编程、分布式架构、数据库优化、缓存、水平扩展等技术手段来实现高并发和高扩展的学习系统。这些方法可以提高系统的并发处理能力、响应速度和吞吐量,以满足大量用户同时访问的需求。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值