在芹菜Python中,在进程(工作者)之间共享数据的最佳解决方案是什么?

在芹菜Python中,在进程(工作者)之间共享数据的最佳解决方案是什么?,python,redis,multiprocessing,celery,shared-memory,Python,Redis,Multiprocessing,Celery,Shared Memory,我有一个应用程序,它每100毫秒从传感器读取一次浮点数据,将其附加到列表中,每5分钟从该列表中计算一些统计数据,并插入MongoDB数据库。然后它会清理列表,依此类推 有很多这样的列表(和传感器一样多),我需要定期读取数据,所以我设置了芹菜工人。它工作得很好,但每个芹菜工作者都有自己特定的全局变量空间,所以在插入到数据库时,列表具有不同的值,这取决于哪些工作者实际将数据插入到数据库 工作人员之间共享数据并以某种方式锁定数据以防止多个工作人员将其自身版本的传感器数据插入数据库的解决方案是什么 我考

我有一个应用程序,它每100毫秒从传感器读取一次浮点数据,将其附加到列表中,每5分钟从该列表中计算一些统计数据,并插入MongoDB数据库。然后它会清理列表,依此类推

有很多这样的列表(和传感器一样多),我需要定期读取数据,所以我设置了芹菜工人。它工作得很好,但每个芹菜工作者都有自己特定的全局变量空间,所以在插入到数据库时,列表具有不同的值,这取决于哪些工作者实际将数据插入到数据库

工作人员之间共享数据并以某种方式锁定数据以防止多个工作人员将其自身版本的传感器数据插入数据库的解决方案是什么

我考虑过Redis,将传感器数据直接添加到Redis dict,每5分钟从Redis读取数据,计算统计数据,清理Redis dict,等等

import celery
import my_data_reader
import my_stats_calculator
import my_mongo_manager

app = celery.Celery('tasks', broker='redis://localhost')

sensor_data = []

data_reader = my_data_reader.TemperatureReader(1)
mongo_writer = my_mongo_manager.DataWriter()
stats_calculator = my_stats_calculator.Calculator()


# Runs every 100 milliseconds
@app.task
def update_sensors():

    global sensor_data
    global data_reader

    sensor_data.append(data_reader.get_data())

# Runs every 5 seconds
@app.task
def insert_to_database():

    global sensor_data
    global mongo_writer
    global stats_calculator

    stats_dict = stats_calculator.calculate_stats(sensor_data)
    mongo_writer.insert_data(stats_dict)
    del sensor_data[:]
在使用1个进程(--concurrency=1芹菜标志)运行此代码之后,它工作得非常好,但是在实际项目中有超过25个传感器,我希望以某种方式高效地执行这些操作


有人知道在工作人员之间共享这些对象的正确方式吗?

我通过Redis和其他一些东西找到了实现方法。我提供工作代码。如果有人知道更好的解决方案,请张贴在这里

首先,我为芹菜任务编写了一个装饰器,它可以防止多个工作人员同时操作Redis数据。我做了一些研究,发现了一个轻量级的

然而,我看到有其他选择,以实现这一点,使用第三方模块,如夏洛克或芹菜一次

import celery
import redis
import pymongo
from datetime import datetime as dt

app = celery.Celery('tasks', broker='redis://localhost')
redis_client = redis.Redis()

def only_one(function=None, key="", timeout=None):
    """Enforce only one celery task at a time."""

    def _dec(run_func):
        """Decorator."""

        def _caller(*args, **kwargs):
            """Caller."""
            ret_value = None
            have_lock = False
            lock = redis_client.lock(key, timeout=timeout)
            try:
                have_lock = lock.acquire(blocking=False)
                if have_lock:
                    ret_value = run_func(*args, **kwargs)
            finally:
                if have_lock:
                    lock.release()

            return ret_value

        return _caller

    return _dec(function) if function is not None else _dec
实现自定义任务-运行方法现在由我们的Redis锁修饰

class SensorTask(app.Task):
    """A task."""

    @only_one(key='SensorTask', timeout=60 * 5)
    def run(self, **kwargs):
        # Append some data to redis list
        redis_client.lpush('Sensor1', 1.50)


class DatabaseTask(app.Task):
    """A task."""

    # Database connection will stay the same in each process
    # See https://docs.celeryproject.org/en/latest/userguide/tasks.html
    _mongo_client = None

    @property
    def mongo_client(self):
        if self._mongo_client is None:
            self._mongo_client = pymongo.MongoClient()
        return self._mongo_client

    @only_one(key='DatabaseTask', timeout=60 * 5)
    def run(self, **kwargs):

        # Read current list of sensor values from Redis
        current_sensor_values = redis_client.lrange('Sensor1', 0, -1)

        # Convert Redis list to python float list
        # map compares to list comprehension is a bit faster in my case
        # values = [float(i) for i in current_sensor_values]
        values = list(map(float, current_sensor_values))

        # Example Mongo document to insert after 5 minutes of collecting data
        mongo_document = {
                'Timestamp': dt.now(),
                'first': values[0],
                'last': values[-1],
                'max' : max(values),
                'min' : min(values)
                }

        # Insert document to Mongo database and clean the Redis list
        self.mongo_client['Sensors']['Sensor1'].insert_one(mongo_document)
        redis_client.delete('Sensor1')
最后一步是将我们的任务注册到芹菜空间:

update_sensor = app.register_task(SensorTask())
update_database = app.register_task(DatabaseTask()) 

现在它可以很好地与多个工人一起工作。要运行任务,您需要使用创建的别名调用它—在我们的例子中是update_sensor.delay()和update_database.delay()

虽然我没有芹菜方面的经验,但要跨进程共享数据,最好的方法通常是使用队列。实际上,我尝试了一些基本的python共享内存方法,比如数组、值或Manager.list(),但我不知道如何正确处理芹菜任务