Python / Redis中的任务委托

时间:2018-08-07 08:57:39

标签: python python-3.x multithreading design-patterns redis

我在考虑可以解决以下问题的体系结构时遇到问题:

我有一个Web应用程序(生产者),可应要求接收一些数据。我也有许多应处理此数据的过程(消费者)。 1个请求会生成1批数据,并且只能由1个使用者处理。

我当前的解决方案包括接收数据,使用Redis将其缓存在内存中,通过消息通道发送一条消息,即消费者在同一通道上侦听时已写入数据,然后由消费者。这里的问题是,我需要阻止多个消费者使用相同的数据。那么,如何通知其他消费者我已经开始执行此任务?

生产者代码(烧瓶端点):

    data = request.get_json()
    db = redis.Redis(connection_pool=pool)
    db.set(data["externalId"], data)
    # Subscribe to the batches channel and publish the id
    db.pubsub()
    db.publish('batches', request_key)
    results = None
    result_key = str(data["externalId"])

    # Wait till the batch is processed
    while results is None:
        results = db.get(result_key)
        if results is not None:
            results = results.decode('utf8')

    db.delete(data["externalId"])
    db.delete(result_key)

消费者:

    db = redis.Redis(connection_pool = pool)
    channel = db.pubsub()
    channel.subscribe('batches')

    while True:
        try:
            message = channel.get_message()
            message_data = bytes(message['data']).decode('utf8')
            external_id = message_data.split('-')[-1]
            data = json.loads(db.get(external_id).decode('utf8'))
            result = DataProcessor.process(data)
            db.set(str(external_id), result)
        except Exception:
            pass

1 个答案:

答案 0 :(得分:1)

正是由于这个原因,

PUBSUB在任务排队中经常出现问题。从文档(https://redis.io/topics/pubsub):

  

SUBSCRIBE,UNSUBSCRIBE和PUBLISH实现了“发布/订阅”消息传递范例,其中(引用Wikipedia)发件人(发布者)没有被编程为将其消息发送给特定的接收者(订户)。而是将发布的消息按渠道进行分类,而不知道可能有哪些订户(如果有)。

可以考虑采用的一种流行替代方法是,通过将元素推送到Redis列表的末尾来实现“发布”,并通过让您的工作人员按一定的时间间隔对该列表进行轮询来实现“订阅”(指数退避通常是一个适当的选择) 。为了避免多个工人获得相同的工作,请使用lpop从列表中获取和删除元素。 Redis是单线程的,因此可以确保每个工人只有一个工人。

因此,在发布方面,目标是这样的:

db = redis.Redis(connection_pool=pool)
db.rpush("my_queue", task_payload)

在订阅方面,您可以根据需要安全地并行运行这样的循环:

while True:
    db = redis.Redis(connection_pool=pool)
    payload = db.lpop("my_queue")
    if not payload:
        continue
    < deserialize and process payload here >

请注意,这是后进先出队列(LIFO),因为我们将rpush推到右侧,然后使用lpop从左侧弹出。您可以通过组合lpush / lpop来轻松实现FIFO版本。